✨ feat(vscode): 重写注释留会话记录、能接着说,允许画原型图

每块的重写记录存进 .spx/comment-rewrites.json(和问答分开,改名、删文件跟着走),块下面同问答区:灰字历史、当前这一轮、输入框;输入框里的话接着这块最近一次重写的会话说,交回新正文照样写回,写回失败也记下原因。
重写跑在 claude --restricted 下:只放开读搜、OpenPencil 的 op、建 prototypes 目录、改草稿目录,写注释的插件单独加载;超时 15 分钟。
进行中的、出错被叫停留着的那一轮记在扩展端,切走标签页再切回来照样接上(问 AI 同一套)。
This commit is contained in:
cruldra committed 2026-10-12 13:45:37 +11:00
1 parent ae4e2160ef
commit c9f60f5b30
32 files changed
+1654 -860

No files matched your search

+15 -10
View File
@@ -184,7 +184,7 @@
库规则的种类用规则给的图标(`KindBadge.icon`),跟在语言的种类图标后面:SQLAlchemy 数据表 `database`、数据类 `package`、Pydantic 数据模型 `braces`(和类型块同一个图标,颜色不同)、Celery 应用 `server-cog`、定时任务 `calendar-clock`、FastAPI 接口 `globe`、FastAPI 路由器 `network`、React 组件 `atom`、状态 `toggle-right`、引用 `anchor`、数据源 `download`、派生值 `sigma`、副作用 `zap`、事件处理函数 `mouse-pointer-click`、渲染输出 `layout-template`、serde 可序列化 `braces`、thiserror 错误 `triangle-alert`、axum 接口 `globe`、sqlx 查询 `database`、tauri 桌面命令 `square-function`。数据类写了 `frozen=True` 再加一把锁(`lock`,悬停「不可修改」)。实现块再由规则 `rust.impl` 换成卡片,左边因此有两个「实现」图标。
这组图标由插件自己定义,是一个封闭的集合:契约里的 `KIND_ICON_NAMES`(上表 26 个——命令、管道、执行共用一个,解释器、解析指令共用一个,选项、设置、编译属性共用一个,字段、标签共用一个——加库规则用的 14 个(React 的组件、状态、引用、数据源、派生值、副作用、事件处理函数、渲染输出的 8 个也在里面),lucide 的 kebab-case 名字),类型 `KindIconName` 是它们的联合,概念的 `icon` 只能写其中一个,写别的是类型错误。内置种类 → 图标名在 `contract.ts` 的 `KIND_ICONS`(宿主写进概念列表)。webview 给每个名字配一个逐个 import 的 lucide 图标组件(`kindIcons.tsx` 的 `KIND_ICON_COMPONENTS`,打包只带这些,预览脚本约 157 kB);webview 的 `KindIconName` 照契约手抄,`kindIconNames.test.ts` 核对两边一样。规则要新图标就把名字加进这组、在 webview 配上图标,没有按任意名字找图标的路。
- 右上角的图标按钮(原型组件 `Reader / BlockTools`,普通块、属性表块、库规则的卡片共用):只有图标、不带字,名字写在悬停提示和 `aria-label` 里;整排是 `role="group"`(`aria-label="块操作"`)。最前面是「按注释规范重写」(注释图标,块还没有注释时叫「按注释规范写注释」;不能写注释的块没有,见「重写注释」),然后是「问 AI」(sparkles 图标,点开 / 收起块下面的问答区,展开时带按下的底色、`aria-expanded`;文件不在工作区文件夹里时没有,见「问 AI」),再是「编辑 / 预览」切换:预览时铅笔「编辑」,编辑时眼睛「预览」、带按下的底色(`--vscode-toolbar-activeBackground`)。按钮宽 24px、高 20px(和种类图标同高),图标 14px、`descriptionForeground` 色;悬停 `--vscode-toolbar-hoverBackground`,键盘聚焦描 1px `--vscode-focusBorder`;Tab 能走到,回车 / 空格切换。
- 右上角的图标按钮(原型组件 `Reader / BlockTools`,普通块、属性表块、库规则的卡片共用):只有图标、不带字,名字写在悬停提示和 `aria-label` 里;整排是 `role="group"`(`aria-label="块操作"`)。最前面是「按注释规范重写」(注释图标,块还没有注释时叫「按注释规范写注释」,重写过、记录收着时叫「展开重写记录」;不能写注释的块没有,见「重写注释」),然后是「问 AI」(sparkles 图标,点开 / 收起块下面的问答区,展开时带按下的底色、`aria-expanded`;文件不在工作区文件夹里时没有,见「问 AI」),再是「编辑 / 预览」切换:预览时铅笔「编辑」,编辑时眼睛「预览」、带按下的底色(`--vscode-toolbar-activeBackground`)。按钮宽 24px、高 20px(和种类图标同高),图标 14px、`descriptionForeground` 色;悬停 `--vscode-toolbar-hoverBackground`,键盘聚焦描 1px `--vscode-focusBorder`;Tab 能走到,回车 / 空格切换。
- 悬停提示(整个插件的网页只走这一条路,预览和看板共用同一个组件):带提示的元素只写 `data-tooltip="提示"`(名字照旧写在 `aria-label` 里,只有图标的按钮必须有 `aria-label`),页面根上挂一个 `TooltipHost`(`webview-ui/src/components/ui/tooltip.tsx`)统一弹,不用原生 `title`;提示里的换行照样换行(看板摘修复弹框里「迁移」列出的几个文件一行一个);注释、回答里 Markdown 链接的标题(`[文字](网址 "标题")`)也写成 `data-tooltip`。网页的 TSX 里写原生 `title` 属性是 lint 错误(`vscode/eslint.config.mjs`),只有 `iframe` 这种必须带标题的元素例外,组件自己叫 `title` 的属性不算。原因:macOS 上 VS Code 带的 Electron(1.139 是 Electron 43)只在鼠标压着带 `title` 的元素自身边缘时才弹原生提示,停在它里面的图标、文字上不弹(electron/electron#49843,Electron 38 起),种类图标 20px 的框几乎被 16px 的图形占满,基本弹不出来;样子也是系统的。样子照 VS Code 的悬停框:`--vscode-editorHoverWidget-background` 底、`--vscode-editorHoverWidget-border` 边框、`--vscode-editorHoverWidget-foreground` 字、`0 2px 8px --vscode-widget-shadow` 阴影,11px 字,圆角 4px,最宽 22rem;优先在元素上方,上方放不下翻到下方,水平对着元素中间,贴边留 8px,不接鼠标。鼠标停 500ms 才弹,在同一个元素里移动不重新计时,已经弹着时移到另一个带提示的元素上立刻换;移开、移出页面、按下鼠标、按任意键(含 Esc)、滚动就收,收了之后鼠标不离开这个元素不再弹。键盘把焦点移到带提示的元素上(`:focus-visible`)立刻弹,焦点离开就收;提示不是元素自己的名字(`aria-label` 或它的字)时用 `aria-describedby` 挂到元素上,读屏当描述念。
- 种类图标和底色按种类区分(颜色见「颜色」);文件开头的说明(种类「注释」)虚线框。
- 「注释」一行:讲这块的普通注释不单独成块,是这张卡片上的一行(见「注释挂到它讲的块上」)。属性表卡片(语言的、翻译插件排的、库规则的卡片都一样,宿主统一插,规则、翻译插件不用管)插在第一行(名称这类讲它是谁的一行)后面,有说明(值是 Markdown 的那一行)时插在说明后面,所以是「名称、说明、注释、别的」;一行译文的卡片(条件、循环、语句、调用、返回……)排在译文、摊开实参的表、说明的下面,同属性表的一行(左边「注释」、右边 Markdown)。名字是翻译插件给种类 comment 起的名字(中文直译「注释」)。几段注释(中间隔着空行、或前后两处)各是一段 Markdown,照源码顺序,空行隔开。按「注释的 Markdown 排版」显示(仓库路径链接、不带路径的引用、仓库图片都照常能点),标差异时同属性表的行逐词标。这一行上不放按钮,「按注释规范重写」排在卡片右上角那排按钮里(见「重写注释」)。
@@ -605,11 +605,11 @@
- 选项是插件的 profile 目录(同看板、会话:`cc/profiles` 的 `listClaudeProfiles`,名字是文件名去掉 `.json`)。插件的默认 profile(`getDefaultProfilePath`,`offical.json`)在里面时,没选过就选它;不在(一个 profile 都没有,或没有 `offical.json`)时最前面多一项「默认」:不带 `--settings`,claude 用 `~/.claude` 的登录,和以前一样照问,没选过就选它。
- 选了从下一个问题起用它(正在答的不受影响)。所有预览、所有块共用一份,记在扩展的 `globalState`(`superpowers.codeReader.askProfile`,记 profile 名,「默认」记 `''`)里:换个预览、重启 VS Code 都还是它;记着的那个没了(删了、目录换了)就回到默认那项。
- 没展开、但问过的块:块下面一行灰字「问过 N 次」,下面默认摊开灰字历史(能展开、能删),读的时候一眼看到这里问过什么;点这一行收起历史、再点摊开,箭头跟着换;没有输入框。
- 收起就结束这一轮:这一轮答完的都回到灰字历史里(再问照样接着会话,见「追问」);还没答完的接着答,答完照样存。同一块同时只答一个问题(正在答时不能再发),不同块可以同时问,全插件同时最多 3 个,再多就在这一问下面说「同时最多问 3 个问题,等前面的答完再问」。
- 收起就结束这一轮:这一轮答完的都回到灰字历史里(再问照样接着会话,见「追问」);还没答完的接着答,答完照样存;出错、被叫停没存的那一轮扩展端也不再留着。同一块同时只答一个问题(正在答时不能再发),不同块可以同时问,全插件同时最多 3 个,再多就在这一问下面说「同时最多问 3 个问题,等前面的答完再问」。
- 追问:这块存过问答,再问就接着最近一次(按问的时间)存下的那条的会话(`--resume <会话 id>`,会话 id 取 stream-json 的 init 消息,和问答一起存在 `code-qa.json` 里),只发问题和「还是第几行那块」;收起再展开、预览重新加载、重启 VS Code 都一样接得上,前面问过的照旧灰字显示。换了 profile 也照样接着那次会话问(`--resume` 和这次选的 `--settings` 一起带,本机 claude 2.1 试过:换 profile 后会话 id 不变、记得前面说过的),线程里不另外提示。那条没记会话 id、或会话找不到了(过期、被清理)就按第一次那样带上代码上下文重新开会话。删掉最近那条,就接着剩下里最近的;一块下面删光了就是没问过。
- 删除:点垃圾桶马上删,不用确认;一块下面删光了这块就不挂了,一份文件删光了文件里就没有这份文件。那条带的图片文件一起删。
- 找不到原来位置的问答:记号(见下)对不上这份文件里任何一块的问答不显示,也不动它,留在 `code-qa.json` 里;代码改回来、记号又对上了,就又挂回那块下面。
- 预览重新加载(标签页切走再切回来)后,扩展端还在答的问题接上(展开它那块、接着长),答完照样显示。同一份文件开在几处,问答、正在答的都一样。
- 预览重新加载(标签页切走再切回来)后,扩展端记着的当前一轮接上(展开它那块):还在答的接着长、答完照样显示,出错、被叫停留着的照原样画出来(收起这块前一直留着)。同一份文件开在几处,问答、正在答的都一样。
怎么问(扩展端 `qa/`):
@@ -635,14 +635,19 @@
### 重写注释
按写注释的技能(`/ecc:writing-code-comments`)给一块代码重写它的说明。没有说明、但块前面有普通注释时换掉那段;都没有就按这门语言的说明新写一段。
按写注释的技能(`/ecc:writing-code-comments`)给一块代码重写它的注释。没有说明、但块前面有普通注释时换掉那段;都没有就按这门语言的说明新写一段。像问 AI 一样留会话、能接着说:块下面记着重写过的每一轮,在输入框里说修改意见,模型接着那次会话改,交回新正文就写回文件。
- 按钮(`MessageSquareText` 图标,只有图标,悬停 / 读屏「按注释规范重写」,块还没有说明、也没有前面的普通注释时是「按注释规范写注释」):每块代码都有,不管卡片有没有「注释」一行都排在右上角那排按钮的最前面,依次是它、「问 AI」、「编辑」;文件开头的说明卡片也一样。正在重写时不能再点。定义卡片浮窗里、删掉的块没有;嵌着的代码里的块(Dockerfile RUN 里的 shell)、语言既没有说明的写法(`LanguageAdapter.freshDoc`)也没给行注释记号(`LanguageAdapter.lineComment`)、又还没有可换的注释的块没有(`BlockView.commentTarget` 没有)。
- 改哪儿(`BlockView.commentTarget`,扩展端 `blockView.ts` 算):块已经有说明(Python 函数 / 类主体里第一条语句的 docstring、紧贴声明的 JSDoc、Rust 的 `///` / `//!`,以及别的语言记在块上的说明)时换掉这段说明的那几行,正文填回原来的定界符(`"""`、`/** */`、`///`),不在声明上面再插一行行注释;说明和块前面的普通注释都有时只换说明,普通注释留着。没有说明、块前面挂着普通注释时换掉它们(从第一段开头到最后一段结尾,几段中间隔着的空行一起换掉,写成一段);文件开头的说明卡片换掉它自己。都没有时新写:Python 的函数或类在主体里插一段 docstring(缩进同主体,作为第一条语句;声明和主体写在同一行时从主体那一列拆开);JavaScript / TypeScript 在声明紧上面插 `/** */`;Rust 在项紧上面插 `///`;只有行注释的语言(shell、Dockerfile)仍在块的第一行上面插一行行注释。挂在块后面、子块交上来的注释不动。
- 问模型:同「问 AI」用 Claude Code 命令行、在这份文件所在的工作区文件夹里跑、用选的 profile、只读(`--tools Read,Grep,Glob`、`--allowedTools Read,Grep,Glob`、`--permission-mode dontAsk`、`--strict-mcp-config`:它能读那个文件,改不了,写回归扩展),从 stdin 发的只有一句:`/ecc:writing-code-comments <路径>:<起>-<止>`,路径相对 claude 的工作目录、用 `/` 分隔,行号 1 起,是这块代码的完整范围(连同挂在它上面的注释;文件开头的说明卡片是它自己那几行),别的不说(`commentRewrite.ts` 的 `commentRewritePrompt`)。文件有没保存的修改时不问(模型读的是磁盘上的文件),直接失败「先保存再重写注释」。
- 结果按 JSON Schema 交(`--json-schema '{"type":"object","properties":{"body":{"type":"string"}},"required":["body"],"additionalProperties":false}'`):模型调 Claude Code 的 `StructuredOutput` 工具交结果,result 行的 `structured_output.body` 是注释正文(不带注释符号)。不读模型的自由文字:只回了一段话、`structured_output` 没有或 `body` 是空的,都算失败,文件不动。
- 过程(`rewriteProgress`):问的那一句、正文逐字长出来(`--include-partial-messages` 时 `StructuredOutput` 的输入逐段来,按写到一半的 JSON 读出已经写出来的正文)、正在读什么(「读取 a.py」);块下面同问答区的样子显示(`RewriteThread`),能叫停。
- 写回(扩展端,`WorkspaceEdit`,能撤销):换掉时套回原来那段的定界符和缩进(Python 三引号、`#` / `//` / `///` / `//!` 行注释、`/* */` 块注释,`refillComment`;几段隔着空行的照第一段的写法);新写时用这块说明的壳(docstring、`/** */`、`///`,或只有行注释的语言用 `#` / `//`)一行一行写,缩进同插入的那一行,空行写成只有记号的一行(docstring 的空行仍是空行)。写之前核对这块代码的那几行和问的时候一样,变了就不写(「这块代码在重写期间变了」);定界符认不出、编辑器没接受也不写。结束发 `rewriteEnd`(done / failed 带原因 / stopped),失败另弹一条错误提示。
- 按钮(`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` 由扩展起)。
- 输入框里说的话:`--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`、重定向写文件都被拒。
- 画图比读文件慢,超时 15 分钟(问 AI 仍是 5 分钟)。
- 过程:说的话(第一轮是那句技能命令,开始后扩展端报上来)、正文逐字长出来(`--include-partial-messages` 时 `StructuredOutput` 的输入逐段来,按写到一半的 JSON 读出已经写出来的正文)、正在做什么(「读取 a.py」)。块下面同问答区的样子(`AskThread` 换成重写的称呼):灰字历史、当前这一轮、输入框(提示「说说这段注释要怎么改…」,旁边 profile 下拉,能贴图),正在写时发送换成停止。同一块同时只重写一轮,全插件同时最多 3 段。
- 写回(扩展端,`WorkspaceEdit`,能撤销):换掉时套回原来那段的定界符和缩进(Python 三引号、`#` / `//` / `///` / `//!` 行注释、`/* */` 块注释,`refillComment`;几段隔着空行的照第一段的写法);新写时用这块说明的壳(docstring、`/** */`、`///`,或行注释 `#` / `//`)一行一行写,缩进同插入的那一行,空行写成只有记号的一行(docstring 的空行仍是空行)。写之前核对这块代码的那几行和开始时一样,变了就不写(「这块代码在重写期间变了」);没交回正文、定界符认不出、编辑器没接受也不写。
- 记录:每个工作区文件夹一份 `.spx/comment-rewrites.json`,和 `code-qa.json` 同一套写法(同一份 `qaMap.ts`,改名、删文件跟着走),条目 `{ "id", "question", "body"?, "error"?, "createdAt", "sessionId", "profile"?, "images"? }`:说的话(第一轮是技能命令)、交回的正文、没写进文件的原因(写进去了没有)。模型答完就记,写回失败也记下原因,好接着让它改;Claude Code 自己报错、被叫停的不记,文件不动。带的图片和问答的放在同一个 `.spx/code-qa/` 里,删一条跟着删。没展开时块下面一行「重写过 N 次」带灰字历史(没写进文件的那条下面写着原因),能收起、能删(「删除这条记录」)。
- 预览重新加载(标签页切走再切回来)后照样接上:进行中的、出错被叫停留着的那一轮记在扩展端(同问 AI,`QaAsks`),随 `qa` 消息的 `rewriteCurrent` 回来,展开它那块、进展接着长、停止能用;收起这块时留着的那一轮去掉。切走期间扩展端照样写完、写回、记下。
## 通用契约