feat(daemon+store+api): verdict硬闸+执行期锁+conflict优先+schema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+43
-1
@@ -85,14 +85,36 @@ function applyRecord(store: Store, log: IngestLogger, taskId: string, runId: str
|
||||
if (!task) { log.error(`decompose: 任务不存在 ${taskId}`); return; }
|
||||
store.setPlan(taskId, rec.plan || '(无分析说明)');
|
||||
let created = 0;
|
||||
const createdIds: string[] = [];
|
||||
for (const sub of rec.subtasks) {
|
||||
try {
|
||||
store.createTask({ projectId: task.projectId, parentId: taskId, title: sub.title, complexity: sub.complexity });
|
||||
const priority = typeof (sub as { priority?: unknown }).priority === 'number'
|
||||
? (sub as { priority: number }).priority : 1;
|
||||
const newTask = store.createTask({ projectId: task.projectId, parentId: taskId, title: sub.title, complexity: sub.complexity, priority });
|
||||
createdIds.push(newTask.id);
|
||||
created++;
|
||||
} catch (e) {
|
||||
log.error(`decompose: 建子任务「${sub.title}」失败:${(e as Error).message}`);
|
||||
createdIds.push(''); // 占位,保持序号对齐
|
||||
}
|
||||
}
|
||||
|
||||
// 序号 → taskId 映射,写入 deps
|
||||
for (let i = 0; i < rec.subtasks.length; i++) {
|
||||
const sub = rec.subtasks[i] as { deps?: number[] };
|
||||
if (!sub.deps?.length || !createdIds[i]) continue;
|
||||
const depIds = sub.deps
|
||||
.filter((idx) => idx >= 0 && idx < createdIds.length && createdIds[idx])
|
||||
.map((idx) => createdIds[idx]);
|
||||
if (depIds.length > 0) {
|
||||
try {
|
||||
store.patchTask(createdIds[i], { deps: depIds });
|
||||
} catch (e) {
|
||||
log.error(`decompose: 写子任务「${rec.subtasks[i].title}」deps 失败:${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.transition(taskId, 'plan_review', { by: 'ingest', runId, subtasks: created });
|
||||
store.finishRun(runId, 'succeeded', {
|
||||
transcriptRef: rec.transcriptRef ?? undefined,
|
||||
@@ -122,6 +144,26 @@ function applyRecord(store: Store, log: IngestLogger, taskId: string, runId: str
|
||||
securityVerdict: rec.security.verdict,
|
||||
mergeTaskId: null,
|
||||
});
|
||||
|
||||
// verdict 硬闸:任一 reject → 退回重执行,不进 exec_review
|
||||
const codeReject = rec.code.verdict === 'reject';
|
||||
const secReject = rec.security.verdict === 'reject';
|
||||
if (codeReject || secReject) {
|
||||
const reason = [
|
||||
codeReject ? `代码复审拒绝:${rec.code.summary?.slice(0, 200)}` : '',
|
||||
secReject ? `安全审计拒绝:${rec.security.summary?.slice(0, 200)}` : '',
|
||||
].filter(Boolean).join(';');
|
||||
store.finishRun(runId, 'succeeded', {
|
||||
transcriptRef: rec.executor.transcriptRef ?? undefined,
|
||||
claudeSessionId: rec.executor.sessionId ?? undefined,
|
||||
});
|
||||
// 退回重执行(带复审意见)
|
||||
store.failTaskAttempt(taskId, null, `自动复审拒绝,退回重执行:${reason}`);
|
||||
log.info(`task=${taskId} run=${runId} 复审拒绝 → 退回重执行`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 双 approve → 正常流程
|
||||
store.transition(taskId, 'exec_review', { by: 'ingest', runId });
|
||||
store.finishRun(runId, 'succeeded', {
|
||||
transcriptRef: rec.executor.transcriptRef ?? undefined,
|
||||
|
||||
@@ -137,7 +137,16 @@ export function createOrchestrator(store: Store, log: OrchestratorLogger, deps:
|
||||
if (t.nextEligibleAt && nowMs < Date.parse(t.nextEligibleAt)) return false; // 退避冷却中
|
||||
return t.deps.every((dep) => byId.get(dep)?.status === 'done');
|
||||
});
|
||||
return rankByScore(candidates, tasks);
|
||||
const isMergeRemediation = (t: Task): boolean =>
|
||||
t.priority === 0 && t.title.startsWith('合并冲突待解决');
|
||||
const ranked = rankByScore(candidates, tasks);
|
||||
ranked.sort((a, b) => {
|
||||
const aMR = isMergeRemediation(a.task) ? 1 : 0;
|
||||
const bMR = isMergeRemediation(b.task) ? 1 : 0;
|
||||
if (bMR !== aMR) return bMR - aMR;
|
||||
return b.score - a.score;
|
||||
});
|
||||
return ranked;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user