From 43a0bf6badf74ec28d503e0230ab3347a60e56fe Mon Sep 17 00:00:00 2001 From: cruldra Date: Tue, 15 Sep 2026 00:28:58 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(vscode):=20?= =?UTF-8?q?=E7=BB=AD=E8=81=8A=E9=87=8C=E7=9A=84=E5=8E=9F=E5=9E=8B=E6=AE=B5?= =?UTF-8?q?=E8=90=BD=E6=90=AC=E8=BF=9B=E6=A8=A1=E6=9D=BF=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 那段正文原先拼在 prompts.ts 里,设置面板看不到也改不了。现在落到 prompts/brainstorm-continue-prototype.md,面板「提示词」组多一项「续聊·原型段落」, 占位符 {prototypeFile} / {issueNumber} / {prototypeSessionId};取值为空的整行会被 删掉,免得留下半句没有值的说明。 同时把措辞收紧:原型改动只能 ListAgents + SendMessage 给已开着的原型会话, 点名禁止派子代理 / fork 分身 / 自己动插件代码。 Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC --- .../prompts/brainstorm-continue-prototype.md | 8 +++++ vscode/src/cc/prompts.ts | 33 ++++++++++--------- vscode/src/panel/KanbanPanel.ts | 2 ++ vscode/src/panel/handlers/settings.ts | 4 +++ vscode/src/panel/messages.ts | 2 ++ vscode/src/settings/store.ts | 15 ++++++++- vscode/webview-ui/src/App.tsx | 1 + .../src/components/SettingsModal.tsx | 30 +++++++++++++++++ vscode/webview-ui/src/hooks/useIssues.ts | 4 +++ vscode/webview-ui/src/lib/messages.ts | 2 ++ 10 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 vscode/prompts/brainstorm-continue-prototype.md diff --git a/vscode/prompts/brainstorm-continue-prototype.md b/vscode/prompts/brainstorm-continue-prototype.md new file mode 100644 index 0000000..9e07291 --- /dev/null +++ b/vscode/prompts/brainstorm-continue-prototype.md @@ -0,0 +1,8 @@ +工单已有原型插件 {prototypeFile},先读它了解已经定下来的界面和交互,在此基础上只对齐还不明确的地方,不要从零重画。 + +**原型要改时只能交给本工单已经开着的原型会话**:先 ListAgents 找到它,再 SendMessage 说清要改哪块、为什么改、改完怎么算对,然后等它回报。 + +- 原型会话终端名:issue-{issueNumber}-原型 +- 原型会话 id:{prototypeSessionId}(它 transcript 的文件名就是这个 id) + +严禁用下面这些方式代替:Agent 工具派子代理 / fork 分身 / 后台代理,自己动插件代码,翻 git log 猜会话。ListAgents 里没有这个原型会话就**停下告诉我**去工单面板的「原型会话id」行点 ▶ 起一个,不要自作主张换别的做法。 diff --git a/vscode/src/cc/prompts.ts b/vscode/src/cc/prompts.ts index 6308bf0..c91eb0f 100644 --- a/vscode/src/cc/prompts.ts +++ b/vscode/src/cc/prompts.ts @@ -38,35 +38,36 @@ export function getBrainstormContinuePrompt( // id (e.g. LXF-12) so the cc agent references the real issue (readable via // `opencli yt issues show `), not a non-existent gitea number. const out = tpl.split('{issueNumber}').join(vars.issueRef ?? String(vars.issueNumber)) - return fillPrototypeRef(out, vars.prototypeFile, { + return fillPrototypeRef(ctx, out, vars.prototypeFile, { issueNumber: vars.issueRef ?? String(vars.issueNumber), prototypeSessionId: vars.prototypeSessionId, }) } /** - * `{prototypeRef}` 在模板里独占一段:有原型插件就填一段「先读它」的交代, - * 没有就连同前后空行一起抹掉——否则模板里会留下一个空洞段落。 + * `{prototypeRef}` 在模板里独占一段:工单挂了原型插件就填进设置里的「原型段落」 + * 模板,没挂就连同前后空行一起抹掉——否则模板里会留下一个空洞段落。 + * + * 段落模板里凡是某个占位符取值为空的整行直接删掉(如没有原型会话 id 时那条 + * 「原型会话 id:」),免得留下半句没有值的说明。 */ function fillPrototypeRef( + ctx: ExtensionContext, tpl: string, prototypeFile: string | undefined, - ctx: { issueNumber: string, prototypeSessionId: string | undefined }, + vars: { issueNumber: string, prototypeSessionId: string | undefined }, ): string { if (!prototypeFile) return tpl.replace(/\n*\{prototypeRef\}\n*/g, '\n\n').trimEnd() - const idHint = ctx.prototypeSessionId - ? `,会话 id ${ctx.prototypeSessionId}(它 transcript 的文件名就是这个 id)` - : '' - const lines = [ - `工单已有原型插件 ${prototypeFile},先读它了解已经定下来的界面和交互,在此基础上只对齐还不明确的地方,不要从零重画。`, - // 原型改动必须回到那个已经开着的原型会话去做:它手里有整套画图上下文。 - // 派子代理 / fork 分身都是另起一个没上下文的新实例,画出来的东西和已有 - // 原型对不上,还会和它抢着改同一份插件代码。 - `**原型要改时只能交给本工单已经开着的原型会话**(终端名 issue-${ctx.issueNumber}-原型${idHint}):先 ListAgents 找到它,再 SendMessage 说清要改哪块、为什么改、改完怎么算对,然后等它回报。`, - `严禁用下面这些方式代替:Agent 工具派子代理 / fork 分身 / 后台代理,自己动插件代码,翻 git log 猜会话。ListAgents 里没有这个原型会话就**停下告诉我**去工单面板的「原型会话id」行点 ▶ 起一个,不要自作主张换别的做法。`, - ] - return tpl.split('{prototypeRef}').join(lines.join('\n\n')) + + const values: Record = { + prototypeFile, + issueNumber: vars.issueNumber, + prototypeSessionId: vars.prototypeSessionId ?? '', + } + const para = getSettings(ctx).prototypeRefPrompt.split('\n').filter(line => !Object.entries(values).some(([k, v]) => v === '' && line.includes(`{${k}}`))).join('\n').replace(/\{(prototypeFile|issueNumber|prototypeSessionId)\}/g, (_m, k: string) => values[k]).trim() + + return tpl.split('{prototypeRef}').join(para) } export function getImplementPlanPrompt(ctx: ExtensionContext, vars: { planFile: string, issueNumber: number, issueRef?: string }): string { diff --git a/vscode/src/panel/KanbanPanel.ts b/vscode/src/panel/KanbanPanel.ts index 1bd1e7e..21e3574 100644 --- a/vscode/src/panel/KanbanPanel.ts +++ b/vscode/src/panel/KanbanPanel.ts @@ -1008,6 +1008,7 @@ export class KanbanWebviewPanel { webhookSecret: s.webhookSecret, brainstormPrompt: s.brainstormPrompt, brainstormContinuePrompt: s.brainstormContinuePrompt, + prototypeRefPrompt: s.prototypeRefPrompt, implementPlanPrompt: s.implementPlanPrompt, autoReview: s.autoReview, reviewPrompt: s.reviewPrompt, @@ -1099,6 +1100,7 @@ export class KanbanWebviewPanel { webhookSecret: s.webhookSecret, brainstormPrompt: s.brainstormPrompt, brainstormContinuePrompt: s.brainstormContinuePrompt, + prototypeRefPrompt: s.prototypeRefPrompt, implementPlanPrompt: s.implementPlanPrompt, autoReview: s.autoReview, reviewPrompt: s.reviewPrompt, diff --git a/vscode/src/panel/handlers/settings.ts b/vscode/src/panel/handlers/settings.ts index 3622293..06b3d52 100644 --- a/vscode/src/panel/handlers/settings.ts +++ b/vscode/src/panel/handlers/settings.ts @@ -24,6 +24,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { webhookSecret: string brainstormPrompt: string brainstormContinuePrompt: string + prototypeRefPrompt: string implementPlanPrompt: string autoReview: boolean reviewPrompt: string @@ -114,6 +115,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { webhookSecret: payload.webhookSecret, brainstormPrompt: payload.brainstormPrompt || prev.brainstormPrompt, brainstormContinuePrompt: payload.brainstormContinuePrompt || prev.brainstormContinuePrompt, + prototypeRefPrompt: payload.prototypeRefPrompt || prev.prototypeRefPrompt, implementPlanPrompt: payload.implementPlanPrompt || prev.implementPlanPrompt, autoReview: payload.autoReview, reviewPrompt: payload.reviewPrompt || prev.reviewPrompt, @@ -149,6 +151,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { webhookSecret: payload.webhookSecret.trim(), brainstormPrompt: payload.brainstormPrompt, brainstormContinuePrompt: payload.brainstormContinuePrompt, + prototypeRefPrompt: payload.prototypeRefPrompt, implementPlanPrompt: payload.implementPlanPrompt, autoReview: payload.autoReview, reviewPrompt: payload.reviewPrompt, @@ -261,6 +264,7 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom webhookSecret: s.webhookSecret, brainstormPrompt: s.brainstormPrompt, brainstormContinuePrompt: s.brainstormContinuePrompt, + prototypeRefPrompt: s.prototypeRefPrompt, implementPlanPrompt: s.implementPlanPrompt, autoReview: s.autoReview, reviewPrompt: s.reviewPrompt, diff --git a/vscode/src/panel/messages.ts b/vscode/src/panel/messages.ts index c18b477..ea65bd9 100644 --- a/vscode/src/panel/messages.ts +++ b/vscode/src/panel/messages.ts @@ -73,6 +73,7 @@ export type ExtensionToWebview webhookSecret: string brainstormPrompt: string brainstormContinuePrompt: string + prototypeRefPrompt: string implementPlanPrompt: string autoReview: boolean reviewPrompt: string @@ -158,6 +159,7 @@ export type WebviewToExtension webhookSecret: string brainstormPrompt: string brainstormContinuePrompt: string + prototypeRefPrompt: string implementPlanPrompt: string autoReview: boolean reviewPrompt: string diff --git a/vscode/src/settings/store.ts b/vscode/src/settings/store.ts index 7580c61..3096db4 100644 --- a/vscode/src/settings/store.ts +++ b/vscode/src/settings/store.ts @@ -36,13 +36,15 @@ const FALLBACK_IMPLEMENT_PLAN_PROMPT = `/ecc:tdd @{planFile} 按计划实施, const FALLBACK_TEST_PROMPT = `/pr-acceptance-testing 验收 {issueNumber} 号工单({prRef})。验收目标以工单正文为准,不要再问验收哪个。` +const FALLBACK_PROTOTYPE_REF_PROMPT = `工单已有原型插件 {prototypeFile},先读它。原型要改就 ListAgents 找到 issue-{issueNumber}-原型 会话(id {prototypeSessionId})并 SendMessage 给它,不要派子代理、不要自己动插件代码。` + const FALLBACK_REVIEW_PROMPT = `用 tea 拿到 #{prNumber} PR,diff 交给 ecc:code-reviewer 子代理审查。审查意见用 spx pr review-comment 发评论,body 第一行写 。` const FALLBACK_PROTOTYPE_PROMPT = `/ecc:figma-plugin-prototyping 给 {issueNumber} 号工单画原型:先读工单正文锁定要画的界面和状态,再写本地 Figma 插件把画板和点击流做出来。画完汇报插件路径和怎么导入,不要改业务代码。 画完用 \`opencli spx issue marker --issue {issueNumber} --type prototype --value \` 把原型插件挂到工单上,再汇报。` -type PromptName = 'brainstorm' | 'brainstorm-continue' | 'implement-plan' | 'review' | 'test' | 'prototype' +type PromptName = 'brainstorm' | 'brainstorm-continue' | 'brainstorm-continue-prototype' | 'implement-plan' | 'review' | 'test' | 'prototype' /** * Read a default prompt template from `${extensionPath}/prompts/${name}.md`. @@ -62,6 +64,7 @@ export function readDefaultPrompt(extensionPath: string, name: PromptName): stri switch (name) { case 'brainstorm': return FALLBACK_BRAINSTORM_PROMPT case 'brainstorm-continue': return FALLBACK_BRAINSTORM_CONTINUE_PROMPT + case 'brainstorm-continue-prototype': return FALLBACK_PROTOTYPE_REF_PROMPT case 'implement-plan': return FALLBACK_IMPLEMENT_PLAN_PROMPT case 'review': return FALLBACK_REVIEW_PROMPT case 'test': return FALLBACK_TEST_PROMPT @@ -94,6 +97,11 @@ export interface Settings { * 无则连同所在段落一起删掉)。 */ brainstormContinuePrompt: string + /** + * 续聊提示词里 `{prototypeRef}` 那段的正文;只在工单挂了原型插件时填进去。 + * 占位符 {prototypeFile} / {issueNumber} / {prototypeSessionId}。 + */ + prototypeRefPrompt: string /** Prompt template for the implement-plan flow. `{planFile}` placeholder. */ implementPlanPrompt: string /** Whether to automatically run `codex exec review` when a PR opens. */ @@ -282,6 +290,7 @@ function defaults(ctx: ExtensionContext): Settings { webhookSecret: '', brainstormPrompt: readDefaultPrompt(ctx.extensionPath, 'brainstorm'), brainstormContinuePrompt: readDefaultPrompt(ctx.extensionPath, 'brainstorm-continue'), + prototypeRefPrompt: readDefaultPrompt(ctx.extensionPath, 'brainstorm-continue-prototype'), implementPlanPrompt: readDefaultPrompt(ctx.extensionPath, 'implement-plan'), autoReview: true, reviewPrompt: readDefaultPrompt(ctx.extensionPath, 'review'), @@ -334,6 +343,9 @@ export function getSettings(ctx: ExtensionContext): Settings { const brainstormContinuePrompt = typeof stored.brainstormContinuePrompt === 'string' && stored.brainstormContinuePrompt.length > 0 ? stored.brainstormContinuePrompt : base.brainstormContinuePrompt + const prototypeRefPrompt = typeof stored.prototypeRefPrompt === 'string' && stored.prototypeRefPrompt.length > 0 + ? stored.prototypeRefPrompt + : base.prototypeRefPrompt const implementPlanPrompt = typeof stored.implementPlanPrompt === 'string' && stored.implementPlanPrompt.length > 0 ? stored.implementPlanPrompt : base.implementPlanPrompt @@ -452,6 +464,7 @@ export function getSettings(ctx: ExtensionContext): Settings { webhookSecret, brainstormPrompt, brainstormContinuePrompt, + prototypeRefPrompt, implementPlanPrompt, autoReview, reviewPrompt, diff --git a/vscode/webview-ui/src/App.tsx b/vscode/webview-ui/src/App.tsx index 31446ff..0e91937 100644 --- a/vscode/webview-ui/src/App.tsx +++ b/vscode/webview-ui/src/App.tsx @@ -543,6 +543,7 @@ export function App() { initialWebhookSecret={settings?.webhookSecret ?? ''} initialBrainstormPrompt={settings?.brainstormPrompt ?? ''} initialBrainstormContinuePrompt={settings?.brainstormContinuePrompt ?? ''} + initialPrototypeRefPrompt={settings?.prototypeRefPrompt ?? ''} initialImplementPlanPrompt={settings?.implementPlanPrompt ?? ''} initialAutoReview={settings?.autoReview ?? true} initialReviewPrompt={settings?.reviewPrompt ?? ''} diff --git a/vscode/webview-ui/src/components/SettingsModal.tsx b/vscode/webview-ui/src/components/SettingsModal.tsx index 66815aa..6c58c02 100644 --- a/vscode/webview-ui/src/components/SettingsModal.tsx +++ b/vscode/webview-ui/src/components/SettingsModal.tsx @@ -22,6 +22,7 @@ interface SubmitValues { webhookSecret: string brainstormPrompt: string brainstormContinuePrompt: string + prototypeRefPrompt: string implementPlanPrompt: string autoReview: boolean reviewPrompt: string @@ -78,6 +79,7 @@ export interface SettingsModalProps { initialWebhookSecret: string initialBrainstormPrompt: string initialBrainstormContinuePrompt: string + initialPrototypeRefPrompt: string initialImplementPlanPrompt: string initialAutoReview: boolean initialReviewPrompt: string @@ -170,6 +172,7 @@ export function SettingsModal({ initialWebhookSecret, initialBrainstormPrompt, initialBrainstormContinuePrompt, + initialPrototypeRefPrompt, initialImplementPlanPrompt, initialAutoReview, initialReviewPrompt, @@ -214,6 +217,7 @@ export function SettingsModal({ const [webhookSecret, setWebhookSecret] = useState(initialWebhookSecret) const [brainstormPrompt, setBrainstormPrompt] = useState(initialBrainstormPrompt) const [brainstormContinuePrompt, setBrainstormContinuePrompt] = useState(initialBrainstormContinuePrompt) + const [prototypeRefPrompt, setPrototypeRefPrompt] = useState(initialPrototypeRefPrompt) const [implementPlanPrompt, setImplementPlanPrompt] = useState(initialImplementPlanPrompt) const [autoReview, setAutoReview] = useState(initialAutoReview) const [reviewPrompt, setReviewPrompt] = useState(initialReviewPrompt) @@ -260,6 +264,7 @@ export function SettingsModal({ setWebhookSecret(initialWebhookSecret) setBrainstormPrompt(initialBrainstormPrompt) setBrainstormContinuePrompt(initialBrainstormContinuePrompt) + setPrototypeRefPrompt(initialPrototypeRefPrompt) setImplementPlanPrompt(initialImplementPlanPrompt) setAutoReview(initialAutoReview) setReviewPrompt(initialReviewPrompt) @@ -354,6 +359,7 @@ export function SettingsModal({ webhookSecret: webhookSecret.trim(), brainstormPrompt, brainstormContinuePrompt, + prototypeRefPrompt, implementPlanPrompt, autoReview, reviewPrompt, @@ -1010,6 +1016,30 @@ export function SettingsModal({ /> + + 工单挂了原型插件时,这段会填进续聊提示词的 + {'{prototypeRef}'} + 处。可用占位符: + {'{prototypeFile}'} + 、 + {'{issueNumber}'} + 、 + {'{prototypeSessionId}'} + (取值为空时,含该占位符的整行会被删掉) + + )} + > +