From b7c29c60e1eb52ae9e4009081b5d3a02193b0002 Mon Sep 17 00:00:00 2001 From: cruldra Date: Mon, 12 Oct 2026 15:11:10 +1100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(vscode):=20=E9=87=8D=E5=86=99?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=8A=8A=E5=8E=9F=E5=9E=8B=E5=AD=98=E8=BF=9B?= =?UTF-8?q?=20docs/prototype/<=E5=8A=9F=E8=83=BD>/openpencil/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 跟写注释技能 ECC v2.2.53 的原型约定:去掉临时草稿目录 spx-comment- 和 */prototypes 的建目录规则,会话 id 交给 Claude Code 起。 放开 Edit/Write 到 docs/prototype/*/openpencil/ 下、mkdir -p 到 docs/prototype/*/openpencil/exports;Bash 规则的 * 能跨空格,另外拒掉建目录时多跟一个路径的写法。 --- docs/superpowers/specs/code-reader/spec.md | 4 +- vscode/src/codeReader/qa/claudeAsk.test.ts | 57 +++++++++------------- vscode/src/codeReader/qa/claudeAsk.ts | 48 +++++++----------- 3 files changed, 43 insertions(+), 66 deletions(-) diff --git a/docs/superpowers/specs/code-reader/spec.md b/docs/superpowers/specs/code-reader/spec.md index f5d533b..ba1c5a4 100644 --- a/docs/superpowers/specs/code-reader/spec.md +++ b/docs/superpowers/specs/code-reader/spec.md @@ -640,9 +640,9 @@ - 按钮(`MessageSquareText` 图标,只有图标,悬停 / 读屏「按注释规范重写」,块还没有说明、也没有前面的普通注释时是「按注释规范写注释」):每块代码都有,不管卡片有没有「注释」一行都排在右上角那排按钮的最前面,依次是它、「问 AI」、「编辑」;文件开头的说明卡片也一样。定义卡片浮窗里、删掉的块没有;嵌着的代码里的块(Dockerfile RUN 里的 shell)、语言既没有说明的写法(`LanguageAdapter.freshDoc`)也没给行注释记号(`LanguageAdapter.lineComment`)、又还没有可换的注释的块没有(`BlockView.commentTarget` 没有);文件不在工作区文件夹里时重写记录没地方存,也没有。点了按技能命令重写(开新会话)、展开这块的重写记录;展开着又正在写时不能再点。重写过、记录收着时它叫「展开重写记录」,点了只展开(好接着说),展开着再点才重写。 - 改哪儿(`BlockView.commentTarget`,扩展端 `blockView.ts` 算):块已经有说明(Python 函数 / 类主体里第一条语句的 docstring、紧贴声明的 JSDoc、Rust 的 `///` / `//!`,以及别的语言记在块上的说明)时换掉这段说明的那几行,正文填回原来的定界符(`"""`、`/** */`、`///`),不在声明上面再插一行行注释;说明和块前面的普通注释都有时只换说明,普通注释留着。没有说明、块前面挂着普通注释时换掉它们(从第一段开头到最后一段结尾,几段中间隔着的空行一起换掉,写成一段);文件开头的说明卡片换掉它自己。都没有时新写:Python 的函数或类在主体里插一段 docstring(缩进同主体,作为第一条语句;声明和主体写在同一行时从主体那一列拆开);JavaScript / TypeScript 的函数、类、枚举、常量、变量在声明紧上面插 `/** */`;Rust 的项在紧上面插 `///`;别的(语句、Python 的变量,只有行注释的 shell、Dockerfile)在块的第一行上面插一行行注释。挂在块后面、子块交上来的注释不动。技能照这条规矩从范围本身判断写哪种:写说明时带 `@param` 等标签,前端组件的说明画原型图、嵌进正文。 - 问模型:同「问 AI」用 Claude Code 命令行、在这份文件所在的工作区文件夹里跑、用输入框旁边选的 profile,按 JSON Schema 交结果(`--json-schema '{"type":"object","properties":{"body":{"type":"string"}},"required":["body"],"additionalProperties":false}'`:模型调 `StructuredOutput` 交,result 行的 `structured_output.body` 是不带注释符号的正文;只回了一段话、`body` 是空的都算没交回)。文件有没保存的修改时不问(模型读的是磁盘上的文件),直接失败「先保存再重写注释」。 - - 第一轮(点按钮)从 stdin 发的只有一句 `/ecc:writing-code-comments <路径>:<起>-<止>`:路径相对 claude 的工作目录、用 `/` 分隔,行号 1 起,是这块代码的完整范围(连同挂在它上面的注释;文件开头的说明卡片是它自己那几行),开新会话(`--session-id` 由扩展起)。 + - 第一轮(点按钮)从 stdin 发的只有一句 `/ecc:writing-code-comments <路径>:<起>-<止>`:路径相对 claude 的工作目录、用 `/` 分隔,行号 1 起,是这块代码的完整范围(连同挂在它上面的注释;文件开头的说明卡片是它自己那几行),开新会话(会话 id 由 Claude Code 起,结果里带回来)。 - 输入框里说的话:`--resume` 这块最近一次重写的会话(和问答的会话分开,各接各的),发「块现在在哪(`<路径>:<起>-<止>`,写回注释后行号会变,按记号从最新的块树里找)、空一行、用户的话」,带上贴的图。会话找不到了(过期、被清理)就开新会话,发技能命令后面空一行跟着这句话。 - - 能做的事(`claudeAsk.ts` 的 `rewriteArgs`):`--restricted` 不读用户、项目、本地的设置文件(那里放行的命令进不来,比如用户设置里的 `git -C`),文件工具只认工作目录;写注释的插件单独加载(`--plugin-dir`,目录按 `claude plugin list --json` 里启用着的 ecc 找),它的目录和这个会话的草稿目录(系统临时目录下的 `spx-comment-<会话 id>`)加进工作目录(`--add-dir`)。工具只给读、搜、Bash、写文件(`--tools Read,Grep,Glob,Bash,Edit,Write`),不用再批的只有读搜、OpenPencil 的 `op`(`Bash(/Applications/OpenPencil.app/Contents/MacOS/op *)`)、建组件旁边的 `prototypes/` 目录(`Bash(mkdir -p */prototypes)`)、改草稿目录(`Edit(//<草稿目录>/**)`),别的一律拒绝、不等人批(`--permission-mode dontAsk`),不加载 MCP(`--strict-mcp-config`):改不了源码、跑不了别的命令,写回归扩展。本机 claude 2.1.296 试过:op 能跑、PNG 写进 `prototypes/`,改源码、`rm`、`git -C`、`node -e`、重定向写文件都被拒。 + - 能做的事(`claudeAsk.ts` 的 `rewriteArgs`):`--restricted` 不读用户、项目、本地的设置文件(那里放行的命令进不来,比如用户设置里的 `git -C`),文件工具只认工作目录;写注释的插件单独加载(`--plugin-dir`,目录按 `claude plugin list --json` 里启用着的 ecc 找),它的目录加进工作目录(`--add-dir`),技能读得到它的参考文档。原型照技能的约定存在仓库里:`docs/prototype/<功能>/openpencil/` 下的 `build.js`、`<功能>.op`,截图在它的 `exports/` 里,注释链到截图。工具只给读、搜、Bash、写文件(`--tools Read,Grep,Glob,Bash,Edit,Write`),不用再批的只有读搜、OpenPencil 的 `op`(`Bash(/Applications/OpenPencil.app/Contents/MacOS/op *)`)、建原型的截图目录(`Bash(mkdir -p docs/prototype/*/openpencil/exports)`)、改原型目录里的文件(`Edit(/docs/prototype/*/openpencil/**)`,开头的 `/` 是 claude 的工作目录,`*` 只配一层目录;Edit 规则也管 Write)。Bash 规则里的 `*` 能跨空格,建目录时后面多跟一个路径(`mkdir -p docs/prototype/x/openpencil/exports src/y`)也对得上,所以另外拒掉 `Bash(mkdir -p docs/prototype/* *)`(`--disallowedTools`)。别的一律拒绝、不等人批(`--permission-mode dontAsk`),不加载 MCP(`--strict-mcp-config`):改不了源码、跑不了别的命令,写回归扩展。本机 claude 2.1.296 用这组参数试过:写、改 `docs/prototype/x/openpencil/build.js`,建它的 `exports/`,`op --version` / `op status` 都行;写、改源码,写 `docs/prototype` 之外和 `docs/prototype/x/` 下 `openpencil/` 之外的文件,在别处建目录(`docs/other/exports`、`src/openpencil/exports`、仓库外的绝对路径、带 `..` 的路径、合法路径后面多跟一个),`rm`、`git -C`、`node -e`、重定向写文件都被拒。 - 画图比读文件慢,超时 15 分钟(问 AI 仍是 5 分钟)。 - 过程:说的话(第一轮是那句技能命令,开始后扩展端报上来)、正文逐字长出来(`--include-partial-messages` 时 `StructuredOutput` 的输入逐段来,按写到一半的 JSON 读出已经写出来的正文)、正在做什么(「读取 a.py」)。块下面同问答区的样子(`AskThread` 换成重写的称呼):灰字历史、当前这一轮、输入框(提示「说说这段注释要怎么改…」,旁边 profile 下拉,能贴图),正在写时发送换成停止。同一块同时只重写一轮,全插件同时最多 3 段。 - 写回(扩展端,`WorkspaceEdit`,能撤销):换掉时套回原来那段的定界符和缩进(Python 三引号、`#` / `//` / `///` / `//!` 行注释、`/* */` 块注释,`refillComment`;几段隔着空行的照第一段的写法);新写时用这块说明的壳(docstring、`/** */`、`///`,或行注释 `#` / `//`)一行一行写,缩进同插入的那一行,空行写成只有记号的一行(docstring 的空行仍是空行)。写之前核对这块代码的那几行和开始时一样,变了就不写(「这块代码在重写期间变了」);没交回正文、定界符认不出、编辑器没接受也不写。 diff --git a/vscode/src/codeReader/qa/claudeAsk.test.ts b/vscode/src/codeReader/qa/claudeAsk.test.ts index 217fa82..5ef5181 100644 --- a/vscode/src/codeReader/qa/claudeAsk.test.ts +++ b/vscode/src/codeReader/qa/claudeAsk.test.ts @@ -171,11 +171,10 @@ describe('失败了给人看的话', () => { }) describe('重写注释的命令行参数', () => { - const SCRATCH = '/private/var/folders/T/spx-comment-s1' - const allowed = 'Read,Grep,Glob,Bash(/Applications/OpenPencil.app/Contents/MacOS/op *),Bash(mkdir -p */prototypes),Edit(//private/var/folders/T/spx-comment-s1/**)' + const allowed = 'Read,Grep,Glob,Bash(/Applications/OpenPencil.app/Contents/MacOS/op *),Bash(mkdir -p docs/prototype/*/openpencil/exports),Edit(/docs/prototype/*/openpencil/**)' - it('不读用户、项目的设置(--restricted),写注释的插件单独加载、能读;只放开读搜、OpenPencil 的 op、建 prototypes 目录、改草稿目录,别的一律拒、不加载 MCP;按 JSON Schema 交结果', () => { - const args = rewriteArgs({ pluginRoot: '/plugins/ecc', scratch: SCRATCH, profilePath: '/p/sonnet.json', session: { start: 's1' } }) + it('不读用户、项目的设置(--restricted),写注释的插件单独加载、能读;只放开读搜、OpenPencil 的 op、建原型的 exports 目录、改原型目录,别的一律拒、不加载 MCP;按 JSON Schema 交结果', () => { + const args = rewriteArgs({ pluginRoot: '/plugins/ecc', profilePath: '/p/sonnet.json' }) expect(args).toEqual([ '--settings', '/p/sonnet.json', @@ -184,17 +183,15 @@ describe('重写注释的命令行参数', () => { '/plugins/ecc', '--add-dir', '/plugins/ecc', - '--add-dir', - SCRATCH, '--tools', 'Read,Grep,Glob,Bash,Edit,Write', '--allowedTools', allowed, + '--disallowedTools', + 'Bash(mkdir -p docs/prototype/* *)', '--permission-mode', 'dontAsk', '--strict-mcp-config', - '--session-id', - 's1', '-p', '--input-format', 'stream-json', @@ -205,16 +202,21 @@ describe('重写注释的命令行参数', () => { '--json-schema', COMMENT_SCHEMA, ]) - expect(args.join(' ')).not.toMatch(/dangerously|bypassPermissions/) + expect(args.join(' ')).not.toMatch(/dangerously|bypassPermissions|spx-comment|prototypes\)/) expect(JSON.parse(COMMENT_SCHEMA)).toEqual({ type: 'object', properties: { body: { type: 'string' } }, required: ['body'], additionalProperties: false }) }) - it('接着说时 --resume 那次会话、不带 --session-id;没有 profile 就不带 --settings', () => { - const args = rewriteArgs({ pluginRoot: '/plugins/ecc', scratch: SCRATCH, session: { resume: 's1' } }) + it('接着说时 --resume 那次会话,放在 -p 前面;没有 profile 就不带 --settings', () => { + const args = rewriteArgs({ pluginRoot: '/plugins/ecc', resume: 's1' }) expect(args[0]).toBe('--restricted') - expect(args).not.toContain('--session-id') expect(args.slice(args.indexOf('--resume'), args.indexOf('--resume') + 3)).toEqual(['--resume', 's1', '-p']) }) + + it('开新会话不带 --resume,会话 id 由 Claude Code 起', () => { + const args = rewriteArgs({ pluginRoot: '/plugins/ecc' }) + expect(args).not.toContain('--resume') + expect(args).not.toContain('--session-id') + }) }) describe('写注释的插件在哪', () => { @@ -245,25 +247,18 @@ describe('写注释的插件在哪', () => { describe('重写注释问一次', () => { const structured = (body: string, session = 's1'): string => line({ type: 'result', subtype: 'success', is_error: false, result: JSON.stringify({ body }), structured_output: { body }, session_id: session }) - /** 假的 claude 再加上找插件、建草稿目录、起会话 id:都记下来 */ + /** 假的 claude 再加上找插件:问过哪个目录记下来 */ function commentDeps(runs: { lines: string[], error?: Error }[]) { const { deps, calls } = fakeClaude(runs) - const scratches: string[] = [] const lookups: string[] = [] - let next = 0 const commentDeps: CommentAskDeps = { ...deps, pluginRoot: async (cwd) => { lookups.push(cwd) return '/plugins/ecc' }, - scratch: async (sessionId) => { - scratches.push(sessionId) - return `/tmp/spx-comment-${sessionId}` - }, - newSessionId: () => `new-${++next}`, } - return { deps: commentDeps, calls, scratches, lookups } + return { deps: commentDeps, calls, lookups } } function comment(overrides: Partial = {}): CommentAsk & { events: unknown[] } { @@ -271,35 +266,31 @@ describe('重写注释问一次', () => { return { prompt: '/ecc:writing-code-comments src/a.py:3-8', images: [], cwd: '/work', signal: new AbortController().signal, onEvent: event => events.push(event), events, ...overrides } } - it('开新会话:自己起会话 id、按它建草稿目录,带上插件目录和超时;stdin 里是那句提示和贴的图;返回正文和会话 id', async () => { - const { deps, calls, scratches, lookups } = commentDeps([{ lines: [structured('按名字找文件。', 'new-1')] }]) + it('开新会话:带上插件目录和超时;stdin 里是那句提示和贴的图;返回正文和 Claude Code 起的会话 id', async () => { + const { deps, calls, lookups } = commentDeps([{ lines: [init('new-1'), structured('按名字找文件。', 'new-1')] }]) const ask = comment({ profilePath: '/p/sonnet.json', images: [PNG] }) expect(await askCommentBody(ask, deps)).toEqual({ body: '按名字找文件。', sessionId: 'new-1' }) expect(lookups).toEqual(['/work']) - expect(scratches).toEqual(['new-1']) - expect(calls[0].args).toEqual(rewriteArgs({ pluginRoot: '/plugins/ecc', scratch: '/tmp/spx-comment-new-1', profilePath: '/p/sonnet.json', session: { start: 'new-1' } })) + expect(calls[0].args).toEqual(rewriteArgs({ pluginRoot: '/plugins/ecc', profilePath: '/p/sonnet.json' })) expect(calls[0]).toMatchObject({ cwd: '/work', signal: ask.signal, timeoutMs: REWRITE_TIMEOUT_MS }) expect(sent(calls[0])).toMatchObject({ text: '/ecc:writing-code-comments src/a.py:3-8', images: [{ source: { data: PNG.base64 } }] }) }) - it('接着说:--resume 这块最近一次重写的会话,用那个会话的草稿目录,发块现在在哪和用户的话', async () => { - const { deps, calls, scratches } = commentDeps([{ lines: [structured('短一点。', 's0')] }]) + it('接着说:--resume 这块最近一次重写的会话,发块现在在哪和用户的话', async () => { + const { deps, calls } = commentDeps([{ lines: [structured('短一点。', 's0')] }]) expect(await askCommentBody(comment({ followUp: { sessionId: 's0', prompt: 'src/a.py:3-10\n\n短一点' } }), deps)).toEqual({ body: '短一点。', sessionId: 's0' }) - expect(scratches).toEqual(['s0']) - expect(calls[0].args).toEqual(rewriteArgs({ pluginRoot: '/plugins/ecc', scratch: '/tmp/spx-comment-s0', session: { resume: 's0' } })) + expect(calls[0].args).toEqual(rewriteArgs({ pluginRoot: '/plugins/ecc', resume: 's0' })) expect(sent(calls[0]).text).toBe('src/a.py:3-10\n\n短一点') }) it('那次会话找不到了:开新会话按技能命令重新来,图片照样带上', async () => { - const { deps, calls, scratches } = commentDeps([ + const { deps, calls } = commentDeps([ { lines: [line({ type: 'result', subtype: 'error_during_execution', is_error: true, errors: ['No conversation found with session ID: s0'], session_id: 's0' })] }, { lines: [structured('重新写的。', 'new-1')] }, ]) const ask = comment({ prompt: '/ecc:writing-code-comments src/a.py:3-10\n\n短一点', images: [PNG], followUp: { sessionId: 's0', prompt: 'src/a.py:3-10\n\n短一点' } }) expect(await askCommentBody(ask, deps)).toEqual({ body: '重新写的。', sessionId: 'new-1' }) - expect(scratches).toEqual(['s0', 'new-1']) - expect(calls[1].args).toContain('--session-id') - expect(calls[1].args).not.toContain('--resume') + expect(calls[1].args).toEqual(rewriteArgs({ pluginRoot: '/plugins/ecc' })) expect(sent(calls[1])).toMatchObject({ text: '/ecc:writing-code-comments src/a.py:3-10\n\n短一点', images: [{ source: { data: PNG.base64 } }] }) }) diff --git a/vscode/src/codeReader/qa/claudeAsk.ts b/vscode/src/codeReader/qa/claudeAsk.ts index 17b4fc7..b47148e 100644 --- a/vscode/src/codeReader/qa/claudeAsk.ts +++ b/vscode/src/codeReader/qa/claudeAsk.ts @@ -6,10 +6,6 @@ import type { ClaudeImage } from '../../cc/spawnClaude' import type { CommentEvent } from '../commentRewrite' import type { AnswerEvent } from './answerStream' -import { randomUUID } from 'node:crypto' -import { mkdir, realpath } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import * as path from 'node:path' import { buildStreamJsonLine, CLAUDE_MISSING_MSG, ClaudeCancelledError, ClaudeError, ClaudeTimeoutError, runClaudeLines } from '../../cc/spawnClaude' import { COMMENT_PLUGIN, COMMENT_SCHEMA, createCommentParser } from '../commentRewrite' import { createAnswerParser } from './answerStream' @@ -25,6 +21,9 @@ export const REWRITE_TIMEOUT_MS = 15 * 60_000 /** OpenPencil 装在应用里的命令行(写注释的技能用它画前端组件的原型图) */ export const OPENPENCIL_CLI = '/Applications/OpenPencil.app/Contents/MacOS/op' +/** 写注释的技能存原型的目录,`*` 是功能名(`my-apps`)。权限规则里写成 `Edit(/…)`,开头的 `/` 指 claude 的工作目录(仓库根目录),不是磁盘根目录。 */ +const PROTOTYPE_DIR = 'docs/prototype/*/openpencil' + /** * 一次提问的命令行参数;问题连同贴的图片从 stdin 按 stream-json 发(见 askClaude),没贴图也一样。只读靠三层:可用的内置工具只剩读、搜(`--tools`);这几个不用再批(`--allowedTools`), * 别的一律拒绝、不等人批(`--permission-mode dontAsk`,盖过 profile 里的 bypassPermissions); @@ -53,14 +52,15 @@ export function askArgs(options: { profilePath?: string, resume?: string } = {}) /** * 一次重写注释的命令行参数,按 COMMENT_SCHEMA 交结构化结果(`--json-schema`)。除了读、搜,只放开写注释的技能画原型图要的: - * 运行 OpenPencil 的 op、建组件旁边的 `prototypes/` 目录、在草稿目录里写 build.js(`--allowedTools` 里的规则),别的一律拒绝、不等人批。 + * 运行 OpenPencil 的 op、建原型的 `exports/` 目录、改 [PROTOTYPE_DIR] 里的文件(`--allowedTools` 里的规则),别的一律拒绝、不等人批。 + * 规则里的 `*` 能跨空格,`mkdir -p docs/prototype/x/openpencil/exports src/y` 也对得上,所以建目录时多跟一个路径的另外拒掉(`--disallowedTools`)。 * `--restricted` 不读用户、项目的设置文件,那里放行的命令(比如 `git -C`)进不来,文件工具也只认工作目录; - * 写注释的插件因此要单独加载(`--plugin-dir`),它的目录和草稿目录加进工作目录(`--add-dir`),技能才读得到它的参考文档、写得了草稿。 - * session:新会话用 start 当会话 id(草稿目录按它起名),接着说时 resume 那次会话。 + * 写注释的插件因此要单独加载(`--plugin-dir`),它的目录加进工作目录(`--add-dir`),技能才读得到它的参考文档。 + * 有 resume 就接着那次会话说,没有就开新会话,会话 id 由 Claude Code 起。 */ -export function rewriteArgs(options: { pluginRoot: string, scratch: string, profilePath?: string, session: { start: string } | { resume: string } }): string[] { - const { pluginRoot, scratch, profilePath, session } = options - const allowed = [READ_ONLY_TOOLS, `Bash(${OPENPENCIL_CLI} *)`, 'Bash(mkdir -p */prototypes)', `Edit(/${scratch}/**)`].join(',') +export function rewriteArgs(options: { pluginRoot: string, profilePath?: string, resume?: string }): string[] { + const { pluginRoot, profilePath, resume } = options + const allowed = [READ_ONLY_TOOLS, `Bash(${OPENPENCIL_CLI} *)`, `Bash(mkdir -p ${PROTOTYPE_DIR}/exports)`, `Edit(/${PROTOTYPE_DIR}/**)`].join(',') return [ ...(profilePath ? ['--settings', profilePath] : []), '--restricted', @@ -68,16 +68,16 @@ export function rewriteArgs(options: { pluginRoot: string, scratch: string, prof pluginRoot, '--add-dir', pluginRoot, - '--add-dir', - scratch, '--tools', `${READ_ONLY_TOOLS},Bash,Edit,Write`, '--allowedTools', allowed, + '--disallowedTools', + 'Bash(mkdir -p docs/prototype/* *)', '--permission-mode', 'dontAsk', '--strict-mcp-config', - ...('resume' in session ? ['--resume', session.resume] : ['--session-id', session.start]), + ...(resume ? ['--resume', resume] : []), '-p', '--input-format', 'stream-json', @@ -186,24 +186,11 @@ export interface CommentAnswer { export interface CommentAskDeps extends AskDeps { /** 写注释的技能所在插件的目录,按 cwd 问(项目里装的插件也算) */ pluginRoot: (cwd: string) => Promise - /** 这个会话画原型用的草稿目录,建好了返回 */ - scratch: (sessionId: string) => Promise - /** 新会话的 id */ - newSessionId: () => string -} - -/** 这个会话画原型的草稿目录:系统临时目录下的 `spx-comment-<会话 id>`,同一会话每轮都是它。用真实路径,权限规则按它比对。不删,留给系统清。 */ -async function commentScratch(sessionId: string): Promise { - const dir = path.join(await realpath(tmpdir()), `spx-comment-${sessionId}`) - await mkdir(dir, { recursive: true }) - return dir } const COMMENT_DEPS: CommentAskDeps = { runLines: runClaudeLines, pluginRoot: cwd => commentPluginRoot(cwd), - scratch: commentScratch, - newSessionId: randomUUID, } /** @@ -212,13 +199,12 @@ const COMMENT_DEPS: CommentAskDeps = { */ export async function askCommentBody(ask: CommentAsk, deps: CommentAskDeps = COMMENT_DEPS): Promise { const pluginRoot = await deps.pluginRoot(ask.cwd) - const once = async (prompt: string, session: { start: string } | { resume: string }): Promise => { - const scratch = await deps.scratch('resume' in session ? session.resume : session.start) + const once = async (prompt: string, resume?: string): Promise => { const parse = createCommentParser() let result: CommentAnswer | undefined let failure: string | undefined await deps.runLines({ - args: rewriteArgs({ pluginRoot, scratch, profilePath: ask.profilePath, session }), + args: rewriteArgs({ pluginRoot, profilePath: ask.profilePath, resume }), input: buildStreamJsonLine(prompt, ask.images), cwd: ask.cwd, signal: ask.signal, @@ -242,14 +228,14 @@ export async function askCommentBody(ask: CommentAsk, deps: CommentAskDeps = COM } if (ask.followUp) { try { - return await once(ask.followUp.prompt, { resume: ask.followUp.sessionId }) + return await once(ask.followUp.prompt, ask.followUp.sessionId) } catch (err) { if (!missingSession(err)) throw err } } - return once(ask.prompt, { start: deps.newSessionId() }) + return once(ask.prompt) } /** `claude plugin list --json` 里启用着的 name 插件的目录;没装、没启用、读不出来时没有。 */