740d2c2637
PR 流程(开发→提PR→CodeReview→安全审计→审核→通过即合并): - reviewer 拆分为 code review 与安全审计两个独立 CC run(kind=reviewer/security), TaskResult 四字段(summary/verdict + securitySummary/securityVerdict),闸上两节报告两枚结论徽章 - executor/merge.ts: 通过即合并——临时 worktree 内 merge --no-ff,绝不碰用户工作区/不 push; 冲突安全拒绝(报冲突文件);重复合并幂等;合并后回收执行 worktree + 删分支 - decide 路由 merge:false 逃生口 + 看板「仅通过」按钮(目标分支被工作区检出时用) 通知(daemon/notify.ts): - macOS 原生通知: 进审核闸(复审建议拒绝标⚠)/连续失败需人工/合并完成 - 同任务同类型 60s 抑制、osascript 转义截断、MAESTRO_NOTIFY=0 关闭 订阅额度透传(daemon/usage.ts): - OAuth usage API(与 Claude Code/claude-hud 同源),凭证 keychain→内存零泄漏 - 60s 成败双缓存+并发去重+5s 超时,失败降级 null - GET /api/agents 顶层 usage 字段;Agent 面板显示 5h/周用量条+重置倒计时(>80%琥珀/>95%红) 看板与生命周期: - 归档区: 深度1整树完成沉底,时间倒序分页(尺寸 chip 10/20/50/100 置底) - 归档详情对话框: 全属性/执行历史与时长/审批记录/状态流转时间线(GET /api/tasks/:id/events) - 容器收口: 已拆解 Hard 子任务全 done 自动 done(afterDone 逐级向上) - 同步按钮收进配置面板;执行白名单扩测试命令(npm/go/shellcheck/make/pytest) - Agent 面板显示调度模式与各复杂度模型;被依赖阻塞→被阻塞 测试: 74/74(新增 merge 6/notify 10/usage 7/容器收口/双复审适配) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
3.9 KiB
TypeScript
77 lines
3.9 KiB
TypeScript
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, /执行者未留下自述/);
|
||
}
|
||
});
|