✨ feat(vscode): 重写注释留会话记录、能接着说,允许画原型图
每块的重写记录存进 .spx/comment-rewrites.json(和问答分开,改名、删文件跟着走),块下面同问答区:灰字历史、当前这一轮、输入框;输入框里的话接着这块最近一次重写的会话说,交回新正文照样写回,写回失败也记下原因。 重写跑在 claude --restricted 下:只放开读搜、OpenPencil 的 op、建 prototypes 目录、改草稿目录,写注释的插件单独加载;超时 15 分钟。 进行中的、出错被叫停留着的那一轮记在扩展端,切走标签页再切回来照样接上(问 AI 同一套)。
This commit is contained in:
1 parent
ae4e2160ef
commit
c9f60f5b30
32 files changed
+1654
-860
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import type { QaEntry, QaMap } from './qaMap'
|
||||
import type { QaEntry, QaMap, RewriteEntry, RewriteMap } from './qaMap'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
@@ -6,7 +6,7 @@ import * as path from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { detachedFiles, dropEntries, moveEntries, readPathMap, removeFiles, writePathMap } from '../pathMap'
|
||||
import { writeQaImages } from './qaImages'
|
||||
import { latestSession, QA_FILE, withEntry, withoutEntry } from './qaMap'
|
||||
import { latestSession, QA_FILE, REWRITE_FILE, withEntry, withoutEntry } from './qaMap'
|
||||
|
||||
function entry(id: string, createdAt: string, question = `问 ${id}`): QaEntry {
|
||||
return { id, question, answer: `答 ${id}`, createdAt, sessionId: `s-${id}` }
|
||||
@@ -177,8 +177,8 @@ describe('问题带的图片', () => {
|
||||
})
|
||||
|
||||
it('删一次问答,它的图片跟着删;别的问答的图片留着', async () => {
|
||||
const a = { ...entry('a', '2026-09-25T10:00:00.000Z'), images: await writeQaImages(root, 'a', [png('a1'), png('a2')]) }
|
||||
const b = { ...entry('b', '2026-09-26T10:00:00.000Z'), images: await writeQaImages(root, 'b', [png('b1')]) }
|
||||
const a: QaEntry = { ...entry('a', '2026-09-25T10:00:00.000Z'), images: await writeQaImages(root, 'a', [png('a1'), png('a2')]) }
|
||||
const b: QaEntry = { ...entry('b', '2026-09-26T10:00:00.000Z'), images: await writeQaImages(root, 'b', [png('b1')]) }
|
||||
await update(map => withEntry(withEntry(map, 'a.py', IF, a), 'a.py', DISPLAY, b))
|
||||
expect(await exists(imageFile('a-1.png'))).toBe(true)
|
||||
await update(map => withoutEntry(map, 'a.py', IF, 'a'))
|
||||
@@ -188,7 +188,7 @@ describe('问题带的图片', () => {
|
||||
})
|
||||
|
||||
it('源文件删了、问答跟着删时图片也删;改名、挪位置按问答 id 起的名字不用动', async () => {
|
||||
const a = { ...entry('a', '2026-09-25T10:00:00.000Z'), images: await writeQaImages(root, 'a', [png('a1')]) }
|
||||
const a: QaEntry = { ...entry('a', '2026-09-25T10:00:00.000Z'), images: await writeQaImages(root, 'a', [png('a1')]) }
|
||||
await update(map => withEntry(map, 'src/a.py', IF, a))
|
||||
await update(map => moveEntries(map, 'src', 'lib'))
|
||||
expect(await readPathMap(root, QA_FILE)).toEqual({ 'lib/a.py': [{ anchor: IF, entries: [a] }] })
|
||||
@@ -197,3 +197,47 @@ describe('问题带的图片', () => {
|
||||
expect(await exists(imageFile('a-1.png'))).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('重写注释的记录:另存一份,和问答分开', () => {
|
||||
const rewriteFile = (): string => path.join(root, '.spx', 'comment-rewrites.json')
|
||||
const exists = (file: string): Promise<boolean> => fsp.access(file).then(() => true, () => false)
|
||||
|
||||
function rewrite(id: string, createdAt: string, extra: Partial<RewriteEntry> = {}): RewriteEntry {
|
||||
return { id, question: '/ecc:writing-code-comments a.py:3-8', body: `正文 ${id}`, createdAt, sessionId: `r-${id}`, ...extra }
|
||||
}
|
||||
|
||||
it('存在 .spx/comment-rewrites.json,不进问答那份;正文、没写进文件的原因跟着这条读得回来,接着最近一次重写的会话', async () => {
|
||||
const map: RewriteMap = withEntry(withEntry({}, 'a.py', IF, rewrite('a', '2026-09-25T10:00:00.000Z')), 'a.py', IF, rewrite('b', '2026-09-26T10:00:00.000Z', { error: '这段注释的定界符认不出来,没有改文件。' }))
|
||||
await writePathMap(root, REWRITE_FILE, map)
|
||||
expect(await exists(rewriteFile())).toBe(true)
|
||||
expect(await exists(qaFile())).toBe(false)
|
||||
expect(await readPathMap(root, REWRITE_FILE)).toEqual(map)
|
||||
expect(await readPathMap(root, QA_FILE)).toEqual({})
|
||||
expect(latestSession(map['a.py'], IF)).toBe('r-b')
|
||||
})
|
||||
|
||||
it('模型没交回正文的那次只记原因;正文和原因都没有、正文是空的不认', async () => {
|
||||
await fsp.mkdir(path.dirname(rewriteFile()), { recursive: true })
|
||||
const silent = rewrite('s', '2026-09-25T10:00:00.000Z', { error: '模型没有按要求交回注释正文,没有改文件。' })
|
||||
delete silent.body
|
||||
await fsp.writeFile(rewriteFile(), JSON.stringify({ 'a.py': [{ anchor: IF, entries: [
|
||||
silent,
|
||||
{ ...rewrite('bare', '2026-09-25T11:00:00.000Z'), body: undefined },
|
||||
rewrite('blank', '2026-09-25T12:00:00.000Z', { body: ' ' }),
|
||||
] }] }), 'utf8')
|
||||
expect(await readPathMap(root, REWRITE_FILE)).toEqual({ 'a.py': [{ anchor: IF, entries: [silent] }] })
|
||||
})
|
||||
|
||||
it('带的图片和问答的放在同一个目录,删一条重写记录只删它自己的', async () => {
|
||||
const png = { mediaType: 'image/png', base64: Buffer.from('r').toString('base64') }
|
||||
const image = path.join(root, '.spx', 'code-qa', 'r-1.png')
|
||||
const withImage = rewrite('r', '2026-09-25T10:00:00.000Z', { images: await writeQaImages(root, 'r', [png]) })
|
||||
const before = withEntry({}, 'a.py', IF, withImage)
|
||||
await writePathMap(root, REWRITE_FILE, before)
|
||||
expect(await readPathMap(root, REWRITE_FILE)).toEqual(before)
|
||||
const after = withoutEntry(before, 'a.py', IF, 'r')
|
||||
await writePathMap(root, REWRITE_FILE, after)
|
||||
await removeFiles(root, detachedFiles(REWRITE_FILE, before, after))
|
||||
expect(await exists(image)).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user