gsd-pi 2.82.0-dev.dfbc5f58f → 2.82.0-dev.e7a7f1ed5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +1 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +73 -30
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +66 -1
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -16
  8. package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
  9. package/dist/resources/extensions/gsd/auto-start.js +3 -3
  10. package/dist/resources/extensions/gsd/auto-verification.js +17 -4
  11. package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
  12. package/dist/resources/extensions/gsd/auto.js +7 -2
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +27 -6
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -2
  15. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
  16. package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
  17. package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
  18. package/dist/resources/extensions/gsd/doctor-git-checks.js +46 -1
  19. package/dist/resources/extensions/gsd/git-service.js +6 -2
  20. package/dist/resources/extensions/gsd/gsd-db.js +20 -6
  21. package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
  22. package/dist/resources/extensions/gsd/guided-flow.js +95 -116
  23. package/dist/resources/extensions/gsd/guided-unit-context.js +23 -0
  24. package/dist/resources/extensions/gsd/migration-auto-check.js +12 -17
  25. package/dist/resources/extensions/gsd/pending-auto-start.js +52 -0
  26. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  28. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -8
  29. package/dist/resources/extensions/gsd/prompts/discuss.md +9 -9
  30. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
  31. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
  32. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/queue.md +4 -4
  34. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  36. package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
  37. package/dist/resources/extensions/gsd/smart-entry-routing.js +36 -0
  38. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +9 -14
  39. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +19 -24
  40. package/dist/resources/extensions/gsd/status-guards.js +7 -0
  41. package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
  42. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  43. package/dist/web/standalone/.next/BUILD_ID +1 -1
  44. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  45. package/dist/web/standalone/.next/build-manifest.json +2 -2
  46. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  47. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/index.html +1 -1
  65. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  72. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  74. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  75. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  76. package/package.json +1 -1
  77. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  78. package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
  79. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  80. package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
  81. package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
  82. package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
  83. package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
  84. package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
  85. package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
  86. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +24 -6
  89. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  90. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +23 -7
  91. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  92. package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
  93. package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
  94. package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
  95. package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
  96. package/packages/pi-tui/dist/terminal.d.ts +2 -0
  97. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  98. package/packages/pi-tui/dist/terminal.js +12 -0
  99. package/packages/pi-tui/dist/terminal.js.map +1 -1
  100. package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
  101. package/packages/pi-tui/src/terminal.ts +11 -0
  102. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  103. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
  104. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
  105. package/src/resources/extensions/gsd/auto/phases.ts +83 -37
  106. package/src/resources/extensions/gsd/auto-dashboard.ts +72 -1
  107. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
  108. package/src/resources/extensions/gsd/auto-dispatch.ts +10 -16
  109. package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
  110. package/src/resources/extensions/gsd/auto-start.ts +2 -3
  111. package/src/resources/extensions/gsd/auto-verification.ts +22 -2
  112. package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
  113. package/src/resources/extensions/gsd/auto.ts +8 -2
  114. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +36 -6
  115. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -2
  116. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
  117. package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
  118. package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
  119. package/src/resources/extensions/gsd/doctor-git-checks.ts +45 -1
  120. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  121. package/src/resources/extensions/gsd/git-service.ts +6 -3
  122. package/src/resources/extensions/gsd/gsd-db.ts +18 -6
  123. package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
  124. package/src/resources/extensions/gsd/guided-flow.ts +128 -133
  125. package/src/resources/extensions/gsd/guided-unit-context.ts +30 -0
  126. package/src/resources/extensions/gsd/migration-auto-check.ts +15 -23
  127. package/src/resources/extensions/gsd/pending-auto-start.ts +79 -0
  128. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  129. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  130. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -8
  131. package/src/resources/extensions/gsd/prompts/discuss.md +9 -9
  132. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
  133. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
  134. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  135. package/src/resources/extensions/gsd/prompts/queue.md +4 -4
  136. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  137. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  138. package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
  139. package/src/resources/extensions/gsd/smart-entry-routing.ts +77 -0
  140. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +12 -15
  141. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +17 -25
  142. package/src/resources/extensions/gsd/status-guards.ts +8 -0
  143. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +71 -0
  144. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
  145. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +29 -1
  146. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +53 -2
  147. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
  148. package/src/resources/extensions/gsd/tests/auto-stop-notification.test.ts +20 -0
  149. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
  150. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +11 -2
  151. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +5 -9
  152. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
  153. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  154. package/src/resources/extensions/gsd/tests/db-authority-regression.test.ts +208 -0
  155. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
  156. package/src/resources/extensions/gsd/tests/doctor-empty-worktree.test.ts +65 -0
  157. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
  158. package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +2 -0
  159. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +106 -0
  160. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +59 -11
  161. package/src/resources/extensions/gsd/tests/guided-tool-contract.test.ts +65 -0
  162. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +7 -7
  163. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
  164. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
  165. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
  166. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +26 -18
  167. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +29 -5
  168. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +2 -0
  169. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
  170. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +37 -1
  171. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
  172. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
  173. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
  174. package/src/resources/extensions/gsd/tests/smart-entry-routing.test.ts +113 -0
  175. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +22 -1
  176. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +119 -23
  177. package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
  178. package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
  179. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
  180. package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
  181. /package/dist/web/standalone/.next/static/{q0WYuDVbHeFFYbdd-fei2 → 4dSwdrs__8NwCZggxP9KF}/_buildManifest.js +0 -0
  182. /package/dist/web/standalone/.next/static/{q0WYuDVbHeFFYbdd-fei2 → 4dSwdrs__8NwCZggxP9KF}/_ssgManifest.js +0 -0
