✨ feat(vscode): 新增第五种工单会话「原型会话」

照测试会话那条链路补一份 prototype:

- 消息与路由:session/start-prototype、session/resume-prototype、
  issue/update-prototype-profile-path,都进只读门禁
- 会话:handleStartPrototypeSession / handleResumePrototypeSession,
  终端名 issue-N-原型;cwd 有 worktree 用 worktree、没有退主仓库根
  (原 resolveTestSessionCwd 改名 resolveWorktreeOrRootCwd 两种会话共用)
- 不要求有 PR,任何阶段都能起;前置工单锁仍然拦
- 提示词:新增 prompts/prototype.md,设置里可改,占位符 {issueNumber}
- profile:工单级 prototypeProfilePath > 设置里的原型默认 profile >
  内置默认,与其它会话互不回退
- 状态字段 prototypeSessionId / prototypeProfilePath / prototypeTabOpen
  贯通 gitea、youtrack、本机 state 与移交包
- 详情面板加「原型会话id」「原型配置文件」两行,设置面板加对应两项

Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
This commit is contained in:
cruldra committed 2026-09-14 10:20:30 +08:00
1 parent 3fff796a61
commit b62e58d739
27 files changed
+760 -50

No files matched your search

+19 -2
View File
@@ -34,7 +34,9 @@ const FALLBACK_TEST_PROMPT = `/pr-acceptance-testing 验收 {issueNumber} 号工
const FALLBACK_REVIEW_PROMPT = `用 tea 拿到 #{prNumber} PR,diff 交给 ecc:code-reviewer 子代理审查。审查意见用 spx pr review-comment 发评论,body 第一行写 <!-- spx:review=1 -->。`
type PromptName = 'brainstorm' | 'brainstorm-continue' | 'implement-plan' | 'review' | 'test'
const FALLBACK_PROTOTYPE_PROMPT = `/ecc:figma-plugin-prototyping 给 {issueNumber} 号工单画原型:先读工单正文锁定要画的界面和状态,再写本地 Figma 插件把画板和点击流做出来。画完汇报插件路径和怎么导入,不要改业务代码。`
type PromptName = 'brainstorm' | 'brainstorm-continue' | 'implement-plan' | 'review' | 'test' | 'prototype'
/**
* Read a default prompt template from `${extensionPath}/prompts/${name}.md`.
@@ -57,6 +59,7 @@ export function readDefaultPrompt(extensionPath: string, name: PromptName): stri
case 'implement-plan': return FALLBACK_IMPLEMENT_PLAN_PROMPT
case 'review': return FALLBACK_REVIEW_PROMPT
case 'test': return FALLBACK_TEST_PROMPT
case 'prototype': return FALLBACK_PROTOTYPE_PROMPT
}
}
}
@@ -92,6 +95,8 @@ export interface Settings {
reviewPrompt: string
/** 测试会话首个提示词模板;占位符 {issueNumber} / {prRef}。 */
testPrompt: string
/** 原型会话首个提示词模板;占位符 {issueNumber}。 */
prototypePrompt: string
/** Day-to-day development branch (e.g. `main`). Jenkins doesn't watch it. */
devBranch: string
/**
@@ -194,6 +199,8 @@ export interface Settings {
defaultImplementProfilePath: string
/** 测试会话的默认 Claude profile;工单级「测试配置文件」为空时用它。 */
defaultTestProfilePath: string
/** 原型会话的默认 Claude profile;工单级「原型配置文件」为空时用它。 */
defaultPrototypeProfilePath: string
/**
* Claude settings profile path for the conflict-resolution cc session
* (drag to 完成 when PR merge conflicts). Empty = DEFAULT_PROFILE_PATH.
@@ -272,6 +279,7 @@ function defaults(ctx: ExtensionContext): Settings {
autoReview: true,
reviewPrompt: readDefaultPrompt(ctx.extensionPath, 'review'),
testPrompt: readDefaultPrompt(ctx.extensionPath, 'test'),
prototypePrompt: readDefaultPrompt(ctx.extensionPath, 'prototype'),
devBranch: 'main',
releaseBranchPrefix: 'release/',
jenkinsJob: '',
@@ -290,6 +298,7 @@ function defaults(ctx: ExtensionContext): Settings {
defaultBrainstormProfilePath: '',
defaultImplementProfilePath: '',
defaultTestProfilePath: '',
defaultPrototypeProfilePath: '',
conflictResolutionProfilePath: '',
commitProfilePath: '',
prDiffSummaryProfilePath: '',
@@ -328,6 +337,9 @@ export function getSettings(ctx: ExtensionContext): Settings {
const testPrompt = typeof stored.testPrompt === 'string' && stored.testPrompt.length > 0
? stored.testPrompt
: base.testPrompt
const prototypePrompt = typeof stored.prototypePrompt === 'string' && stored.prototypePrompt.length > 0
? stored.prototypePrompt
: base.prototypePrompt
// devBranch always has a usable value — fall back to 'main' if missing/blank
// so we never end up trying to fetch the empty string.
const devBranch = typeof stored.devBranch === 'string' && stored.devBranch.length > 0
@@ -390,7 +402,7 @@ export function getSettings(ctx: ExtensionContext): Settings {
const youtrackCloseCommand = typeof stored.youtrackCloseCommand === 'string'
? stored.youtrackCloseCommand
: base.youtrackCloseCommand
// 三个会话默认 profile:'' 有意义(= 落到 DEFAULT_PROFILE_PATH),不强制回退。
// 四个会话默认 profile:'' 有意义(= 落到 DEFAULT_PROFILE_PATH),不强制回退。
const defaultBrainstormProfilePath = typeof stored.defaultBrainstormProfilePath === 'string'
? stored.defaultBrainstormProfilePath
: base.defaultBrainstormProfilePath
@@ -400,6 +412,9 @@ export function getSettings(ctx: ExtensionContext): Settings {
const defaultTestProfilePath = typeof stored.defaultTestProfilePath === 'string'
? stored.defaultTestProfilePath
: base.defaultTestProfilePath
const defaultPrototypeProfilePath = typeof stored.defaultPrototypeProfilePath === 'string'
? stored.defaultPrototypeProfilePath
: base.defaultPrototypeProfilePath
// conflict-resolution profile: '' is meaningful (= DEFAULT_PROFILE_PATH at
// launch time), so don't coerce. Non-string legacy values collapse to ''.
const conflictResolutionProfilePath = typeof stored.conflictResolutionProfilePath === 'string'
@@ -434,6 +449,7 @@ export function getSettings(ctx: ExtensionContext): Settings {
autoReview,
reviewPrompt,
testPrompt,
prototypePrompt,
devBranch,
releaseBranchPrefix,
jenkinsJob,
@@ -452,6 +468,7 @@ export function getSettings(ctx: ExtensionContext): Settings {
defaultBrainstormProfilePath,
defaultImplementProfilePath,
defaultTestProfilePath,
defaultPrototypeProfilePath,
conflictResolutionProfilePath,
commitProfilePath,
prDiffSummaryProfilePath,