diff --git a/src/resources/extensions/sf/auto-dispatch.ts b/src/resources/extensions/sf/auto-dispatch.ts index 5144e09eb..4deedb775 100644 --- a/src/resources/extensions/sf/auto-dispatch.ts +++ b/src/resources/extensions/sf/auto-dispatch.ts @@ -22,6 +22,7 @@ import { buildPlanSlicePrompt, buildReactiveExecutePrompt, buildReassessRoadmapPrompt, + buildRefineSlicePrompt, buildReplanSlicePrompt, buildResearchMilestonePrompt, buildResearchSlicePrompt, @@ -60,6 +61,7 @@ import { getMilestone, getMilestoneSlices, getPendingGates, + getSlice, getSliceTasks, isDbAvailable, markAllGatesOmitted, @@ -976,6 +978,43 @@ export const DISPATCH_RULES: DispatchRule[] = [ }; }, }, + { + // ADR-011 progressive planning: when a slice was created as a sketch + // (slices.is_sketch=1) and the phases.progressive_planning preference is + // enabled, dispatch refine-slice instead of plan-slice. The refine unit + // expands the stored sketch_scope into a full plan using prior slice + // summaries as authoritative context. When the preference is off, sketches + // fall through to the normal plan-slice rule below — a graceful downgrade. + name: "planning (sketch + progressive_planning) → refine-slice", + match: async ({ state, mid, midTitle, basePath, prefs }) => { + if (state.phase !== "planning") return null; + if (!state.activeSlice) return null; + if (prefs?.phases?.progressive_planning !== true) return null; + const sid = state.activeSlice.id; + const sTitle = state.activeSlice.title; + let isSketch = false; + try { + const sliceRow = getSlice(mid, sid); + isSketch = sliceRow?.is_sketch === 1; + } catch { + /* DB unavailable or column missing on pre-migration installs — fall through */ + return null; + } + if (!isSketch) return null; + return { + action: "dispatch", + unitType: "refine-slice", + unitId: `${mid}/${sid}`, + prompt: await buildRefineSlicePrompt( + mid, + midTitle, + sid, + sTitle, + basePath, + ), + }; + }, + }, { name: "planning → plan-slice", match: async ({ state, mid, midTitle, basePath }) => {