import { test } from 'node:test'; import assert from 'node:assert/strict'; import { parseVerdict, buildReviewPrompt } from '../src/executor/reviewer.js'; import type { Project, Task } from '../src/model/types.js'; test('parseVerdict:approve', () => { const { summary, verdict } = parseVerdict('## 做了什么\n改了 README\n\n## 结论\n建议通过\nVERDICT: approve'); assert.equal(verdict, 'approve'); assert.equal(summary, '## 做了什么\n改了 README\n\n## 结论\n建议通过'); // VERDICT 行已剔除 }); test('parseVerdict:reject(大小写/前后空白宽容)', () => { const { summary, verdict } = parseVerdict('问题很多\n verdict: REJECT \n'); assert.equal(verdict, 'reject'); assert.equal(summary, '问题很多'); }); test('parseVerdict:解析不到 = null,summary 原样保留', () => { const { summary, verdict } = parseVerdict('## 结论\n建议通过,但忘了写 VERDICT 行'); assert.equal(verdict, null); assert.equal(summary, '## 结论\n建议通过,但忘了写 VERDICT 行'); }); test('parseVerdict:多个 VERDICT 行取最后一个,全部从 summary 剔除', () => { const { summary, verdict } = parseVerdict('VERDICT: approve\n复查后改判\nVERDICT: reject'); assert.equal(verdict, 'reject'); assert.equal(summary, '复查后改判'); }); test('parseVerdict:VERDICT 行带多余内容不算(如模板原文 approve|reject)', () => { const { verdict } = parseVerdict('VERDICT: approve|reject'); assert.equal(verdict, null); }); test('buildReviewPrompt(code):含任务说明、执行者自述、diff 指令与 code review 模板', () => { const task = { id: 'task_1', title: '修复登录', operations: '改 auth.ts', spec: null, plan: null } as Task; const project = { defaultBranch: 'main' } as Project; const wt = { dir: '/tmp/wt', branch: 'maestro/task_1' }; const prompt = buildReviewPrompt('code', task, project, wt, '我改了 auth.ts 并加了测试'); assert.match(prompt, /修复登录/); assert.match(prompt, /改 auth\.ts/); assert.match(prompt, /我改了 auth\.ts 并加了测试/); assert.match(prompt, /git diff main\.\.\.maestro\/task_1/); for (const section of ['## 做了什么', '## 怎么做的', '## 测试情况', '## Code Review', '## 结论']) { assert.ok(prompt.includes(section), `模板缺少 ${section}`); } assert.ok(!prompt.includes('## 安全审计'), 'code review 模板不应含安全审计节'); assert.match(prompt, /VERDICT: approve\|reject/); assert.match(prompt, /只读权限/); }); test('buildReviewPrompt(security):安全审计模板(凭证/注入/权限/文案红线/部署风险)', () => { const task = { id: 'task_1', title: '修复登录', operations: '改 auth.ts', spec: null, plan: null } as Task; const project = { defaultBranch: 'main' } as Project; const wt = { dir: '/tmp/wt', branch: 'maestro/task_1' }; const prompt = buildReviewPrompt('security', task, project, wt, '我改了 auth.ts'); assert.match(prompt, /安全审计/); for (const section of ['凭证与密钥泄露', '注入与危险命令', '权限与边界', 'UI 文案红线词', '生产部署风险', '## 结论']) { assert.ok(prompt.includes(section), `安全模板缺少 ${section}`); } assert.ok(!prompt.includes('## 做了什么'), '安全审计模板不应含 code review 节'); assert.match(prompt, /git diff main\.\.\.maestro\/task_1/); assert.match(prompt, /VERDICT: approve\|reject/); assert.match(prompt, /只读权限/); }); test('buildReviewPrompt:执行者无自述时给占位说明(两种角色)', () => { const task = { id: 'task_2', title: 't', operations: null, spec: null, plan: null } as Task; const project = { defaultBranch: 'main' } as Project; for (const role of ['code', 'security'] as const) { const prompt = buildReviewPrompt(role, task, project, { dir: '/x', branch: 'maestro/task_2' }, ''); assert.match(prompt, /执行者未留下自述/); } });