✨ feat(vscode): 审查通过写入工单状态并在卡片上区分
This commit is contained in:
1 parent
ac1b7ed641
commit
177e75910e
21 files changed
+203
-10
No files matched your search
@@ -0,0 +1,2 @@
|
||||
/** 改「审查结论」之前的默认审查提示词。设置里若原样存着这份,整段换成新文件。 */
|
||||
export const PREVIOUS_DEFAULT_REVIEW_PROMPT = '审查这个仓库的 PR #{{prNumber}}。\n\n用 `tea pulls {{prNumber}}` 看 PR 元信息(标题/描述/分支)。看代码差异用 git(当前目录就是 PR 分支的 worktree):先 `git fetch origin main`,再 `git diff origin/main...HEAD`(概览可加 --stat)。注意 tea 没有 `diff` 子命令,不要尝试 `tea pulls diff`。\n\n## 审查方式\n\nECC 的 `/ecc:review-pr` 和 `/ecc:code-review` 的 PR 模式都走 `gh`,对 Gitea 不可用,不要调。直接把上面的 diff 喂给 ECC 的审查子代理,按改动涉及的语言挑:\n\n- `ecc:code-reviewer`(必跑)\n- `ecc:silent-failure-hunter`、`ecc:pr-test-analyzer`(必跑)\n- 语言专项:TypeScript 用 `ecc:typescript-reviewer`,Python 用 `ecc:python-reviewer`,React 用 `ecc:react-reviewer`,其余同理\n\n汇总时去重、按严重程度排序,只报置信度高的问题。\n\n## 提交审查意见\n\n把审查意见(markdown 格式)写到 `/tmp/review-{{prNumber}}.md`,调 spx:\n\n```\nopencli spx pr review-comment --pr {{prNumber}} --body-file /tmp/review-{{prNumber}}.md\n```\n\n审查结束以后,删除所有审查过程中创建的临时 worktree(`git worktree remove --force <路径>`),你当前所在的 worktree 除外。\n\n## 严禁\n\n- 不要在审查意见里建议"合并 PR"或"merge"\n- 不要执行 `tea pulls merge` 或任何合并命令\n- 不要 push 到 main / dev 分支\n\n合并权完全在用户手上,你的工作只是指出问题或确认通过。\n'
|
||||
@@ -1,9 +1,11 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { PREVIOUS_DEFAULT_REVIEW_PROMPT } from './previousReviewPrompt'
|
||||
import {
|
||||
composeBrainstormTemplate,
|
||||
planPromptMigration,
|
||||
planReviewPromptMigration,
|
||||
PROMPT_TEMPLATE_VERSION,
|
||||
splitIfBlock,
|
||||
upgradePlaceholders,
|
||||
@@ -147,3 +149,43 @@ describe('planPromptMigration', () => {
|
||||
expect(merged).toContain('工单已有原型插件 {{prototypeFile}}')
|
||||
})
|
||||
})
|
||||
|
||||
describe('planReviewPromptMigration', () => {
|
||||
const nextDefault = readFileSync(
|
||||
path.resolve(__dirname, '../../prompts/review.md'),
|
||||
'utf-8',
|
||||
)
|
||||
|
||||
it('和改之前的默认一样时,整段换成现在的文件', () => {
|
||||
const result = planReviewPromptMigration(
|
||||
PREVIOUS_DEFAULT_REVIEW_PROMPT,
|
||||
PREVIOUS_DEFAULT_REVIEW_PROMPT,
|
||||
nextDefault,
|
||||
)
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.prompt).toBe(nextDefault)
|
||||
expect(result!.prompt).toContain('{"reviewPassed":true}')
|
||||
expect(result!.notes[0]?.message).toContain('旧版默认')
|
||||
})
|
||||
|
||||
it('用户改过、还没有 reviewPassed 时,只把审查结论接到末尾', () => {
|
||||
const custom = '先看 diff,再写意见。\n不要跑无关命令。\n'
|
||||
const result = planReviewPromptMigration(custom, PREVIOUS_DEFAULT_REVIEW_PROMPT, nextDefault)
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.prompt.startsWith('先看 diff,再写意见。\n不要跑无关命令。\n\n## 审查结论\n')).toBe(true)
|
||||
expect(result!.prompt).toContain('reviewPassed')
|
||||
expect(result!.prompt).toContain('小建议不算不通过')
|
||||
expect(result!.prompt).not.toContain('## 严禁')
|
||||
expect(result!.prompt).not.toContain('tea pulls')
|
||||
})
|
||||
|
||||
it('已经写了 reviewPassed 的不改', () => {
|
||||
const custom = '我自己的审查。\nreviewPassed 我写过了。\n'
|
||||
expect(planReviewPromptMigration(custom, PREVIOUS_DEFAULT_REVIEW_PROMPT, nextDefault)).toBeNull()
|
||||
})
|
||||
|
||||
it('没存审查提示词时不改', () => {
|
||||
expect(planReviewPromptMigration('', PREVIOUS_DEFAULT_REVIEW_PROMPT, nextDefault)).toBeNull()
|
||||
expect(planReviewPromptMigration(undefined, PREVIOUS_DEFAULT_REVIEW_PROMPT, nextDefault)).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -188,3 +188,52 @@ export function planPromptMigration(
|
||||
next.promptTemplateVersion = PROMPT_TEMPLATE_VERSION
|
||||
return { blob: next, notes }
|
||||
}
|
||||
|
||||
function normalizePrompt(text: string): string {
|
||||
return text.replace(/\r\n/g, '\n').trim()
|
||||
}
|
||||
|
||||
/** 新版审查提示词里「审查结论」那一节,到下一个同级标题为止。 */
|
||||
export function reviewConclusionSection(nextDefault: string): string | null {
|
||||
const normalized = nextDefault.replace(/\r\n/g, '\n')
|
||||
const heading = '## 审查结论'
|
||||
const start = normalized.indexOf(heading)
|
||||
if (start < 0)
|
||||
return null
|
||||
const rest = normalized.slice(start)
|
||||
const relative = rest.slice(heading.length).search(/\n## /)
|
||||
const section = relative < 0 ? rest : rest.slice(0, heading.length + relative)
|
||||
const trimmed = section.trim()
|
||||
return trimmed.length > 0 ? trimmed : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置里已经存了一份审查提示词时:和改之前的默认一样就整段换成新文件;
|
||||
* 用户改过、且还没有 reviewPassed,只把「审查结论」接到末尾。
|
||||
* 返回 null 表示不用写盘。
|
||||
*/
|
||||
export function planReviewPromptMigration(
|
||||
stored: unknown,
|
||||
previousDefault: string,
|
||||
nextDefault: string,
|
||||
): { prompt: string, notes: MigrationNote[] } | null {
|
||||
if (typeof stored !== 'string' || stored.length === 0)
|
||||
return null
|
||||
if (normalizePrompt(stored) === normalizePrompt(previousDefault)) {
|
||||
if (normalizePrompt(nextDefault) === normalizePrompt(previousDefault))
|
||||
return null
|
||||
return {
|
||||
prompt: nextDefault,
|
||||
notes: [{ level: 'info', message: '审查提示词与旧版默认一致,已换成带审查结论的新版' }],
|
||||
}
|
||||
}
|
||||
if (stored.includes('reviewPassed'))
|
||||
return null
|
||||
const section = reviewConclusionSection(nextDefault)
|
||||
if (!section)
|
||||
return null
|
||||
return {
|
||||
prompt: `${stored.replace(/\s+$/, '')}\n\n${section}\n`,
|
||||
notes: [{ level: 'info', message: '审查提示词末尾已追加「审查结论」,其余段落未改' }],
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@ import path from 'node:path'
|
||||
import { seedCommitProfilePath } from '../cc/commitProfile'
|
||||
import { listClaudeProfiles } from '../cc/profiles'
|
||||
import { normalizeReleasePrefix } from '../git/release'
|
||||
import { planPromptMigration } from './promptMigration'
|
||||
import { PREVIOUS_DEFAULT_REVIEW_PROMPT } from './previousReviewPrompt'
|
||||
import { planPromptMigration, planReviewPromptMigration } from './promptMigration'
|
||||
|
||||
/**
|
||||
* Fallback prompt strings used when the on-disk markdown can't be read.
|
||||
@@ -513,10 +514,19 @@ export async function saveSettings(ctx: ExtensionContext, next: Partial<Settings
|
||||
* 单测里直接 import。
|
||||
*/
|
||||
export function migratePromptTemplates(ctx: ExtensionContext): MigrationNote[] {
|
||||
const { blob, notes } = planPromptMigration(
|
||||
readStoredSettings(ctx) as Record<string, unknown>,
|
||||
const stored = readStoredSettings(ctx) as Record<string, unknown>
|
||||
const planned = planPromptMigration(
|
||||
stored,
|
||||
readDefaultPrompt(ctx.extensionPath, 'brainstorm'),
|
||||
)
|
||||
const review = planReviewPromptMigration(
|
||||
planned.blob?.reviewPrompt ?? stored.reviewPrompt,
|
||||
PREVIOUS_DEFAULT_REVIEW_PROMPT,
|
||||
readDefaultPrompt(ctx.extensionPath, 'review'),
|
||||
)
|
||||
const blob = review
|
||||
? { ...(planned.blob ?? stored), reviewPrompt: review.prompt }
|
||||
: planned.blob
|
||||
if (!blob)
|
||||
return []
|
||||
const out: MigrationNote[] = []
|
||||
@@ -535,7 +545,11 @@ export function migratePromptTemplates(ctx: ExtensionContext): MigrationNote[] {
|
||||
}
|
||||
}
|
||||
writeStoredSettings(ctx, blob as Partial<Settings>)
|
||||
out.push(...notes, { level: 'info', message: '提示词模板已迁到 Handlebars' })
|
||||
out.push(...planned.notes)
|
||||
if (review)
|
||||
out.push(...review.notes)
|
||||
if (planned.blob)
|
||||
out.push({ level: 'info', message: '提示词模板已迁到 Handlebars' })
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user