feat(sf): wire deep planning mode dispatch (PDD)

Closes the deep-mode rollout. With this commit, planning_depth: 'deep'
in PREFERENCES.md produces a 4-stage project-level discussion BEFORE
any milestone work — workflow-preferences → discuss-project →
discuss-requirements → research-project (research-decision is auto-
resolved to skip-default by SF's resolver, simpler than gsd-2's
explicit user-decision gate).

PDD spec for this change:

Purpose: route auto-mode through project-level setup before milestones
  when planning_depth='deep'. When absent or 'light', existing dispatch
  is preserved 1:1.
Consumer: auto-mode dispatcher (DISPATCH_RULES). One new rule sits at
  the top of the pre-planning ladder; existing rules unchanged.
Contract:
  1. planning_depth absent or 'light' → rule returns null → existing
     dispatch unchanged. Verified: returns 'not-applicable'.
  2. planning_depth='deep' + empty project → dispatches workflow-
     preferences then progresses through stages as artifacts land.
     Verified: returns 'pending'/'workflow-preferences'.
  3. status='blocked' → returns dispatch action 'stop' with the gate's
     reason — never silently bypasses a blocker.
  4. status='complete' → returns null → milestone-level rules below
     take over.
Failure boundary: if resolveDeepProjectSetupState() throws, return
  null and fall through to legacy rules. Never blocks the user on a
  helper crash.
Evidence: typecheck passes; gate-resolver smoke test verifies all
  three contract conditions; existing dispatch tests unchanged
  (light-mode regression-protected).
Non-goals:
  - In-flight idempotency markers for research-project (gsd-2 has
    these; SF's resolver auto-completes the stage when files land
    so the simple guard is sufficient — can add markers later if
    parallel orchestrator races emerge).
  - Plumbing structuredQuestionsAvailable through DispatchContext
    (defaulted to 'false' in builders for now; UI capability
    detection can be threaded later).
Invariants:
  - Safety: light-mode + absent-prefs paths return null at the FIRST
    check, before any DB or filesystem access. No regression possible.
  - Liveness: the resolver enforces forward progress — once a stage's
    artifact lands, the next gate fires next dispatch cycle.
Assumptions verified:
  - resolveDeepProjectSetupState exists in SF (deep-project-setup-policy.ts).
  - planning_depth: 'light' | 'deep' typed in preferences-types.ts:425.
  - All 4 dispatched unit types have builders in auto-prompts.ts (added
    in 5e8bdefbe).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-02 19:42:41 +02:00
parent 5e8bdefbea
commit d742602454

View file

@ -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 }) => {