Files
superwork/vscode/src/settings/promptMigration.test.ts
T

192 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
} from './promptMigration'
const defaultBrainstorm = readFileSync(
path.resolve(__dirname, '../../prompts/brainstorm.md'),
'utf-8',
)
describe('upgradePlaceholders', () => {
it('已知占位符改写成双花括号', () => {
expect(upgradePlaceholders('实施 @{planFile} 的 #{issueNumber}')).toBe('实施 @{{planFile}} 的 #{{issueNumber}}')
})
it('已经是双花括号的不再加层', () => {
expect(upgradePlaceholders('{{issueNumber}}')).toBe('{{issueNumber}}')
})
it('名单外的花括号内容原样保留', () => {
expect(upgradePlaceholders('{foo} {planfile} {"a":1}')).toBe('{foo} {planfile} {"a":1}')
})
})
describe('composeBrainstormTemplate', () => {
it('两份正文拼成条件分支,原型段落嵌进 prototypeRef 的位置', () => {
const out = composeBrainstormTemplate({
newIssue: '新建 {{userRequest}}',
cont: '续聊 {{issueNumber}}\n\n{{prototypeRef}}\n\n收尾',
proto: '原型 {{prototypeFile}}\n- id:{{prototypeSessionId}}',
})
expect(out).toBe([
'{{#if issueNumber}}',
'续聊 {{issueNumber}}',
'',
'{{#if prototypeFile}}',
'原型 {{prototypeFile}}',
'{{#if prototypeSessionId}}',
'- id:{{prototypeSessionId}}',
'{{/if}}',
'{{/if}}',
'',
'收尾',
'{{else}}',
'新建 {{userRequest}}',
'{{/if}}',
'',
].join('\n'))
})
it('续聊正文里没有 prototypeRef 时原型段落接在末尾', () => {
const out = composeBrainstormTemplate({ newIssue: '新建', cont: '续聊', proto: '原型 {{prototypeFile}}' })
expect(out).toContain('续聊\n\n{{#if prototypeFile}}\n原型 {{prototypeFile}}\n{{/if}}')
})
it('没有原型段落时不留空的条件块', () => {
const out = composeBrainstormTemplate({ newIssue: '新建', cont: '续聊\n\n{{prototypeRef}}', proto: '' })
expect(out).not.toContain('prototypeFile')
expect(out).not.toContain('prototypeRef')
})
})
describe('splitIfBlock', () => {
it('只认第一层的 else,内层条件块不当分界', () => {
const branches = splitIfBlock(defaultBrainstorm, 'issueNumber')
expect(branches!.then).toContain('/ecc:grilling')
expect(branches!.then).toContain('{{#if prototypeFile}}')
expect(branches!.else).toContain('opencli spx issue create')
expect(branches!.else).not.toContain('{{else}}')
})
it('没有这个条件块时返回 null', () => {
expect(splitIfBlock('普通模板', 'issueNumber')).toBeNull()
})
})
describe('planPromptMigration', () => {
const legacy = {
brainstormPrompt: '建工单 {userRequest} nonce={nonce}',
brainstormContinuePrompt: '讨论 {issueNumber}\n\n{prototypeRef}\n\n收尾',
prototypeRefPrompt: '原型 {prototypeFile}\n- id:{prototypeSessionId}',
implementPlanPrompt: '实施 @{planFile} #{issueNumber}',
testPrompt: '验收 {issueNumber}({prRef})',
reviewPrompt: '审查 #{prNumber}',
prototypePrompt: '画原型 {issueNumber}',
webhookPort: 17421,
}
it('五份存量提示词全部换成双花括号,并合并成一份头脑风暴模板', () => {
const { blob, notes } = planPromptMigration(legacy, defaultBrainstorm)
expect(blob).not.toBeNull()
expect(blob!.implementPlanPrompt).toBe('实施 @{{planFile}} #{{issueNumber}}')
expect(blob!.testPrompt).toBe('验收 {{issueNumber}}({{prRef}})')
expect(blob!.reviewPrompt).toBe('审查 #{{prNumber}}')
expect(blob!.prototypePrompt).toBe('画原型 {{issueNumber}}')
expect(blob!.brainstormPrompt).toContain('{{#if issueNumber}}')
expect(blob!.brainstormPrompt).toContain('{{#if prototypeFile}}')
expect(blob!.brainstormPrompt).toContain('{{#if prototypeSessionId}}')
expect(blob!.brainstormPrompt).toContain('{{else}}')
expect(blob!.brainstormPrompt).toContain('建工单 {{userRequest}} nonce={{nonce}}')
expect(notes.some(n => n.level === 'warn')).toBe(false)
})
it('被删掉的两个字段迁移后不再留在 settings 里,其他设置不动', () => {
const { blob } = planPromptMigration(legacy, defaultBrainstorm)
expect(blob!.brainstormContinuePrompt).toBeUndefined()
expect(blob!.prototypeRefPrompt).toBeUndefined()
expect(blob!.webhookPort).toBe(17421)
expect(blob!.promptTemplateVersion).toBe(PROMPT_TEMPLATE_VERSION)
})
it('迁过一次就不再重复迁', () => {
const { blob } = planPromptMigration(legacy, defaultBrainstorm)
expect(planPromptMigration(blob!, defaultBrainstorm)).toEqual({ blob: null, notes: [] })
})
it('没存过任何提示词时什么都不做', () => {
expect(planPromptMigration({ webhookPort: 17421 }, defaultBrainstorm)).toEqual({ blob: null, notes: [] })
})
it('只改过新建工单提示词时,续聊分支用内置默认补齐', () => {
const { blob } = planPromptMigration(
{ brainstormPrompt: '建工单 {userRequest}', brainstormContinuePrompt: '' },
defaultBrainstorm,
)
const merged = String(blob!.brainstormPrompt)
expect(merged).toContain('建工单 {{userRequest}}')
expect(merged).toContain('/ecc:grilling 讨论下 {{issueNumber}} 号工单')
// 默认续聊分支自带原型段落,别再插第二份
expect(merged.match(/\{\{#if prototypeFile\}\}/g)).toHaveLength(1)
})
it('只改过续聊提示词时,新建分支和原型段落用内置默认补齐', () => {
const { blob } = planPromptMigration(
{ brainstormContinuePrompt: '讨论 {issueNumber}\n\n{prototypeRef}' },
defaultBrainstorm,
)
const merged = String(blob!.brainstormPrompt)
expect(merged).toContain('讨论 {{issueNumber}}')
expect(merged).toContain('opencli spx issue create')
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()
})
})