Files
superwork/vscode/src/cc/prompts.ts
T
cruldra 7bac5c8b03 ✨ 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
2026-09-15 00:44:28 +08:00

87 lines
3.3 KiB
TypeScript

/**
* 五个会话入口(头脑风暴 / 实施 / 审查 / 测试 / 原型)的首个提示词。
* 模板正文在 `prompts/*.md`,用户改过的副本落在设置里,这里只负责
* 「读设置 + 灌上下文 + 渲染」。
*
* 上下文一律是「工单 state JSON 全量字段 + 调用方补的几个变量」,所以模板里
* 想用 `{{branch}}`、`{{specFile}}` 这类字段不必改扩展代码。
*/
import type { ExtensionContext } from 'vscode'
import type { PromptContext } from './promptTemplate'
import { logger } from '../logging/logger'
import { getSettings } from '../settings/store'
import { buildPromptContext, renderPromptTemplate } from './promptTemplate'
/** 调用方补进模板上下文的变量;state 之外的都放这儿。 */
export interface PromptVars {
/** `readIssueState` 的结果,读失败时给 undefined(模板里条件块自然走 else)。 */
state?: PromptContext
/** 工单号;youtrack 卡片传可读 id(LXF-12),别传看板的合成号。 */
issueNumber?: string
userRequest?: string
nonce?: string
planFile?: string
prNumber?: string
prRef?: string
}
/**
* 模板语法错误不该把会话卡死:记 error 日志后原样把模板正文当提示词发出去,
* 用户在终端里一眼能看到没被替换的 `{{...}}`,知道是自己模板写坏了。
*/
function render(name: string, tpl: string, vars: PromptVars): string {
const { state, ...extra } = vars
try {
return renderPromptTemplate(tpl, buildPromptContext(state, extra))
}
catch (err) {
logger.add({
level: 'error',
source: 'prompts',
message: `${name}提示词模板有语法错误,已原样发出未渲染的模板`,
details: err instanceof Error ? err.message : String(err),
})
return tpl
}
}
/**
* 头脑风暴提示词。模板用 `{{#if issueNumber}}` 分叉:有工单号 = 续聊已有工单,
* 没有 = 新建工单(用 userRequest / nonce)。
*
* 粘贴的参考图片不进模板——它是会话级附加信息,不是模板变量。
*/
export function renderBrainstormPrompt(
ctx: ExtensionContext,
vars: PromptVars & { imagePaths?: string[] },
): string {
const { imagePaths, ...rest } = vars
const out = render('头脑风暴', getSettings(ctx).brainstormPrompt, rest)
if (!imagePaths || imagePaths.length === 0)
return out
const lines = imagePaths.map(p => `- ${p}`).join('\n')
return `${out}\n\n参考图片(请用 Read 工具查看):\n${lines}`
}
export function renderImplementPlanPrompt(ctx: ExtensionContext, vars: PromptVars): string {
return render('实施', getSettings(ctx).implementPlanPrompt, vars)
}
/**
* 测试会话首个提示词。`prRef` 是给验收技能的定位线索:主 worktree 上是
* 已合并的 PR,worktree 里就是那条分支。
*/
export function renderTestPrompt(ctx: ExtensionContext, vars: PromptVars): string {
return render('测试', getSettings(ctx).testPrompt, vars)
}
/** 原型会话首个提示词。只认工单号——原型照工单正文画界面,与 PR / 分支无关。 */
export function renderPrototypePrompt(ctx: ExtensionContext, vars: PromptVars): string {
return render('原型', getSettings(ctx).prototypePrompt, vars)
}
export function renderReviewPrompt(ctx: ExtensionContext, vars: PromptVars): string {
return render('审查', getSettings(ctx).reviewPrompt, vars)
}