diff --git a/src/daemon/orchestrator.ts b/src/daemon/orchestrator.ts index 4408886..d9cae82 100644 --- a/src/daemon/orchestrator.ts +++ b/src/daemon/orchestrator.ts @@ -133,6 +133,11 @@ export function createOrchestrator(store: Store, log: OrchestratorLogger, deps: if (!CLAIMABLE.has(t.status)) return false; if (inflight.has(t.id)) return false; // 已有在途 run(executor/planner) if (parents.has(t.id)) return false; // 非叶子(容器)跳过 + // 父任务拆解待审批 / 拆解中 → 冻结子任务,避免拆解被驳回时子任务已白跑(仅冻结这两态,不影响 sync 等流程) + if (t.parentId) { + const ps = byId.get(t.parentId)?.status; + if (ps === 'plan_review' || ps === 'analyzing') return false; + } if (easyOnly && t.complexity !== 'easy') return false; if (t.nextEligibleAt && nowMs < Date.parse(t.nextEligibleAt)) return false; // 退避冷却中 return t.deps.every((dep) => byId.get(dep)?.status === 'done'); diff --git a/test/orchestrator.test.ts b/test/orchestrator.test.ts index 4530090..d2ad749 100644 --- a/test/orchestrator.test.ts +++ b/test/orchestrator.test.ts @@ -415,6 +415,23 @@ test('claimable 纳入 analyzing:hard 任务派 planner-decompose', () => { store.close(); }); +test('子任务在父任务 plan_review 期间不可领取,拆解批准(decomposed)后放行', () => { + const { store, projectId } = setup('auto-approved'); + const parent = store.createTask({ projectId, title: 'epic', complexity: 'hard' }); // → analyzing + const child = store.createTask({ projectId, parentId: parent.id, title: 'sub', complexity: 'easy' }); // → ready + store.transition(parent.id, 'plan_review', { by: 'test' }); // 模拟拆解完成待审 + const state = freshState(); + const { deps } = mockDeps(state); + const orch = createOrchestrator(store, noopLog, deps); + orch.claimTick(); + assert.equal(state.spawned.length, 0, '父任务 plan_review(拆解待审)期间,子任务不应被领取'); + store.transition(parent.id, 'decomposed', { by: 'test' }); // 批准拆解 + orch.claimTick(); + assert.equal(state.spawned.length, 1, '拆解批准后子任务放行可领取'); + assert.equal(store.listRuns(child.id).length, 1, '被领取的是该子任务'); + store.close(); +}); + test('auto-easy 不做规划:medium(speccing) 不被领取', () => { const { store, projectId } = setup('auto-easy'); const t = store.createTask({ projectId, title: 'feat', complexity: 'medium' }); // → speccing