/** * Prompt templates for the two claude entry points (brainstorm and * implement-plan). Defaults live in `src/settings/store.ts` (alongside the * other tunables); these helpers just read the persisted setting and run * placeholder substitution. * * Substitution is a plain global string replace of the placeholder text * (`{userRequest}` / `{planFile}`) — no template engine. If the user removes * the placeholder, substitution is a no-op and that's their bug. */ import type { ExtensionContext } from 'vscode' import { getSettings } from '../settings/store' export function getBrainstormPrompt( ctx: ExtensionContext, vars: { userRequest: string, nonce: string, imagePaths?: string[] }, ): string { const tpl = getSettings(ctx).brainstormPrompt let out = tpl .split('{userRequest}') .join(vars.userRequest) .split('{nonce}') .join(vars.nonce) if (vars.imagePaths && vars.imagePaths.length > 0) { const lines = vars.imagePaths.map(p => `- ${p}`).join('\n') out += `\n\n参考图片(请用 Read 工具查看):\n${lines}` } return out } export function getBrainstormContinuePrompt( ctx: ExtensionContext, vars: { issueNumber: number, issueRef?: string, prototypeFile?: string }, ): string { const tpl = getSettings(ctx).brainstormContinuePrompt // For youtrack cards the board number is synthetic; substitute the readable // 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) } /** * `{prototypeRef}` 在模板里独占一段:有原型插件就填一段「先读它」的交代, * 没有就连同前后空行一起抹掉——否则模板里会留下一个空洞段落。 */ function fillPrototypeRef(tpl: string, prototypeFile: string | undefined): string { if (!prototypeFile) return tpl.replace(/\n*\{prototypeRef\}\n*/g, '\n\n').trimEnd() const para = `工单已有原型插件 ${prototypeFile},先读它了解已经定下来的界面和交互,在此基础上只对齐还不明确的地方,不要从零重画。` return tpl.split('{prototypeRef}').join(para) } export function getImplementPlanPrompt(ctx: ExtensionContext, vars: { planFile: string, issueNumber: number, issueRef?: string }): string { const tpl = getSettings(ctx).implementPlanPrompt return tpl .split('{planFile}') .join(vars.planFile) .split('{issueNumber}') .join(vars.issueRef ?? String(vars.issueNumber)) } /** * 测试会话首个提示词。`prRef` 是给验收技能的定位线索:主 worktree 上是 * 已合并的 PR,worktree 里就是那条分支。 */ export function getTestPrompt(ctx: ExtensionContext, vars: { issueNumber: number, issueRef?: string, prRef: string }): string { const tpl = getSettings(ctx).testPrompt return tpl .split('{issueNumber}') .join(vars.issueRef ?? String(vars.issueNumber)) .split('{prRef}') .join(vars.prRef) } /** * 原型会话首个提示词。只认工单号——原型是照工单正文画界面,与 PR / 分支无关。 */ export function getPrototypePrompt(ctx: ExtensionContext, vars: { issueNumber: number, issueRef?: string }): string { const tpl = getSettings(ctx).prototypePrompt return tpl.split('{issueNumber}').join(vars.issueRef ?? String(vars.issueNumber)) } export function getReviewPrompt(ctx: ExtensionContext, vars: { prNumber: string }): string { const tpl = getSettings(ctx).reviewPrompt return tpl.split('{prNumber}').join(vars.prNumber) }