feat(merge): 自动合并失败时建最高优先级补救任务来完成合并
decide 端 exec_review accept→merge 冲突失败时,不再只是报错留在闸: 自动建一个 P0 easy 补救任务(operations 写明分支/目标/冲突详情与解冲突步骤), 自治项目会自动领取去解决。幂等:补救任务 id 记到原任务 result.mergeTaskId, 已有未结束补救任务则复用不重复建;结束(done/cancelled)后允许新建。 - model/types: TaskResult 加 mergeTaskId 字段 - mappers/orchestrator: 同步透传/补 null - store.ensureMergeRemediationTask + store.test 覆盖(P0/easy/ready/operations/幂等/可重建) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+7
-1
@@ -240,7 +240,13 @@ export function buildServer(opts: ApiOptions): FastifyInstance {
|
||||
if (!project) throw new StoreError(`项目不存在: ${task.projectId}`);
|
||||
const { branch, worktree } = task.result;
|
||||
const mr = await mergeBranch(project.repoPath, branch, project.defaultBranch, task.id);
|
||||
if (!mr.ok) throw new StoreError(`合并失败:${mr.error}(任务保留在审核闸)`);
|
||||
if (!mr.ok) {
|
||||
// 自动合并失败(多为冲突)→ 建/复用最高优先级补救任务来完成合并,原任务留在审核闸
|
||||
const rem = store.ensureMergeRemediationTask(task.id, {
|
||||
branch, targetBranch: project.defaultBranch, conflictError: mr.error ?? '未知冲突',
|
||||
});
|
||||
throw new StoreError(`合并失败:${mr.error}(原任务保留在审核闸;已建最高优先级补救任务 ${rem.id}「${rem.title}」来完成合并)`);
|
||||
}
|
||||
|
||||
store.decide(id, 'accept', b.actor ?? 'user', b.reason ?? null);
|
||||
// 合并产物记录:复用 prUrl 字段写 merged:<mergeCommit>
|
||||
|
||||
@@ -140,6 +140,7 @@ export function createOrchestrator(store: Store, log: OrchestratorLogger, deps:
|
||||
diffSummary: diff.diffSummary, commits: diff.commits, prUrl: null,
|
||||
summary: code.summary, verdict: code.verdict,
|
||||
securitySummary: sec.summary, securityVerdict: sec.verdict,
|
||||
mergeTaskId: null,
|
||||
});
|
||||
store.transition(task.id, 'exec_review', { by: 'orchestrator', runId: run.id });
|
||||
store.finishRun(run.id, 'succeeded', {
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface TaskResult {
|
||||
verdict: ReviewVerdict | null; // code review 结论,解析不到 / 复审失败 = null
|
||||
securitySummary: string | null; // 安全审计报告(markdown),旧数据缺省 null
|
||||
securityVerdict: ReviewVerdict | null; // 安全审计结论,解析不到 / 审计失败 = null
|
||||
mergeTaskId: string | null; // 自动合并失败时建的最高优先级补救任务 id(幂等标记)
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
|
||||
@@ -53,6 +53,7 @@ function parseResult(json: string): TaskResult {
|
||||
verdict: verdict(raw.verdict),
|
||||
securitySummary: raw.securitySummary ?? null,
|
||||
securityVerdict: verdict(raw.securityVerdict),
|
||||
mergeTaskId: raw.mergeTaskId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +257,49 @@ export class Store {
|
||||
return this.getTask(taskId)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动合并失败时,为原任务建一个最高优先级(P0)补救任务来完成合并。
|
||||
* 幂等:原任务 result.mergeTaskId 已指向一个未结束(非 done/cancelled)的任务则直接复用,不重复建。
|
||||
* 补救任务为 easy(直达 ready,自治项目会自动领取去解冲突),operations 写明分支/目标/冲突详情与步骤。
|
||||
*/
|
||||
ensureMergeRemediationTask(
|
||||
taskId: string,
|
||||
opts: { branch: string; targetBranch: string; conflictError: string },
|
||||
): Task {
|
||||
const orig = this.getTask(taskId);
|
||||
if (!orig) throw new StoreError(`任务不存在: ${taskId}`);
|
||||
|
||||
const existingId = orig.result?.mergeTaskId;
|
||||
if (existingId) {
|
||||
const existing = this.getTask(existingId);
|
||||
if (existing && existing.status !== 'done' && existing.status !== 'cancelled') return existing;
|
||||
}
|
||||
|
||||
const rem = this.createTask({
|
||||
projectId: orig.projectId,
|
||||
title: `合并冲突待解决:${orig.title}`,
|
||||
complexity: 'easy',
|
||||
priority: 0, // 最高优先级
|
||||
});
|
||||
this.setOperations(
|
||||
rem.id,
|
||||
`## 操作(自动生成 · 合并冲突补救)\n` +
|
||||
`原任务「${orig.title}」(${orig.id}) 的分支 \`${opts.branch}\` 自动合并到 \`${opts.targetBranch}\` 时发生冲突,需解决冲突并完成合并。\n\n` +
|
||||
`### 冲突详情\n${opts.conflictError}\n\n` +
|
||||
`### 步骤(在本任务 worktree 内,已从 ${opts.targetBranch} 检出)\n` +
|
||||
`1. \`git merge ${opts.branch}\` 触发冲突;\n` +
|
||||
`2. 逐个解决冲突文件,保留两边意图(不要丢任一方改动);\n` +
|
||||
`3. \`git add -A && git commit\`(沿用默认合并信息即可);\n` +
|
||||
`4. 本任务进 exec_review 后,「通过并合并」会把已解决的结果干净并入 ${opts.targetBranch};\n` +
|
||||
`5. 完成后,原任务 ${orig.id} 可用「仅通过」标记 done(其改动已随本任务并入)。`,
|
||||
);
|
||||
|
||||
// 幂等标记写回原任务 result(保留原有结果字段)
|
||||
if (orig.result) this.setResult(orig.id, { ...orig.result, mergeTaskId: rem.id });
|
||||
this.emit(orig.projectId, rem.id, 'task.updated', { field: 'merge-remediation', forTask: orig.id });
|
||||
return this.getTask(rem.id)!;
|
||||
}
|
||||
|
||||
setResult(taskId: string, result: TaskResult): Task {
|
||||
const row = this.getTaskRow(taskId);
|
||||
if (!row) throw new StoreError(`任务不存在: ${taskId}`);
|
||||
|
||||
Reference in New Issue
Block a user