@@ -26,10 +26,11 @@ import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.j
26
26
  import { nativeAddAll, nativeCommit, nativeHasCommittedHead, nativeIsRepo, nativeInit } from "./native-git-bridge.js";
27
27
  import { isInheritedRepo } from "./repo-identity.js";
28
28
  import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
29
- import { loadEffectiveGSDPreferences } from "./preferences.js";
29
+ import { getIsolationMode, loadEffectiveGSDPreferences } from "./preferences.js";
30
30
  import { resolveUokFlags } from "./uok/flags.js";
31
31
  import { ensurePlanV2Graph, isMissingFinalizedContextResult } from "./uok/plan-v2.js";
32
32
  import { detectProjectState, hasGsdBootstrapArtifacts } from "./detection.js";
33
+ import { isFutureMilestoneStatus } from "./status-guards.js";
33
34
  import { showProjectInit, offerMigration } from "./init-wizard.js";
34
35
  import { validateDirectory } from "./validate-directory.js";
35
36
  import { showConfirm } from "../shared/tui.js";
@@ -43,8 +44,10 @@ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
43
44
  import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForGuidedUnit, supportsStructuredQuestions, } from "./workflow-mcp.js";
44
45
  import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, } from "./preparation.js";
45
46
  import { verifyExpectedArtifact } from "./auto-recovery.js";
46
- import { createWorkspace, scopeMilestone } from "./workspace.js";
47
47
  import { getPendingGate, extractDepthVerificationMilestoneId } from "./bootstrap/write-gate.js";
48
+ import { _getPendingAutoStart, deletePendingAutoStart, hasPendingAutoStart, setPendingAutoStart, } from "./pending-auto-start.js";
49
+ import { clearGuidedUnitContext, setGuidedUnitContext } from "./guided-unit-context.js";
50
+ export { _getPendingAutoStart, clearPendingAutoStart, getDiscussionMilestoneId, setPendingAutoStart, } from "./pending-auto-start.js";
48
51
  export function shouldSkipGitBootstrapAfterInit(result) {
49
52
  return result.gitEnabled === false;
50
53
  }
@@ -56,6 +59,8 @@ import { deleteRuntimeKv } from "./db/runtime-kv.js";
56
59
  import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
57
60
  import { buildWorkflowDispatchContent } from "./workflow-protocol.js";
58
61
  import { isFullGsdToolSurfaceRequested, restoreGsdWorkflowTools, scopeGsdWorkflowToolsForDispatch } from "./bootstrap/register-hooks.js";
