✨ feat(vscode): 测试提示词改成可配置模板并带上工单号

启动测试会话原先硬编码发 `/pr-acceptance-testing`,不带目标,验收技能只能
反问要测哪个。现在做成 prompts/test.md 模板 + 设置里的「测试提示词」,占位符
{issueNumber} 和 {prRef}(worktree 里是分支,主仓库里是已合并的 PR)。

Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
This commit is contained in:
cruldra committed 2026-09-13 13:19:28 +08:00
1 parent e05a5234db
commit f946e5cfee
11 files changed
+77 -4

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
/pr-acceptance-testing 验收 {issueNumber} 号工单({prRef})。
验收目标以工单正文为准,不要再问验收哪个。测出问题就停下报告,不要顺手改代码。
+13
View File
@@ -46,6 +46,19 @@ export function getImplementPlanPrompt(ctx: ExtensionContext, vars: { planFile:
.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)
}
export function getReviewPrompt(ctx: ExtensionContext, vars: { prNumber: string }): string {
const tpl = getSettings(ctx).reviewPrompt
return tpl.split('{prNumber}').join(vars.prNumber)
+2
View File
@@ -988,6 +988,7 @@ export class KanbanWebviewPanel {
implementPlanPrompt: s.implementPlanPrompt,
autoReview: s.autoReview,
reviewPrompt: s.reviewPrompt,
testPrompt: s.testPrompt,
devBranch: s.devBranch,
releaseBranchPrefix: s.releaseBranchPrefix,
jenkinsJob: s.jenkinsJob,
@@ -1076,6 +1077,7 @@ export class KanbanWebviewPanel {
implementPlanPrompt: s.implementPlanPrompt,
autoReview: s.autoReview,
reviewPrompt: s.reviewPrompt,
testPrompt: s.testPrompt,
devBranch: s.devBranch,
releaseBranchPrefix: s.releaseBranchPrefix,
jenkinsJob: s.jenkinsJob,
+9 -3
View File
@@ -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, getReviewPrompt } from '../../cc/prompts'
import { getBrainstormContinuePrompt, getImplementPlanPrompt, getReviewPrompt, getTestPrompt } from '../../cc/prompts'
import { projectsDirFor, watchForNewSession } from '../../cc/sessionWatcher'
import { detectRepo } from '../../git/remote'
import { ensureWorktree, resolveWorktreePath } from '../../git/worktree'
@@ -1176,8 +1176,14 @@ export async function handleStartTestSession(panel: KanbanWebviewPanel, issueNum
}
// worktree 里:代码就在当前工作目录,不用 tea 拉取,直接测;
// 主 worktree:PR 已合并,先用 tea 熟悉改动再测。
const prompt = '/pr-acceptance-testing'
// 主 worktree:PR 已合并,先用 tea 熟悉改动再测。验收目标直接写进提示词,
// 省得验收技能反问要测哪个。
const prompt = getTestPrompt(panel.context, {
issueNumber,
prRef: inWorktree
? `worktree 分支 ${branch || effectiveCwd}`
: `PR #${pr}`,
})
if (prompt.includes('\'')) {
void window.showErrorMessage('启动测试失败:prompt 含单引号,拒绝执行')
return
+4
View File
@@ -27,6 +27,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
implementPlanPrompt: string
autoReview: boolean
reviewPrompt: string
testPrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -113,6 +114,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
implementPlanPrompt: payload.implementPlanPrompt || prev.implementPlanPrompt,
autoReview: payload.autoReview,
reviewPrompt: payload.reviewPrompt || prev.reviewPrompt,
testPrompt: payload.testPrompt || prev.testPrompt,
devBranch: trimmedDevBranch || prev.devBranch,
releaseBranchPrefix: trimmedReleasePrefix || prev.releaseBranchPrefix,
jenkinsJob: trimmedJenkinsJob,
@@ -145,6 +147,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
implementPlanPrompt: payload.implementPlanPrompt,
autoReview: payload.autoReview,
reviewPrompt: payload.reviewPrompt,
testPrompt: payload.testPrompt,
devBranch: trimmedDevBranch,
releaseBranchPrefix: trimmedReleasePrefix,
jenkinsJob: trimmedJenkinsJob,
@@ -254,6 +257,7 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom
implementPlanPrompt: s.implementPlanPrompt,
autoReview: s.autoReview,
reviewPrompt: s.reviewPrompt,
testPrompt: s.testPrompt,
devBranch: s.devBranch,
releaseBranchPrefix: s.releaseBranchPrefix,
jenkinsJob: s.jenkinsJob,
+2
View File
@@ -76,6 +76,7 @@ export type ExtensionToWebview
implementPlanPrompt: string
autoReview: boolean
reviewPrompt: string
testPrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -158,6 +159,7 @@ export type WebviewToExtension
implementPlanPrompt: string
autoReview: boolean
reviewPrompt: string
testPrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
+11 -1
View File
@@ -30,9 +30,11 @@ const FALLBACK_BRAINSTORM_CONTINUE_PROMPT = `/ecc:grilling 讨论下 {issueNumbe
const FALLBACK_IMPLEMENT_PLAN_PROMPT = `/ecc:tdd @{planFile} 按计划实施,编码派子代理,发起 PR 时在 body 中包含 "Closes #{issueNumber}"。严禁合并 PR。`
const FALLBACK_TEST_PROMPT = `/pr-acceptance-testing 验收 {issueNumber} 号工单({prRef})。验收目标以工单正文为准,不要再问验收哪个。`
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'
type PromptName = 'brainstorm' | 'brainstorm-continue' | 'implement-plan' | 'review' | 'test'
/**
* Read a default prompt template from `${extensionPath}/prompts/${name}.md`.
@@ -54,6 +56,7 @@ export function readDefaultPrompt(extensionPath: string, name: PromptName): stri
case 'brainstorm-continue': return FALLBACK_BRAINSTORM_CONTINUE_PROMPT
case 'implement-plan': return FALLBACK_IMPLEMENT_PLAN_PROMPT
case 'review': return FALLBACK_REVIEW_PROMPT
case 'test': return FALLBACK_TEST_PROMPT
}
}
}
@@ -87,6 +90,8 @@ export interface Settings {
autoReview: boolean
/** Prompt template for the auto-review flow. `{prNumber}` placeholder. */
reviewPrompt: string
/** 测试会话首个提示词模板;占位符 {issueNumber} / {prRef}。 */
testPrompt: string
/** Day-to-day development branch (e.g. `main`). Jenkins doesn't watch it. */
devBranch: string
/**
@@ -266,6 +271,7 @@ function defaults(ctx: ExtensionContext): Settings {
implementPlanPrompt: readDefaultPrompt(ctx.extensionPath, 'implement-plan'),
autoReview: true,
reviewPrompt: readDefaultPrompt(ctx.extensionPath, 'review'),
testPrompt: readDefaultPrompt(ctx.extensionPath, 'test'),
devBranch: 'main',
releaseBranchPrefix: 'release/',
jenkinsJob: '',
@@ -319,6 +325,9 @@ export function getSettings(ctx: ExtensionContext): Settings {
const reviewPrompt = typeof stored.reviewPrompt === 'string' && stored.reviewPrompt.length > 0
? stored.reviewPrompt
: base.reviewPrompt
const testPrompt = typeof stored.testPrompt === 'string' && stored.testPrompt.length > 0
? stored.testPrompt
: base.testPrompt
// 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
@@ -424,6 +433,7 @@ export function getSettings(ctx: ExtensionContext): Settings {
implementPlanPrompt,
autoReview,
reviewPrompt,
testPrompt,
devBranch,
releaseBranchPrefix,
jenkinsJob,
+1
View File
@@ -538,6 +538,7 @@ export function App() {
initialImplementPlanPrompt={settings?.implementPlanPrompt ?? ''}
initialAutoReview={settings?.autoReview ?? true}
initialReviewPrompt={settings?.reviewPrompt ?? ''}
initialTestPrompt={settings?.testPrompt ?? ''}
initialDevBranch={settings?.devBranch ?? 'main'}
initialReleaseBranchPrefix={settings?.releaseBranchPrefix ?? 'release/'}
initialJenkinsJob={settings?.jenkinsJob ?? ''}
@@ -25,6 +25,7 @@ interface SubmitValues {
implementPlanPrompt: string
autoReview: boolean
reviewPrompt: string
testPrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -78,6 +79,7 @@ export interface SettingsModalProps {
initialImplementPlanPrompt: string
initialAutoReview: boolean
initialReviewPrompt: string
initialTestPrompt: string
initialDevBranch: string
initialReleaseBranchPrefix: string
initialJenkinsJob: string
@@ -167,6 +169,7 @@ export function SettingsModal({
initialImplementPlanPrompt,
initialAutoReview,
initialReviewPrompt,
initialTestPrompt,
initialDevBranch,
initialReleaseBranchPrefix,
initialJenkinsJob,
@@ -208,6 +211,7 @@ export function SettingsModal({
const [implementPlanPrompt, setImplementPlanPrompt] = useState(initialImplementPlanPrompt)
const [autoReview, setAutoReview] = useState(initialAutoReview)
const [reviewPrompt, setReviewPrompt] = useState(initialReviewPrompt)
const [testPrompt, setTestPrompt] = useState(initialTestPrompt)
const [devBranch, setDevBranch] = useState(initialDevBranch)
const [releaseBranchPrefix, setReleaseBranchPrefix] = useState(initialReleaseBranchPrefix)
const [jenkinsJob, setJenkinsJob] = useState(initialJenkinsJob)
@@ -251,6 +255,7 @@ export function SettingsModal({
setImplementPlanPrompt(initialImplementPlanPrompt)
setAutoReview(initialAutoReview)
setReviewPrompt(initialReviewPrompt)
setTestPrompt(initialTestPrompt)
setDevBranch(initialDevBranch)
setReleaseBranchPrefix(initialReleaseBranchPrefix)
setJenkinsJob(initialJenkinsJob)
@@ -342,6 +347,7 @@ export function SettingsModal({
implementPlanPrompt,
autoReview,
reviewPrompt,
testPrompt,
devBranch: devBranch.trim(),
releaseBranchPrefix: releaseBranchPrefix.trim(),
jenkinsJob: jenkinsJob.trim(),
@@ -1002,6 +1008,26 @@ export function SettingsModal({
className={`${inputClass} resize-y font-mono`}
/>
</Field>
<Field
label="测试提示词"
hint={(
<>
可用占位符:
<code>{'{issueNumber}'}</code>
、
<code>{'{prRef}'}</code>
(worktree 里是分支,主仓库里是已合并的 PR)
</>
)}
>
<textarea
rows={4}
value={testPrompt}
onChange={e => setTestPrompt(e.target.value)}
className={`${inputClass} resize-y font-mono`}
/>
</Field>
</>
)}
+4
View File
@@ -43,6 +43,7 @@ export interface SettingsValues {
implementPlanPrompt: string
autoReview: boolean
reviewPrompt: string
testPrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -82,6 +83,7 @@ export interface SettingsOverlayState {
implementPlanPrompt: string
autoReview: boolean
reviewPrompt: string
testPrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -421,6 +423,7 @@ export function useIssues(): UseIssuesResult {
implementPlanPrompt: values.implementPlanPrompt,
autoReview: values.autoReview,
reviewPrompt: values.reviewPrompt,
testPrompt: values.testPrompt,
devBranch: values.devBranch,
releaseBranchPrefix: values.releaseBranchPrefix,
jenkinsJob: values.jenkinsJob,
@@ -886,6 +889,7 @@ export function useIssues(): UseIssuesResult {
implementPlanPrompt: msg.implementPlanPrompt,
autoReview: msg.autoReview,
reviewPrompt: msg.reviewPrompt,
testPrompt: msg.testPrompt,
devBranch: msg.devBranch,
releaseBranchPrefix: msg.releaseBranchPrefix,
jenkinsJob: msg.jenkinsJob,
+2
View File
@@ -107,6 +107,7 @@ export type ExtensionToWebview
implementPlanPrompt: string
autoReview: boolean
reviewPrompt: string
testPrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string
@@ -189,6 +190,7 @@ export type WebviewToExtension
implementPlanPrompt: string
autoReview: boolean
reviewPrompt: string
testPrompt: string
devBranch: string
releaseBranchPrefix: string
jenkinsJob: string