feat: 合并失败→补救→完成 全程事件轨迹与自动收口 [tsk_BagzbKZNAHXV]
不新增顶层状态,用 events + result 字段记录完整轨迹: - 新增 merge.failed / merge.remediated 两类事件 - merge 失败时在原任务时间线记 merge.failed 节点(结构化冲突文件 + 补救任务链接) - 补救任务成功合并后,findMergeOriginTask 反查原任务,remediateOrigin 自动把原任务标 done 并记 merge.remediated 节点,无需人工 merge:false - 看板:归档时间线渲染失败/补救节点;结果评审闸显示「合并失败 → 补救任务」横幅 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -302,6 +302,57 @@ test('合并失败补救:建最高优先级 easy 任务,且幂等不重复
|
||||
s.close();
|
||||
});
|
||||
|
||||
test('合并失败→补救→完成:事件流完整 + 补救合并后原任务自动 done', () => {
|
||||
const s = freshStore();
|
||||
const p = s.createProject({ name: 'rem', repoPath: '/tmp/rem-' + Math.random() });
|
||||
// 原任务进入 exec_review、带分支结果
|
||||
const t = s.createTask({ projectId: p.id, title: '原任务', complexity: 'easy', priority: 1 });
|
||||
s.setOperations(t.id, 'op');
|
||||
s.transition(t.id, 'queued');
|
||||
s.transition(t.id, 'executing');
|
||||
s.setResult(t.id, {
|
||||
branch: 'maestro/' + t.id, worktree: '/tmp/wt', diffSummary: '', commits: [], prUrl: null,
|
||||
summary: null, verdict: null, securitySummary: null, securityVerdict: null, mergeTaskId: null,
|
||||
});
|
||||
s.transition(t.id, 'exec_review');
|
||||
|
||||
// 合并失败 → 建补救任务(带结构化冲突文件)
|
||||
const rem = s.ensureMergeRemediationTask(t.id, {
|
||||
branch: 'maestro/' + t.id, targetBranch: 'main',
|
||||
conflictError: '冲突文件:web/app.js', conflictFiles: ['web/app.js', 'src/x.ts'],
|
||||
});
|
||||
|
||||
// 1) 原任务时间线出现 merge.failed 节点(带冲突文件 + 补救任务链接)
|
||||
const failed = s.listTaskEvents(t.id).filter((e) => e.type === 'merge.failed');
|
||||
assert.equal(failed.length, 1, '应记一个合并失败事件');
|
||||
assert.equal(failed[0].payload.remediationTaskId, rem.id);
|
||||
assert.deepEqual(failed[0].payload.conflictFiles, ['web/app.js', 'src/x.ts']);
|
||||
|
||||
// 2) 反查:补救任务 → 原任务
|
||||
assert.equal(s.findMergeOriginTask(rem.id)?.id, t.id);
|
||||
assert.equal(s.findMergeOriginTask('tsk_nope'), null);
|
||||
|
||||
// 3) 补救任务跑到 exec_review,合并成功后自动收口原任务
|
||||
s.transition(rem.id, 'queued');
|
||||
s.transition(rem.id, 'executing');
|
||||
s.transition(rem.id, 'exec_review');
|
||||
s.decide(rem.id, 'accept', 'user'); // 补救任务自身合并(done)
|
||||
const closed = s.remediateOrigin(t.id, rem.id, 'abc123');
|
||||
assert.equal(closed?.status, 'done', '补救合并后原任务自动 done');
|
||||
assert.match(closed?.result?.prUrl ?? '', /^merged-via:/, '记录经补救任务并入');
|
||||
|
||||
// 4) 原任务时间线出现 merge.remediated 节点
|
||||
const remediated = s.listTaskEvents(t.id).filter((e) => e.type === 'merge.remediated');
|
||||
assert.equal(remediated.length, 1, '应记一个补救完成事件');
|
||||
assert.equal(remediated[0].payload.remediationTaskId, rem.id);
|
||||
|
||||
// 幂等:原任务已 done,再次收口不报错、不改状态
|
||||
const again = s.remediateOrigin(t.id, rem.id, 'abc123');
|
||||
assert.equal(again?.status, 'done');
|
||||
assert.equal(s.listTaskEvents(t.id).filter((e) => e.type === 'merge.remediated').length, 1, '不重复记事件');
|
||||
s.close();
|
||||
});
|
||||
|
||||
test('patchProject:maxRetries/timeoutMs 可配 + 校验', () => {
|
||||
const s = freshStore();
|
||||
const p = s.createProject({ name: 'cfg', repoPath: '/tmp/cfg-' + Math.random() });
|
||||
|
||||
Reference in New Issue
Block a user