192 lines
7.7 KiB
TypeScript
192 lines
7.7 KiB
TypeScript
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()
|
||
})
|
||
})
|