✨ feat(vscode): 七份提示词统一走 Handlebars,头脑风暴三档合一
- 模板引擎换成 Handlebars(noEscape,提示词是纯文本不能被转义),单花括号写法彻底退场
- brainstorm-continue / brainstorm-continue-prototype 两份并进 brainstorm.md,
用 {{#if issueNumber}} 分新建 / 续聊,续聊里用 {{#if prototypeFile}} 嵌原型段落,
设置面板对应的两个 textarea 一并删掉
- 模板上下文 = 工单 state JSON 全量字段 + issueNumber / userRequest / nonce /
planFile / prNumber / prRef,以后模板想用新字段不用改扩展代码
- 各会话入口共用一条 state 读取路径,读失败只记 warn 按空状态继续,不拦开会话
- 升级后首次启动把存量自定义提示词迁到双花括号并合并成一份,改写前把
settings.json 备份成 settings.json.bak-<时间戳>,迁移动作全写日志
- 装 handlebars 时给 vsce 传递依赖的老 semver 加 trustPolicyExclude 豁免,
否则 no-downgrade 策略卡住整个装包
Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
This commit is contained in:
1 parent
efeac8c978
commit
7bac5c8b03
28 files changed
+853
-424
No files matched your search
@@ -12,7 +12,7 @@ import { getToken } from '../../auth/secrets'
|
||||
import { buildCcCommand } from '../../cc/ccCommand'
|
||||
import { resolveCommitProfileForRun } from '../../cc/commitProfile'
|
||||
import { resolveProfilePath } from '../../cc/profiles'
|
||||
import { getBrainstormPrompt } from '../../cc/prompts'
|
||||
import { renderBrainstormPrompt } from '../../cc/prompts'
|
||||
import { claudeProjectsRoot, installClaudeSession } from '../../cc/sessionBundle'
|
||||
import { projectsDirFor, watchForNewSession } from '../../cc/sessionWatcher'
|
||||
import { spawnClaude } from '../../cc/spawnClaude'
|
||||
@@ -1414,7 +1414,8 @@ export async function handleIssueCreate(
|
||||
}
|
||||
|
||||
const color = pickRandomIssueColor()
|
||||
const prompt = getBrainstormPrompt(panel.context, {
|
||||
// 工单还不存在,模板上下文里没有 issueNumber / state —— 模板据此走新建工单分支。
|
||||
const prompt = renderBrainstormPrompt(panel.context, {
|
||||
userRequest: trimmed,
|
||||
nonce,
|
||||
imagePaths: imagePaths.length > 0 ? imagePaths : undefined,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { buildCcCommand } from '../../cc/ccCommand'
|
||||
import { buildCodexCommandString } from '../../cc/codexCommand'
|
||||
import { watchForNewCodexSession } from '../../cc/codexSessionWatcher'
|
||||
import { resolveProfilePath } from '../../cc/profiles'
|
||||
import { getBrainstormContinuePrompt, getImplementPlanPrompt, getPrototypePrompt, getReviewPrompt, getTestPrompt } from '../../cc/prompts'
|
||||
import { renderBrainstormPrompt, renderImplementPlanPrompt, renderPrototypePrompt, renderReviewPrompt, renderTestPrompt } from '../../cc/prompts'
|
||||
import { projectsDirFor, watchForNewSession } from '../../cc/sessionWatcher'
|
||||
import { detectRepo } from '../../git/remote'
|
||||
import { ensureWorktree, resolveWorktreePath } from '../../git/worktree'
|
||||
@@ -29,6 +29,34 @@ import { cleanupFeatureBranch } from './branchCleanup'
|
||||
import { planResumeCwd, resumeTerminalName } from './resumeTarget'
|
||||
import { submitToSessionTerminal } from './terminals'
|
||||
|
||||
/**
|
||||
* 会话入口统一的 state 读取:读失败只记 warn,按空状态继续——提示词模板里的
|
||||
* 条件块会自然走 else 分支,没必要为一次网络抖动拦住开会话。
|
||||
*/
|
||||
async function readStateForPrompt(
|
||||
panel: KanbanWebviewPanel,
|
||||
issueNumber: number,
|
||||
source: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
return await panel.readIssueState(issueNumber)
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source,
|
||||
message: `读取 #${issueNumber} state JSON 失败,按空状态继续`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/** state 里「空串 = 未设置」,取值时统一收敛成 undefined。 */
|
||||
function nonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === 'string' && value.length > 0 ? value : undefined
|
||||
}
|
||||
|
||||
export async function handleResumeSession(panel: KanbanWebviewPanel, sessionId: string, sessionKind: ResumeKind, profilePath?: string, relCwd?: string, issueNumber?: number): Promise<void> {
|
||||
// In-flight 锁:防 impl-tab-pre-create 钩子 await(pgrep pycharm 最多 15s)
|
||||
// 期间用户重复点同一会话 id 触发重复 createTerminal。issueNumber 为空时
|
||||
@@ -838,7 +866,13 @@ export async function handleImplement(
|
||||
// Build the prompt. The extension already created the worktree, so cc
|
||||
// just implements the plan. `$pr_no` is left as-is for cc to fill in
|
||||
// via tool calls.
|
||||
const prompt = getImplementPlanPrompt(panel.context, { planFile, issueNumber, issueRef: panel.refFor(issueNumber).externalId })
|
||||
// 上面刚写进 state 的 branch / worktreePath 也带进模板上下文,省一次回读。
|
||||
const state = await readStateForPrompt(panel, issueNumber, 'implement')
|
||||
const prompt = renderImplementPlanPrompt(panel.context, {
|
||||
state: { ...state, branch, worktreePath },
|
||||
issueNumber: panel.issueRefLabel(issueNumber),
|
||||
planFile,
|
||||
})
|
||||
if (prompt.includes('\'')) {
|
||||
void window.showErrorMessage('实施失败:prompt 含单引号,拒绝执行')
|
||||
return
|
||||
@@ -944,38 +978,15 @@ export async function handleContinueBrainstorm(panel: KanbanWebviewPanel, issueN
|
||||
return
|
||||
}
|
||||
|
||||
// 头脑风暴只用 brainstormProfilePath;空 = DEFAULT,不回退实施 profile。
|
||||
let brainstormProfilePath: string | undefined
|
||||
let sessionId: string | undefined
|
||||
// 工单已有原型插件时,续聊提示词里让 cc 先读它,别把已经画定的界面又讨论一遍;
|
||||
// 会话 id 一并带上,讨论里要改原型时直接 SendMessage 给原型会话。
|
||||
let prototypeFile: string | undefined
|
||||
let prototypeSessionId: string | undefined
|
||||
try {
|
||||
const stateObj = await panel.readIssueState(issueNumber)
|
||||
if (stateObj && typeof stateObj.brainstormProfilePath === 'string' && stateObj.brainstormProfilePath.length > 0)
|
||||
brainstormProfilePath = stateObj.brainstormProfilePath
|
||||
if (stateObj && typeof stateObj.sessionId === 'string' && stateObj.sessionId.length > 0)
|
||||
sessionId = stateObj.sessionId
|
||||
if (stateObj && typeof stateObj.prototypeFile === 'string' && stateObj.prototypeFile.length > 0)
|
||||
prototypeFile = stateObj.prototypeFile
|
||||
if (stateObj && typeof stateObj.prototypeSessionId === 'string' && stateObj.prototypeSessionId.length > 0)
|
||||
prototypeSessionId = stateObj.prototypeSessionId
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source: 'brainstorm',
|
||||
message: `读取 #${issueNumber} state JSON 失败,使用默认 profile`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
// state 读一次就够:profile / 会话 id 和提示词上下文(原型插件、会话 id 等)
|
||||
// 都从这份里取。读不到就按空状态走,提示词里的条件块自然走 else 分支。
|
||||
const state = await readStateForPrompt(panel, issueNumber, 'brainstorm')
|
||||
const brainstormProfilePath = nonEmptyString(state.brainstormProfilePath)
|
||||
const sessionId = nonEmptyString(state.sessionId)
|
||||
|
||||
const prompt = getBrainstormContinuePrompt(panel.context, {
|
||||
issueNumber,
|
||||
issueRef: panel.refFor(issueNumber).externalId,
|
||||
prototypeFile,
|
||||
prototypeSessionId,
|
||||
const prompt = renderBrainstormPrompt(panel.context, {
|
||||
state,
|
||||
issueNumber: panel.issueRefLabel(issueNumber),
|
||||
})
|
||||
const terminalName = `issue-${issueNumber}-规划`
|
||||
// 新建工单流程的 tab 叫 issue-new-{nonce}-规划,只能靠 newIssueTerminals 反查。
|
||||
@@ -1142,31 +1153,11 @@ export async function handleStartTestSession(panel: KanbanWebviewPanel, issueNum
|
||||
// 从 state JSON 读 pr / testProfile / worktree。pr 仅在主 worktree(已合并)
|
||||
// 测试时必需——那条路径要靠 tea 了解合并进来的改动;worktree 内测试代码
|
||||
// 本就在工作目录,不依赖 PR。
|
||||
let pr: string | undefined
|
||||
let testProfilePath: string | undefined
|
||||
let worktreePathRel: string | undefined
|
||||
let branch: string | undefined
|
||||
try {
|
||||
const stateObj = await panel.readIssueState(issueNumber)
|
||||
if (stateObj) {
|
||||
if (typeof stateObj.pr === 'string' && stateObj.pr.length > 0)
|
||||
pr = stateObj.pr
|
||||
if (typeof stateObj.testProfilePath === 'string' && stateObj.testProfilePath.length > 0)
|
||||
testProfilePath = stateObj.testProfilePath
|
||||
if (typeof stateObj.worktreePath === 'string' && stateObj.worktreePath.length > 0)
|
||||
worktreePathRel = stateObj.worktreePath
|
||||
if (typeof stateObj.branch === 'string' && stateObj.branch.length > 0)
|
||||
branch = stateObj.branch
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source: 'panel',
|
||||
message: `读取 #${issueNumber} state JSON 失败,无法启动测试会话`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
const state = await readStateForPrompt(panel, issueNumber, 'panel')
|
||||
const pr = nonEmptyString(state.pr)
|
||||
const testProfilePath = nonEmptyString(state.testProfilePath)
|
||||
const worktreePathRel = nonEmptyString(state.worktreePath)
|
||||
const branch = nonEmptyString(state.branch)
|
||||
|
||||
// worktree 还在(PR 未合并/未清理)就进 worktree 测,已清理则回退主 worktree(main)。
|
||||
const effectiveCwd = await resolveWorktreeOrRootCwd(workspaceRoot, worktreePathRel)
|
||||
@@ -1191,8 +1182,9 @@ export async function handleStartTestSession(panel: KanbanWebviewPanel, issueNum
|
||||
// worktree 里:代码就在当前工作目录,不用 tea 拉取,直接测;
|
||||
// 主 worktree:PR 已合并,先用 tea 熟悉改动再测。验收目标直接写进提示词,
|
||||
// 省得验收技能反问要测哪个。
|
||||
const prompt = getTestPrompt(panel.context, {
|
||||
issueNumber,
|
||||
const prompt = renderTestPrompt(panel.context, {
|
||||
state,
|
||||
issueNumber: panel.issueRefLabel(issueNumber),
|
||||
prRef: inWorktree
|
||||
? `worktree 分支 ${branch || effectiveCwd}`
|
||||
: `PR #${pr}`,
|
||||
@@ -1306,23 +1298,9 @@ export async function handleStartReviewSession(panel: KanbanWebviewPanel, issueN
|
||||
return
|
||||
}
|
||||
|
||||
let pr: string | undefined
|
||||
let worktreePath = ''
|
||||
try {
|
||||
const stateObj = await panel.readIssueState(issueNumber)
|
||||
if (typeof stateObj.pr === 'string' && stateObj.pr.length > 0)
|
||||
pr = stateObj.pr.replace(/\(已合并\)\s*$/, '')
|
||||
if (typeof stateObj.worktreePath === 'string')
|
||||
worktreePath = stateObj.worktreePath
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source: 'review',
|
||||
message: `读取 #${issueNumber} state JSON 失败,无法启动审查会话`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
const state = await readStateForPrompt(panel, issueNumber, 'review')
|
||||
const pr = nonEmptyString(state.pr)?.replace(/\(已合并\)\s*$/, '')
|
||||
const worktreePath = typeof state.worktreePath === 'string' ? state.worktreePath : ''
|
||||
|
||||
if (!pr) {
|
||||
void window.showWarningMessage('该工单还没有 PR,无法启动审查会话')
|
||||
@@ -1332,7 +1310,11 @@ export async function handleStartReviewSession(panel: KanbanWebviewPanel, issueN
|
||||
const ok = await triggerAutoReviewTab(panel, {
|
||||
issueNumber,
|
||||
prNumber: pr,
|
||||
prompt: getReviewPrompt(panel.context, { prNumber: pr }),
|
||||
prompt: renderReviewPrompt(panel.context, {
|
||||
state,
|
||||
issueNumber: panel.issueRefLabel(issueNumber),
|
||||
prNumber: pr,
|
||||
}),
|
||||
worktreePath,
|
||||
workspaceRoot,
|
||||
})
|
||||
@@ -1509,28 +1491,10 @@ export async function handleStartPrototypeSession(panel: KanbanWebviewPanel, iss
|
||||
return
|
||||
}
|
||||
|
||||
let prototypeProfilePath: string | undefined
|
||||
let worktreePathRel: string | undefined
|
||||
let branch: string | undefined
|
||||
try {
|
||||
const stateObj = await panel.readIssueState(issueNumber)
|
||||
if (stateObj) {
|
||||
if (typeof stateObj.prototypeProfilePath === 'string' && stateObj.prototypeProfilePath.length > 0)
|
||||
prototypeProfilePath = stateObj.prototypeProfilePath
|
||||
if (typeof stateObj.worktreePath === 'string' && stateObj.worktreePath.length > 0)
|
||||
worktreePathRel = stateObj.worktreePath
|
||||
if (typeof stateObj.branch === 'string' && stateObj.branch.length > 0)
|
||||
branch = stateObj.branch
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({
|
||||
level: 'warn',
|
||||
source: 'panel',
|
||||
message: `读取 #${issueNumber} state JSON 失败,无法启动原型会话`,
|
||||
details: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
const state = await readStateForPrompt(panel, issueNumber, 'panel')
|
||||
const prototypeProfilePath = nonEmptyString(state.prototypeProfilePath)
|
||||
const worktreePathRel = nonEmptyString(state.worktreePath)
|
||||
const branch = nonEmptyString(state.branch)
|
||||
|
||||
// worktree 还在就进 worktree 画,已清理则退回主 worktree。
|
||||
const effectiveCwd = await resolveWorktreeOrRootCwd(workspaceRoot, worktreePathRel)
|
||||
@@ -1544,7 +1508,10 @@ export async function handleStartPrototypeSession(panel: KanbanWebviewPanel, iss
|
||||
return
|
||||
}
|
||||
|
||||
const prompt = getPrototypePrompt(panel.context, { issueNumber })
|
||||
const prompt = renderPrototypePrompt(panel.context, {
|
||||
state,
|
||||
issueNumber: panel.issueRefLabel(issueNumber),
|
||||
})
|
||||
if (prompt.includes('\'')) {
|
||||
void window.showErrorMessage('启动原型失败:prompt 含单引号,拒绝执行')
|
||||
return
|
||||
|
||||
@@ -23,8 +23,6 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
||||
webhookPort: number
|
||||
webhookSecret: string
|
||||
brainstormPrompt: string
|
||||
brainstormContinuePrompt: string
|
||||
prototypeRefPrompt: string
|
||||
implementPlanPrompt: string
|
||||
autoReview: boolean
|
||||
reviewPrompt: string
|
||||
@@ -114,8 +112,6 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
||||
webhookPort: payload.webhookPort,
|
||||
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,
|
||||
@@ -150,8 +146,6 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
||||
webhookPort: payload.webhookPort,
|
||||
webhookSecret: payload.webhookSecret.trim(),
|
||||
brainstormPrompt: payload.brainstormPrompt,
|
||||
brainstormContinuePrompt: payload.brainstormContinuePrompt,
|
||||
prototypeRefPrompt: payload.prototypeRefPrompt,
|
||||
implementPlanPrompt: payload.implementPlanPrompt,
|
||||
autoReview: payload.autoReview,
|
||||
reviewPrompt: payload.reviewPrompt,
|
||||
@@ -263,8 +257,6 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom
|
||||
webhookPort: s.webhookPort,
|
||||
webhookSecret: s.webhookSecret,
|
||||
brainstormPrompt: s.brainstormPrompt,
|
||||
brainstormContinuePrompt: s.brainstormContinuePrompt,
|
||||
prototypeRefPrompt: s.prototypeRefPrompt,
|
||||
implementPlanPrompt: s.implementPlanPrompt,
|
||||
autoReview: s.autoReview,
|
||||
reviewPrompt: s.reviewPrompt,
|
||||
|
||||
Reference in new issue
Block a user