diff --git a/src/resources/extensions/sf/auto-dispatch.ts b/src/resources/extensions/sf/auto-dispatch.ts index 4deedb775..630b0af18 100644 --- a/src/resources/extensions/sf/auto-dispatch.ts +++ b/src/resources/extensions/sf/auto-dispatch.ts @@ -15,6 +15,8 @@ import { buildCompleteMilestonePrompt, buildCompleteSlicePrompt, buildDiscussMilestonePrompt, + buildDiscussProjectPrompt, + buildDiscussRequirementsPrompt, buildExecuteTaskPrompt, buildGateEvaluatePrompt, buildParallelResearchSlicesPrompt, @@ -24,15 +26,18 @@ import { buildReassessRoadmapPrompt, buildRefineSlicePrompt, buildReplanSlicePrompt, + buildResearchProjectPrompt, buildResearchMilestonePrompt, buildResearchSlicePrompt, buildRewriteDocsPrompt, buildRunUatPrompt, buildValidateMilestonePrompt, + buildWorkflowPreferencesPrompt, checkNeedsReassessment, checkNeedsRunUat, } from "./auto-prompts.js"; import { hasImplementationArtifacts } from "./auto-recovery.js"; +import { resolveDeepProjectSetupState } from "./deep-project-setup-policy.js"; import { getExecuteTaskInstructionConflict, skipExecuteTaskForInstructionConflict, @@ -738,6 +743,75 @@ export const DISPATCH_RULES: DispatchRule[] = [ }; }, }, + { + // Deep planning mode: the project-level setup gate runs before any + // milestone-level discuss/research/plan when planning_depth === "deep". + // resolveDeepProjectSetupState walks the staged-prerequisite chain + // (workflow-prefs → project → requirements → research-decision auto- + // resolved → project-research) and returns the next pending stage. Each + // stage's prompt writes its expected artifact, the gate flips the next + // time, and the milestone-level rules below take over when status = + // "complete" or planning_depth !== "deep". + name: "deep planning gate → project-level units", + match: async ({ state, basePath, prefs }) => { + if (prefs?.planning_depth !== "deep") return null; + if ( + state.phase !== "pre-planning" && + state.phase !== "needs-discussion" + ) { + return null; + } + let gate; + try { + gate = resolveDeepProjectSetupState(prefs, basePath); + } catch { + return null; // helper failure → fall through to legacy rules + } + if (gate.status === "not-applicable" || gate.status === "complete") { + return null; + } + if (gate.status === "blocked") { + return { + action: "stop", + reason: gate.reason ?? "Deep planning gate is blocked.", + level: "warning", + }; + } + // status === "pending" + switch (gate.stage) { + case "workflow-preferences": + return { + action: "dispatch", + unitType: "workflow-preferences", + unitId: "WORKFLOW-PREFERENCES", + prompt: await buildWorkflowPreferencesPrompt(basePath), + }; + case "project": + return { + action: "dispatch", + unitType: "discuss-project", + unitId: "PROJECT", + prompt: await buildDiscussProjectPrompt(basePath), + }; + case "requirements": + return { + action: "dispatch", + unitType: "discuss-requirements", + unitId: "REQUIREMENTS", + prompt: await buildDiscussRequirementsPrompt(basePath), + }; + case "project-research": + return { + action: "dispatch", + unitType: "research-project", + unitId: "RESEARCH-PROJECT", + prompt: await buildResearchProjectPrompt(basePath), + }; + default: + return null; + } + }, + }, { name: "needs-discussion → discuss-milestone", match: async ({ state, mid, midTitle, basePath }) => {