62
+ import { resolveActiveTaskChoiceRoute, } from "./smart-entry-routing.js";
63
+ export { resolveGuidedExecuteLaunchMode } from "./smart-entry-routing.js";
59
64
  function scheduleAutoStartAfterIdle(ctx, pi, basePath, verboseMode, options, launch = startAutoDetached) {
60
65
  const waitForIdle = typeof ctx.waitForIdle === "function"
61
66
  ? ctx.waitForIdle.bind(ctx)
@@ -157,7 +162,6 @@ const MAX_PLAN_BLOCKED_RECOVERIES = 3;
157
162
  // to emit. Accepts any M-prefixed milestone ID (three digits + optional
158
163
  // suffix) with optional trailing punctuation.
159
164
  const READY_PHRASE_RE = /\bMilestone\s+M\d{3}[A-Z0-9-]*\s+ready\.?/i;
160
- const pendingAutoStartMap = new Map();
161
165
  const pendingDeepProjectSetupMap = new Map();
162
166
  const USER_DRIVEN_DEEP_SETUP_UNITS = new Set([
163
167
  "discuss-project",
@@ -183,18 +187,6 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
183
187
 
184
188
  - Do NOT call \`ask_user_questions\`, \`AskUserQuestion\`, or ToolSearch to discover user-input tools.
185
189
  - Ask one focused round, then stop and wait for the user's normal chat response.`;
186
- /**
187
- * Backward-compat bridge: returns a mutable reference to the entry matching
188
- * basePath, or the sole entry when only one session exists.
189
- * Exported for testing — internal use only in production code.
190
- */
191
- export function _getPendingAutoStart(basePath) {
192
- if (basePath)
193
- return pendingAutoStartMap.get(basePath) ?? null;
194
- if (pendingAutoStartMap.size === 1)
195
- return pendingAutoStartMap.values().next().value;
196
- return null;
197
- }
198
190
  function hasNestedFileOrSymlink(dir) {
199
191
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
200
192
  if (entry.isFile() || entry.isSymbolicLink())
@@ -228,28 +220,6 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath, entries) {
228
220
  }
229
221
  return remaining;
230
222
  }
231
- /**
232
- * Store pending auto-start state for a project.
233
- * Exported for testing (#2985).
234
- */
235
- export function setPendingAutoStart(basePath, entry) {
236
- const ws = createWorkspace(entry.basePath);
237
- const scope = scopeMilestone(ws, entry.milestoneId);
238
- pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope });
239
- }
240
- /**
241
- * Clear pending auto-start state.
242
- * If basePath is given, clears only that project. Otherwise clears all.
243
- * Exported for testing (#2985).
244
- */
245
- export function clearPendingAutoStart(basePath) {
246
- if (basePath) {
247
- pendingAutoStartMap.delete(basePath);
248
- }
249
- else {
250
- pendingAutoStartMap.clear();
251
- }
252
- }
253
223
  export function clearPendingDeepProjectSetup(basePath) {
254
224
  if (basePath) {
255
225
  pendingDeepProjectSetupMap.delete(basePath);
@@ -258,22 +228,6 @@ export function clearPendingDeepProjectSetup(basePath) {
258
228
  pendingDeepProjectSetupMap.clear();
259
229
  }
260
230
  }
261
- /**
262
- * Returns the milestoneId being discussed for the given project.
263
- * When basePath is omitted and only one session is active, returns that
264
- * session's milestoneId for backward compatibility. Returns null when
265
- * multiple sessions exist and basePath is not specified (#2985 Bug 4).
266
- */
267
- export function getDiscussionMilestoneId(basePath) {
268
- if (basePath) {
269
- return pendingAutoStartMap.get(basePath)?.milestoneId ?? null;
270
- }
271
- // Backward compat: return the sole entry's milestoneId, or null if ambiguous
272
- if (pendingAutoStartMap.size === 1) {
273
- return pendingAutoStartMap.values().next().value.milestoneId;
274
- }
275
- return null;
276
- }
277
231
  function _getPendingDeepProjectSetup(basePath) {
278
232
  if (basePath)
279
233
  return pendingDeepProjectSetupMap.get(basePath) ?? null;
@@ -399,12 +353,12 @@ async function dispatchNextDeepProjectSetupStage(entry) {
399
353
  entry.currentUnitType = result.unitType;
400
354
  entry.currentUnitId = result.unitId;
401
355
  entry.createdAt = Date.now();
402
- await dispatchWorkflow(entry.pi, `${result.prompt}\n\n${FOREGROUND_DEEP_SETUP_QUESTION_POLICY}`, "gsd-run", entry.ctx, result.unitType);
356
+ await dispatchWorkflow(entry.pi, `${result.prompt}\n\n${FOREGROUND_DEEP_SETUP_QUESTION_POLICY}`, "gsd-run", entry.ctx, result.unitType, { basePath: entry.basePath });
403
357
  return true;
404
358
  }
405
359
  /** Called from agent_end to check if auto-mode should start after discuss */
406
- export function checkAutoStartAfterDiscuss() {
407
- const entry = _getPendingAutoStart();
360
+ export function checkAutoStartAfterDiscuss(lookupBasePath) {
361
+ const entry = _getPendingAutoStart(lookupBasePath);
408
362
  if (!entry)
409
363
  return false;
410
364
  const { ctx, pi, basePath, milestoneId, step } = entry;
@@ -569,7 +523,7 @@ export function checkAutoStartAfterDiscuss() {
569
523
  return false;
570
524
  }
571
525
  }
572
- pendingAutoStartMap.delete(basePath);
526
+ deletePendingAutoStart(basePath);
573
527
  ctx.ui.notify(`Milestone ${milestoneId} ready.`, "success");
574
528
  scheduleAutoStartAfterIdle(ctx, pi, basePath, false, { step });
575
529
  return true;
@@ -641,8 +595,8 @@ function hasToolUse(msg) {
641
595
  * Returns true when a nudge (or give-up) was emitted, signaling the caller to
642
596
  * skip `resolveAgentEnd`.
643
597
  */
644
- export function maybeHandleReadyPhraseWithoutFiles(event) {
645
- const entry = _getPendingAutoStart();
598
+ export function maybeHandleReadyPhraseWithoutFiles(event, lookupBasePath) {
599
+ const entry = _getPendingAutoStart(lookupBasePath);
646
600
  if (!entry)
647
601
  return false;
648
602
  const { ctx, pi, basePath, milestoneId } = entry;
@@ -684,7 +638,7 @@ export function maybeHandleReadyPhraseWithoutFiles(event) {
684
638
  if (entry.readyRejectCount > MAX_READY_REJECTS) {
685
639
  // Give up: clear state and tell the user to re-run /gsd. Avoids an
686
640
  // infinite nudge loop when the LLM never produces the writes.
687
- pendingAutoStartMap.delete(basePath);
641
+ deletePendingAutoStart(basePath);
688
642
  ctx.ui.notify(`Milestone ${milestoneId}: LLM signaled "ready" ${entry.readyRejectCount} times without writing files. ` +
689
643
  `Stopping auto-nudge. Run /gsd to try again.`, "error");
690
644
  return true;
@@ -747,10 +701,10 @@ export function resetEmptyTurnCounter(basePath) {
747
701
  else
748
702
  emptyTurnCounterByBase.clear();
749
703
  }
750
- export function maybeHandleEmptyIntentTurn(event, isAuto) {
704
+ export function maybeHandleEmptyIntentTurn(event, isAuto, lookupBasePath) {
751
705
  // Gate: only fire when there is system-driven work in flight. Interactive
752
706
  // /gsd discuss (user-driven) produces legitimate text-only turns.
753
- if (!isAuto && pendingAutoStartMap.size === 0)
707
+ if (!isAuto && !hasPendingAutoStart(lookupBasePath))
754
708
  return false;
755
709
  const lastMsg = event.messages[event.messages.length - 1];
756
710
  if (!lastMsg)
@@ -778,7 +732,7 @@ export function maybeHandleEmptyIntentTurn(event, isAuto) {
778
732
  return false;
779
733
  // Resolve the target basePath + pi for injection. Prefer the pending
780
734
  // autostart entry (discuss flow); otherwise we cannot inject.
781
- const entry = _getPendingAutoStart();
735
+ const entry = _getPendingAutoStart(lookupBasePath);
782
736
  if (!entry)
783
737
  return false;
784
738
  const { ctx, pi, basePath } = entry;
@@ -817,7 +771,9 @@ function parseMilestoneSequenceFromProject(content) {
817
771
  }
818
772
  return ids;
819
773
  }
820
- // ─── Helpers ──────────────────────────────────────────────────────────────────
774
+ export function resolveGuidedDispatchProjectRoot(basePath) {
775
+ return basePath ?? process.cwd();
776
+ }
821
777
  /**
822
778
  * Read GSD-WORKFLOW.md and dispatch it to the LLM with a contextual note.
823
779
  * This is the only way the wizard triggers work — everything else is the LLM's job.
@@ -827,12 +783,17 @@ function parseMilestoneSequenceFromProject(content) {
827
783
  * dispatching. This ensures guided-flow dispatches respect the same
828
784
  * per-phase model preferences that auto-mode uses.
829
785
  */
830
- async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType) {
786
+ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType, options) {
787
+ const resolvedOptions = options ?? {};
788
+ const projectRoot = resolveGuidedDispatchProjectRoot(resolvedOptions.basePath);
789
+ const loadPreferences = resolvedOptions.deps?.loadPreferences ?? loadEffectiveGSDPreferences;
790
+ const selectModel = resolvedOptions.deps?.selectModel ?? selectAndApplyModel;
791
+ const getTransportSupportError = resolvedOptions.deps?.getTransportSupportError ?? getWorkflowTransportSupportError;
831
792
  // Route through the dynamic routing pipeline (complexity classification,
832
793
  // tier downgrade, fallback chains) — same path as auto-mode dispatches (#2958).
833
794
  if (ctx && unitType) {
834
- const prefs = loadEffectiveGSDPreferences()?.preferences;
835
- const result = await selectAndApplyModel(ctx, pi, unitType, /* unitId */ "", /* basePath */ process.cwd(), prefs, /* verbose */ false, /* autoModeStartModel */ null,
795
+ const prefs = loadPreferences(projectRoot)?.preferences;
796
+ const result = await selectModel(ctx, pi, unitType, /* unitId */ "", projectRoot, prefs, /* verbose */ false, /* autoModeStartModel */ null,
836
797
  /* retryContext */ undefined, /* isAutoMode */ false);
837
798
  if (result.appliedModel) {
838
799
  debugLog("guided-flow-model-applied", {
@@ -841,8 +802,8 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
841
802
  routing: result.routing,
842
803
  });
843
804
  }
844
- const compatibilityError = getWorkflowTransportSupportError(result.appliedModel?.provider ?? ctx.model?.provider, getRequiredWorkflowToolsForGuidedUnit(unitType), {
845
- projectRoot: process.cwd(),
805
+ const compatibilityError = getTransportSupportError(result.appliedModel?.provider ?? ctx.model?.provider, getRequiredWorkflowToolsForGuidedUnit(unitType), {
806
+ projectRoot,
846
807
  surface: "guided flow",
847
808
  unitType,
848
809
  authMode: result.appliedModel?.provider
@@ -851,6 +812,7 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
851
812
  ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
852
813
  : undefined,
853
814
  baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
815
+ activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
854
816
  });
855
817
  if (compatibilityError) {
856
818
  ctx.ui.notify(compatibilityError, "error");
@@ -894,11 +856,19 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
894
856
  }
895
857
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(gsdHome(), "agent", "GSD-WORKFLOW.md");
896
858
  const workflow = readFileSync(workflowPath, "utf-8");
897
- pi.sendMessage({
898
- customType,
899
- content: buildWorkflowDispatchContent({ workflow, workflowPath, task: note }),
900
- display: false,
901
- }, { triggerTurn: true });
859
+ if (unitType)
860
+ setGuidedUnitContext(projectRoot, unitType);
861
+ try {
862
+ pi.sendMessage({
863
+ customType,
864
+ content: buildWorkflowDispatchContent({ workflow, workflowPath, task: note }),
865
+ display: false,
866
+ }, { triggerTurn: true });
867
+ }
868
+ catch (err) {
869
+ clearGuidedUnitContext(projectRoot);
870
+ throw err;
871
+ }
902
872
  }
903
873
  finally {
904
874
  // Restore full tool set after the message is queued. The LLM turn has
@@ -1086,7 +1056,7 @@ export async function showHeadlessMilestoneCreation(ctx, pi, basePath, seedConte
1086
1056
  // model/tool routing to skip discuss-flow tool scoping and
1087
1057
  // `checkAutoStartAfterDiscuss` guardrails that rely on the
1088
1058
  // "discuss-"-prefixed unitType.
1089
- await dispatchWorkflow(pi, prompt, "gsd-run", ctx, "discuss-milestone");
1059
+ await dispatchWorkflow(pi, prompt, "gsd-run", ctx, "discuss-milestone", { basePath });
1090
1060
  }
1091
1061
  // ─── Discuss Flow ─────────────────────────────────────────────────────────────
1092
1062
  /**
@@ -1196,7 +1166,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1196
1166
  // No active milestone (or corrupted milestone with undefined id) —
1197
1167
  // check for pending milestones to discuss instead
1198
1168
  if (!state.activeMilestone?.id) {
1199
- const pendingMilestones = state.registry.filter(m => m.status === "pending");
1169
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
1200
1170
  if (pendingMilestones.length === 0) {
1201
1171
  ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
1202
1172
  return;
@@ -1247,7 +1217,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1247
1217
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1248
1218
  : basePrompt;
1249
1219
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1250
- await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1220
+ await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone", { basePath });
1251
1221
  }
1252
1222
  else if (choice === "discuss_fresh") {
1253
1223
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
@@ -1258,7 +1228,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1258
1228
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1259
1229
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
1260
1230
  fastPathInstruction: "",
1261
- }), "gsd-discuss", ctx, "discuss-milestone");
1231
+ }), "gsd-discuss", ctx, "discuss-milestone", { basePath });
1262
1232
  }
1263
1233
  else if (choice === "skip_milestone") {
1264
1234
  const { ensureDbOpen } = await import("./bootstrap/dynamic-tools.js");
@@ -1267,7 +1237,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1267
1237
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1268
1238
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1269
1239
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
1270
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1240
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone", { basePath });
1271
1241
  }
1272
1242
  return;
1273
1243
  }
@@ -1301,7 +1271,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1301
1271
  const pendingSlices = normSlices.filter(s => !s.done);
1302
1272
  if (pendingSlices.length === 0) {
1303
1273
  // All slices complete — but queued milestones may still need discussion (#3150)
1304
- const pendingMilestones = state.registry.filter(m => m.status === "pending");
1274
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
1305
1275
  if (pendingMilestones.length > 0) {
1306
1276
  await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
1307
1277
  return;
@@ -1322,7 +1292,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1322
1292
  // If all pending slices are discussed, check for queued milestones before exiting (#3150)
1323
1293
  const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
1324
1294
  if (allDiscussed) {
1325
- const pendingMilestones = state.registry.filter(m => m.status === "pending");
1295
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
1326
1296
  if (pendingMilestones.length > 0) {
1327
1297
  await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
1328
1298
  return;
@@ -1353,7 +1323,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1353
1323
  };
1354
1324
  });
1355
1325
  // Offer access to queued milestones when any exist
1356
- const pendingMilestones = state.registry.filter(m => m.status === "pending");
1326
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
1357
1327
  if (pendingMilestones.length > 0) {
1358
1328
  actions.push({
1359
1329
  id: "discuss_queued_milestone",
@@ -1399,7 +1369,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1399
1369
  }
1400
1370
  const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
1401
1371
  const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
1402
- await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
1372
+ await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice", { basePath });
1403
1373
  // Wait for the discuss session to finish, then loop back to the picker
1404
1374
  await ctx.waitForIdle();
1405
1375
  invalidateAllCaches();
@@ -1417,10 +1387,11 @@ async function showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones)
1417
1387
  const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
1418
1388
  const hasDraft = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
1419
1389
  const contextStatus = hasContext ? "context ✓" : hasDraft ? "draft context" : "no context yet";
1390
+ const statusLabel = m.status === "planned" ? "planned" : "queued";
1420
1391
  return {
1421
1392
  id: m.id,
1422
1393
  label: `${m.id}: ${m.title}`,
1423
- description: `[queued] · ${contextStatus}`,
1394
+ description: `[${statusLabel}] · ${contextStatus}`,
1424
1395
  recommended: i === 0,
1425
1396
  };
1426
1397
  });
@@ -1501,7 +1472,7 @@ async function dispatchDiscussForMilestone(ctx, pi, basePath, mid, milestoneTitl
1501
1472
  const prompt = draftContent
1502
1473
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1503
1474
  : basePrompt;
1504
- await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-milestone");
1475
+ await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-milestone", { basePath });
1505
1476
  }
1506
1477
  // ─── Smart Entry Point ────────────────────────────────────────────────────────
1507
1478
  /**
@@ -1627,7 +1598,7 @@ async function handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneT
1627
1598
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1628
1599
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1629
1600
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1630
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1601
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone", { basePath });
1631
1602
  return true;
1632
1603
  }
1633
1604
  // "back" or null
@@ -1758,16 +1729,17 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1758
1729
  }
1759
1730
  if (interrupted.classification !== "recoverable") {
1760
1731
  try {
1761
- const { autoImportMarkdownHierarchyIfDbMismatch } = await import("./migration-auto-check.js");
1762
- const result = await autoImportMarkdownHierarchyIfDbMismatch(basePath);
1763
- if (result.action === "imported") {
1764
- ctx.ui.notify(`Recovered migrated planning state into gsd.db (${result.reason}): ${result.afterDb.milestones} milestone(s), ${result.afterDb.slices} slice(s), ${result.afterDb.tasks} task(s).`, "info");
1732
+ const { checkMarkdownHierarchyAgainstDb } = await import("./migration-auto-check.js");
1733
+ const result = await checkMarkdownHierarchyAgainstDb(basePath);
1734
+ if (result.action === "recovery-required") {
1735
+ ctx.ui.notify(result.message ??
1736
+ `Markdown planning artifacts do not match the authoritative DB. Run \`${result.recoveryCommand ?? "gsd recover"}\` to import markdown explicitly.`, "warning");
1765
1737
  }
1766
1738
  }
1767
1739
  catch (err) {
1768
1740
  const message = err instanceof Error ? err.message : String(err);
1769
- ctx.ui.notify(`GSD could not auto-import existing planning state into gsd.db: ${message}`, "warning");
1770
- logWarning("guided", `planning state auto-import failed: ${message}`, { file: "guided-flow.ts" });
1741
+ ctx.ui.notify(`GSD could not compare markdown planning artifacts with gsd.db: ${message}`, "warning");
1742
+ logWarning("guided", `planning state DB/markdown comparison failed: ${message}`, { file: "guided-flow.ts" });
1771
1743
  }
1772
1744
  }
1773
1745
  // Always derive from the project root — the assessment may have derived
@@ -1805,17 +1777,17 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1805
1777
  // Both /gsd and /gsd auto reach this branch when no milestone exists yet.
1806
1778
  // Without this guard, every subsequent /gsd call overwrites the pending auto-start
1807
1779
  // and fires another dispatchWorkflow, resetting the conversation mid-interview.
1808
- if (pendingAutoStartMap.has(basePath)) {
1780
+ if (hasPendingAutoStart(basePath)) {
1809
1781
  // #3274: If /clear interrupted the discussion, the pending entry is stale.
1810
1782
  // Detect staleness: no manifest, no milestone CONTEXT artifact, AND entry is older than
1811
1783
  // 30s (avoids race between .set() and LLM writing first artifact).
1812
- const entry = pendingAutoStartMap.get(basePath);
1784
+ const entry = _getPendingAutoStart(basePath);
1813
1785
  const ageMs = Date.now() - (entry.createdAt || 0);
1814
1786
  const manifestExists = existsSync(join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json"));
1815
1787
  const milestoneHasContext = !!resolveMilestoneFile(basePath, entry.milestoneId, "CONTEXT");
1816
1788
  if (!manifestExists && !milestoneHasContext && ageMs > 30_000) {
1817
1789
  // Stale entry from an interrupted discussion — clear and continue
1818
- pendingAutoStartMap.delete(basePath);
1790
+ deletePendingAutoStart(basePath);
1819
1791
  }
1820
1792
  else {
1821
1793
  ctx.ui.notify("Discussion already in progress — answer the question above to continue.", "info");
@@ -1849,7 +1821,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1849
1821
  // First ever — skip wizard, just ask directly
1850
1822
  ctx.ui.setStatus("gsd-step", "New Milestone · answer the questions above to plan");
1851
1823
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1852
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`, basePath), "gsd-run", ctx, "discuss-milestone");
1824
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`, basePath), "gsd-run", ctx, "discuss-milestone", { basePath });
1853
1825
  }
1854
1826
  else {
1855
1827
  const choice = await showNextAction(ctx, {
@@ -1876,7 +1848,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1876
1848
  else if (choice === "new_milestone") {
1877
1849
  ctx.ui.setStatus("gsd-step", "New Milestone · answer the questions above to plan");
1878
1850
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1879
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1851
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone", { basePath });
1880
1852
  }
1881
1853
  }
1882
1854
  return;
@@ -1885,7 +1857,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1885
1857
  const milestoneTitle = state.activeMilestone.title;
1886
1858
  if (planV2GateDecision === "recover-missing-context") {
1887
1859
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1888
- await dispatchWorkflow(pi, await buildDiscussMilestonePrompt(milestoneId, milestoneTitle, basePath, getStructuredQuestionsAvailability(pi, ctx)), "gsd-discuss", ctx, "discuss-milestone");
1860
+ await dispatchWorkflow(pi, await buildDiscussMilestonePrompt(milestoneId, milestoneTitle, basePath, getStructuredQuestionsAvailability(pi, ctx)), "gsd-discuss", ctx, "discuss-milestone", { basePath });
1889
1861
  return;
1890
1862
  }
1891
1863
  // ── All milestones complete → New milestone ──────────────────────────
@@ -1921,7 +1893,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1921
1893
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1922
1894
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1923
1895
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1924
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1896
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone", { basePath });
1925
1897
  }
1926
1898
  else if (choice === "status") {
1927
1899
  const { fireStatusViaCommand } = await import("./commands.js");
@@ -1969,7 +1941,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1969
1941
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1970
1942
  : basePrompt;
1971
1943
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1972
- await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1944
+ await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone", { basePath });
1973
1945
  }
1974
1946
  else if (choice === "discuss_fresh") {
1975
1947
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
@@ -1980,14 +1952,14 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1980
1952
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1981
1953
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1982
1954
  fastPathInstruction: "",
1983
- }), "gsd-discuss", ctx, "discuss-milestone");
1955
+ }), "gsd-discuss", ctx, "discuss-milestone", { basePath });
1984
1956
  }
1985
1957
  else if (choice === "skip_milestone") {
1986
1958
  const milestoneIds = findMilestoneIds(basePath);
1987
1959
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1988
1960
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1989
1961
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1990
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1962
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone", { basePath });
1991
1963
  }
1992
1964
  return;
1993
1965
  }
@@ -2051,7 +2023,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
2051
2023
  else if (choice === "plan") {
2052
2024
  ctx.ui.setStatus("gsd-step", "Planning Milestone · decomposing into slices");
2053
2025
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
2054
- await dispatchWorkflow(pi, await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath), "gsd-run", ctx, "plan-milestone");
2026
+ await dispatchWorkflow(pi, await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath), "gsd-run", ctx, "plan-milestone", { basePath });
2055
2027
  }
2056
2028
  else if (choice === "discuss") {
2057
2029
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
@@ -2061,14 +2033,14 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
2061
2033
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
2062
2034
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
2063
2035
  fastPathInstruction: "",
2064
- }), "gsd-run", ctx, "discuss-milestone");
2036
+ }), "gsd-run", ctx, "discuss-milestone", { basePath });
2065
2037
  }
2066
2038
  else if (choice === "skip_milestone") {
2067
2039
  const milestoneIds = findMilestoneIds(basePath);
2068
2040
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
2069
2041
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
2070
2042
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
2071
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
2043
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone", { basePath });
2072
2044
  }
2073
2045
  else if (choice === "discard_milestone") {
2074
2046
  const confirmed = await showConfirm(ctx, {
@@ -2176,11 +2148,11 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
2176
2148
  });
2177
2149
  if (choice === "plan") {
2178
2150
  ctx.ui.setStatus("gsd-step", "Slice Planning · answer the questions above");
2179
- await dispatchWorkflow(pi, await buildPlanSlicePrompt(milestoneId, milestoneTitle, sliceId, sliceTitle, basePath), "gsd-run", ctx, "plan-slice");
2151
+ await dispatchWorkflow(pi, await buildPlanSlicePrompt(milestoneId, milestoneTitle, sliceId, sliceTitle, basePath), "gsd-run", ctx, "plan-slice", { basePath });
2180
2152
  }
2181
2153
  else if (choice === "discuss") {
2182
2154
  const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
2183
- await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
2155
+ await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice", { basePath });
2184
2156
  }
2185
2157
  else if (choice === "research") {
2186
2158
  const researchTemplates = inlineTemplate("research", "Research");
@@ -2196,7 +2168,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
2196
2168
  sliceTitle,
2197
2169
  extraContext: [researchTemplates],
2198
2170
  }),
2199
- }), "gsd-run", ctx, "research-slice");
2171
+ }), "gsd-run", ctx, "research-slice", { basePath });
2200
2172
  }
2201
2173
  else if (choice === "status") {
2202
2174
  const { fireStatusViaCommand } = await import("./commands.js");
@@ -2236,7 +2208,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
2236
2208
  });
2237
2209
  if (choice === "complete") {
2238
2210
  ctx.ui.setStatus("gsd-step", "Completing Slice · review changes above");
2239
- await dispatchWorkflow(pi, await buildCompleteSlicePrompt(milestoneId, milestoneTitle, sliceId, sliceTitle, basePath), "gsd-run", ctx, "complete-slice");
2211
+ await dispatchWorkflow(pi, await buildCompleteSlicePrompt(milestoneId, milestoneTitle, sliceId, sliceTitle, basePath), "gsd-run", ctx, "complete-slice", { basePath });
2240
2212
  }
2241
2213
  else if (choice === "status") {
2242
2214
  const { fireStatusViaCommand } = await import("./commands.js");
@@ -2291,11 +2263,18 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
2291
2263
  ],
2292
2264
  notYetMessage: "Run /gsd when ready.",
2293
2265
  });
2294
- if (choice === "auto") {
2295
- startAutoDetached(ctx, pi, basePath, false);
2266
+ if (choice === "not_yet")
2267
+ return;
2268
+ const route = resolveActiveTaskChoiceRoute({
2269
+ choice: choice,
2270
+ isolationMode: getIsolationMode(basePath),
2271
+ milestoneId,
2272
+ });
2273
+ if (route.kind === "auto-bootstrap") {
2274
+ startAutoDetached(ctx, pi, basePath, route.verboseMode, route.options);
2296
2275
  return;
2297
2276
  }
2298
- if (choice === "execute") {
2277
+ if (route.kind === "guided-dispatch") {
2299
2278
  ctx.ui.setStatus("gsd-step", "Executing Task · follow progress above");
2300
2279
  if (hasInterrupted) {
2301
2280
  await dispatchWorkflow(pi, loadPrompt("guided-resume-task", {
@@ -2308,17 +2287,17 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
2308
2287
  taskId,
2309
2288
  taskTitle,
2310
2289
  }),
2311
- }), "gsd-run", ctx, "execute-task");
2290
+ }), "gsd-run", ctx, "execute-task", { basePath });
2312
2291
  }
2313
2292
  else {
2314
- await dispatchWorkflow(pi, await buildExecuteTaskPrompt(milestoneId, sliceId, sliceTitle, taskId, taskTitle, basePath), "gsd-run", ctx, "execute-task");
2293
+ await dispatchWorkflow(pi, await buildExecuteTaskPrompt(milestoneId, sliceId, sliceTitle, taskId, taskTitle, basePath), "gsd-run", ctx, "execute-task", { basePath });
2315
2294
  }
2316
2295
  }
2317
- else if (choice === "status") {
2296
+ else if (route.kind === "status") {
2318
2297
  const { fireStatusViaCommand } = await import("./commands.js");
2319
2298
  await fireStatusViaCommand(ctx);
2320
2299
  }
2321
- else if (choice === "milestone_actions") {
2300
+ else if (route.kind === "milestone-actions") {
2322
2301
  const acted = await handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneTitle, options);
2323
2302
  if (acted)
2324
2303
  return showSmartEntry(ctx, pi, basePath, options);
@@ -0,0 +1,23 @@
1
+ // GSD-2 — Guided workflow Unit context.
2
+ // Tracks the guided Unit whose queued turn should use manifest Tool Contract policy.
3
+ const guidedUnitContextByBasePath = new Map();
4
+ export function setGuidedUnitContext(basePath, unitType) {
5
+ const context = { basePath, unitType, startedAt: Date.now() };
6
+ guidedUnitContextByBasePath.set(basePath, context);
7
+ return context;
8
+ }
9
+ export function getGuidedUnitContext(basePath) {
10
+ if (basePath)
11
+ return guidedUnitContextByBasePath.get(basePath) ?? null;
12
+ if (guidedUnitContextByBasePath.size === 1)
13
+ return guidedUnitContextByBasePath.values().next().value;
14
+ return null;
15
+ }
16
+ export function clearGuidedUnitContext(basePath) {
17
+ if (basePath) {
18
+ guidedUnitContextByBasePath.delete(basePath);
19
+ }
20
+ else {
21
+ guidedUnitContextByBasePath.clear();
22
+ }
23
+ }
@@ -1,10 +1,8 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
3
- import { clearEngineHierarchy, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, transaction, } from "./gsd-db.js";
4
- import { migrateHierarchyToDb } from "./md-importer.js";
3
+ import { getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, } from "./gsd-db.js";
5
4
  import { parsePlan, parseRoadmap } from "./parsers-legacy.js";
6
5
  import { milestonesDir, resolveMilestoneFile, resolveSliceFile, } from "./paths.js";
7
- import { invalidateStateCache } from "./state.js";
8
6
  function zeroCounts() {
9
7
  return { milestones: 0, slices: 0, tasks: 0 };
10
8
  }
@@ -50,7 +48,7 @@ export function countDbHierarchy() {
50
48
  }
51
49
  return counts;
52
50
  }
53
- export async function autoImportMarkdownHierarchyIfDbMismatch(basePath) {
51
+ export async function checkMarkdownHierarchyAgainstDb(basePath) {
54
52
  const markdown = countMarkdownHierarchy(basePath);
55
53
  if (sameCounts(markdown, zeroCounts())) {
56
54
  return {
@@ -70,18 +68,15 @@ export async function autoImportMarkdownHierarchyIfDbMismatch(basePath) {
70
68
  return { action: "none", reason: "in-sync", markdown, beforeDb, afterDb: beforeDb };
71
69
  }
72
70
  const reason = sameCounts(beforeDb, zeroCounts()) ? "db-empty" : "count-mismatch";
73
- const imported = transaction(() => {
74
- clearEngineHierarchy();
75
- return migrateHierarchyToDb(basePath);
76
- });
77
- invalidateStateCache();
78
- const afterDb = {
79
- milestones: imported.milestones,
80
- slices: imported.slices,
81
- tasks: imported.tasks,
71
+ return {
72
+ action: "recovery-required",
73
+ reason,
74
+ markdown,
75
+ beforeDb,
76
+ afterDb: beforeDb,
77
+ recoveryCommand: "gsd recover",
78
+ message: `Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
79
+ `do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). ` +
80
+ "Runtime startup will not import markdown automatically; run explicit GSD recovery if markdown should repopulate the database.",
82
81
  };
83
- if (!sameCounts(markdown, afterDb)) {
84
- throw new Error(`migration auto-import verification failed: markdown ${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T, db ${afterDb.milestones}M/${afterDb.slices}S/${afterDb.tasks}T`);
85
- }
86
- return { action: "imported", reason, markdown, beforeDb, afterDb };
87
82
  }