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
@@ -44,10 +44,11 @@ import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.j
44
44
  import { nativeAddAll, nativeCommit, nativeHasCommittedHead, nativeIsRepo, nativeInit } from "./native-git-bridge.js";
45
45
  import { isInheritedRepo } from "./repo-identity.js";
46
46
  import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
47
- import { loadEffectiveGSDPreferences } from "./preferences.js";
47
+ import { getIsolationMode, loadEffectiveGSDPreferences } from "./preferences.js";
48
48
  import { resolveUokFlags } from "./uok/flags.js";
49
49
  import { ensurePlanV2Graph, isMissingFinalizedContextResult } from "./uok/plan-v2.js";
50
50
  import { detectProjectState, hasGsdBootstrapArtifacts } from "./detection.js";
51
+ import { isFutureMilestoneStatus } from "./status-guards.js";
51
52
  import { showProjectInit, offerMigration } from "./init-wizard.js";
52
53
  import { validateDirectory } from "./validate-directory.js";
53
54
  import { showConfirm } from "../shared/tui.js";
@@ -69,8 +70,24 @@ import {
69
70
  formatPriorContextBrief,
70
71
  } from "./preparation.js";
71
72
  import { verifyExpectedArtifact } from "./auto-recovery.js";
72
- import { createWorkspace, scopeMilestone, type MilestoneScope } from "./workspace.js";
73
+ import type { MilestoneScope } from "./workspace.js";
73
74
  import { getPendingGate, extractDepthVerificationMilestoneId } from "./bootstrap/write-gate.js";
75
+ import {
76
+ _getPendingAutoStart,
77
+ clearPendingAutoStart,
78
+ deletePendingAutoStart,
79
+ getDiscussionMilestoneId,
80
+ hasPendingAutoStart,
81
+ setPendingAutoStart,
82
+ } from "./pending-auto-start.js";
83
+ import { clearGuidedUnitContext, setGuidedUnitContext } from "./guided-unit-context.js";
84
+
85
+ export {
86
+ _getPendingAutoStart,
87
+ clearPendingAutoStart,
88
+ getDiscussionMilestoneId,
89
+ setPendingAutoStart,
90
+ } from "./pending-auto-start.js";
74
91
 
75
92
  export function shouldSkipGitBootstrapAfterInit(result: { gitEnabled?: boolean }): boolean {
76
93
  return result.gitEnabled === false;
@@ -92,6 +109,12 @@ import { deleteRuntimeKv } from "./db/runtime-kv.js";
92
109
  import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
93
110
  import { buildWorkflowDispatchContent } from "./workflow-protocol.js";
94
111
  import { isFullGsdToolSurfaceRequested, restoreGsdWorkflowTools, scopeGsdWorkflowToolsForDispatch } from "./bootstrap/register-hooks.js";
112
+ import {
113
+ resolveActiveTaskChoiceRoute,
114
+ type ActiveTaskChoice,
115
+ } from "./smart-entry-routing.js";
116
+
117
+ export { resolveGuidedExecuteLaunchMode } from "./smart-entry-routing.js";
95
118
 
96
119
  type AutoStartOptions = Parameters<typeof startAutoDetached>[4];
97
120
  type AutoStartLauncher = typeof startAutoDetached;
@@ -228,26 +251,6 @@ function buildDocsCommitInstruction(_message: string): string {
228
251
 
229
252
  // ─── Auto-start after discuss ─────────────────────────────────────────────────
230
253
 
231
- /** Pending auto-start context, keyed by basePath for session isolation (#2985). */
232
- interface PendingAutoStartEntry {
233
- ctx: ExtensionCommandContext;
234
- pi: ExtensionAPI;
235
- basePath: string;
236
- milestoneId: string; // the milestone being discussed
237
- step?: boolean; // preserve step mode through discuss → auto transition
238
- createdAt: number; // timestamp for staleness detection (#3274)
239
- // #4573: counter for how many times the LLM emitted the ready phrase
240
- // without writing the required artifacts. Cleared on entry delete/recreate.
241
- readyRejectCount?: number;
242
- // C1: scope is pinned at reservation time so path resolution is immune to
243
- // cwd-drift between discuss and checkAutoStartAfterDiscuss.
244
- // TODO(C3): basePath becomes redundant once all consumers migrate to scope.
245
- scope: MilestoneScope;
246
- // H1: retry counter for Gate 1b plan-blocked recovery. Capped at
247
- // MAX_PLAN_BLOCKED_RECOVERIES to prevent infinite recovery loops (#5012).
248
- planBlockedRecoveryCount: number;
249
- }
250
-
251
254
  interface PendingDeepProjectSetupEntry {
252
255
  ctx: ExtensionCommandContext;
253
256
  pi: ExtensionAPI;
@@ -273,7 +276,6 @@ const MAX_PLAN_BLOCKED_RECOVERIES = 3;
273
276
  // suffix) with optional trailing punctuation.
274
277
  const READY_PHRASE_RE = /\bMilestone\s+M\d{3}[A-Z0-9-]*\s+ready\.?/i;
275
278
 
276
- const pendingAutoStartMap = new Map<string, PendingAutoStartEntry>();
277
279
  const pendingDeepProjectSetupMap = new Map<string, PendingDeepProjectSetupEntry>();
278
280
  const USER_DRIVEN_DEEP_SETUP_UNITS = new Set([
279
281
  "discuss-project",
@@ -300,17 +302,6 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
300
302
  - Do NOT call \`ask_user_questions\`, \`AskUserQuestion\`, or ToolSearch to discover user-input tools.
301
303
  - Ask one focused round, then stop and wait for the user's normal chat response.`;
302
304
 
303
- /**
304
- * Backward-compat bridge: returns a mutable reference to the entry matching
305
- * basePath, or the sole entry when only one session exists.
306
- * Exported for testing — internal use only in production code.
307
- */
308
- export function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
309
- if (basePath) return pendingAutoStartMap.get(basePath) ?? null;
310
- if (pendingAutoStartMap.size === 1) return pendingAutoStartMap.values().next().value!;
311
- return null;
312
- }
313
-
314
305
  function hasNestedFileOrSymlink(dir: string): boolean {
315
306
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
316
307
  if (entry.isFile() || entry.isSymbolicLink()) return true;
@@ -344,29 +335,6 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath: string, entries: st
344
335
  return remaining;
345
336
  }
346
337
 
347
- /**
348
- * Store pending auto-start state for a project.
349
- * Exported for testing (#2985).
350
- */
351
- export function setPendingAutoStart(basePath: string, entry: { basePath: string; milestoneId: string; ctx?: ExtensionCommandContext; pi?: ExtensionAPI; step?: boolean; createdAt?: number }): void {
352
- const ws = createWorkspace(entry.basePath);
353
- const scope = scopeMilestone(ws, entry.milestoneId);
354
- pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope } as PendingAutoStartEntry);
355
- }
356
-
357
- /**
358
- * Clear pending auto-start state.
359
- * If basePath is given, clears only that project. Otherwise clears all.
360
- * Exported for testing (#2985).
361
- */
362
- export function clearPendingAutoStart(basePath?: string): void {
363
- if (basePath) {
364
- pendingAutoStartMap.delete(basePath);
365
- } else {
366
- pendingAutoStartMap.clear();
367
- }
368
- }
369
-
370
338
  export function clearPendingDeepProjectSetup(basePath?: string): void {
371
339
  if (basePath) {
372
340
  pendingDeepProjectSetupMap.delete(basePath);
@@ -375,23 +343,6 @@ export function clearPendingDeepProjectSetup(basePath?: string): void {
375
343
  }
376
344
  }
377
345
 
378
- /**
379
- * Returns the milestoneId being discussed for the given project.
380
- * When basePath is omitted and only one session is active, returns that
381
- * session's milestoneId for backward compatibility. Returns null when
382
- * multiple sessions exist and basePath is not specified (#2985 Bug 4).
383
- */
384
- export function getDiscussionMilestoneId(basePath?: string): string | null {
385
- if (basePath) {
386
- return pendingAutoStartMap.get(basePath)?.milestoneId ?? null;
387
- }
388
- // Backward compat: return the sole entry's milestoneId, or null if ambiguous
389
- if (pendingAutoStartMap.size === 1) {
390
- return pendingAutoStartMap.values().next().value!.milestoneId;
391
- }
392
- return null;
393
- }
394
-
395
346
  function _getPendingDeepProjectSetup(basePath?: string): PendingDeepProjectSetupEntry | null {
396
347
  if (basePath) return pendingDeepProjectSetupMap.get(basePath) ?? null;
397
348
  if (pendingDeepProjectSetupMap.size === 1) return pendingDeepProjectSetupMap.values().next().value!;
@@ -544,13 +495,14 @@ async function dispatchNextDeepProjectSetupStage(entry: PendingDeepProjectSetupE
544
495
  "gsd-run",
545
496
  entry.ctx,
546
497
  result.unitType,
498
+ { basePath: entry.basePath },
547
499
  );
548
500
  return true;
549
501
  }
550
502
 
551
503
  /** Called from agent_end to check if auto-mode should start after discuss */
552
- export function checkAutoStartAfterDiscuss(): boolean {
553
- const entry = _getPendingAutoStart();
504
+ export function checkAutoStartAfterDiscuss(lookupBasePath?: string): boolean {
505
+ const entry = _getPendingAutoStart(lookupBasePath);
554
506
  if (!entry) return false;
555
507
 
556
508
  const { ctx, pi, basePath, milestoneId, step } = entry;
@@ -735,7 +687,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
735
687
  }
736
688
  }
737
689
 
738
- pendingAutoStartMap.delete(basePath);
690
+ deletePendingAutoStart(basePath);
739
691
  ctx.ui.notify(`Milestone ${milestoneId} ready.`, "success");
740
692
  scheduleAutoStartAfterIdle(ctx, pi, basePath, false, { step });
741
693
  return true;
@@ -806,8 +758,8 @@ function hasToolUse(msg: any): boolean {
806
758
  * Returns true when a nudge (or give-up) was emitted, signaling the caller to
807
759
  * skip `resolveAgentEnd`.
808
760
  */
809
- export function maybeHandleReadyPhraseWithoutFiles(event: { messages: any[] }): boolean {
810
- const entry = _getPendingAutoStart();
761
+ export function maybeHandleReadyPhraseWithoutFiles(event: { messages: any[] }, lookupBasePath?: string): boolean {
762
+ const entry = _getPendingAutoStart(lookupBasePath);
811
763
  if (!entry) return false;
812
764
  const { ctx, pi, basePath, milestoneId } = entry;
813
765
 
@@ -854,7 +806,7 @@ export function maybeHandleReadyPhraseWithoutFiles(event: { messages: any[] }):
854
806
  if (entry.readyRejectCount > MAX_READY_REJECTS) {
855
807
  // Give up: clear state and tell the user to re-run /gsd. Avoids an
856
808
  // infinite nudge loop when the LLM never produces the writes.
857
- pendingAutoStartMap.delete(basePath);
809
+ deletePendingAutoStart(basePath);
858
810
  ctx.ui.notify(
859
811
  `Milestone ${milestoneId}: LLM signaled "ready" ${entry.readyRejectCount} times without writing files. ` +
860
812
  `Stopping auto-nudge. Run /gsd to try again.`,
@@ -935,10 +887,11 @@ export function resetEmptyTurnCounter(basePath?: string): void {
935
887
  export function maybeHandleEmptyIntentTurn(
936
888
  event: { messages: any[] },
937
889
  isAuto: boolean,
890
+ lookupBasePath?: string,
938
891
  ): boolean {
939
892
  // Gate: only fire when there is system-driven work in flight. Interactive
940
893
  // /gsd discuss (user-driven) produces legitimate text-only turns.
941
- if (!isAuto && pendingAutoStartMap.size === 0) return false;
894
+ if (!isAuto && !hasPendingAutoStart(lookupBasePath)) return false;
942
895
 
943
896
  const lastMsg = event.messages[event.messages.length - 1];
944
897
  if (!lastMsg) return false;
@@ -965,7 +918,7 @@ export function maybeHandleEmptyIntentTurn(
965
918
 
966
919
  // Resolve the target basePath + pi for injection. Prefer the pending
967
920
  // autostart entry (discuss flow); otherwise we cannot inject.
968
- const entry = _getPendingAutoStart();
921
+ const entry = _getPendingAutoStart(lookupBasePath);
969
922
  if (!entry) return false;
970
923
  const { ctx, pi, basePath } = entry;
971
924
 
@@ -1024,6 +977,19 @@ type UIContext = ExtensionContext;
1024
977
 
1025
978
  // ─── Helpers ──────────────────────────────────────────────────────────────────
1026
979
 
980
+ interface DispatchWorkflowOptions {
981
+ basePath?: string;
982
+ deps?: {
983
+ loadPreferences?: typeof loadEffectiveGSDPreferences;
984
+ selectModel?: typeof selectAndApplyModel;
985
+ getTransportSupportError?: typeof getWorkflowTransportSupportError;
986
+ };
987
+ }
988
+
989
+ export function resolveGuidedDispatchProjectRoot(basePath?: string): string {
990
+ return basePath ?? process.cwd();
991
+ }
992
+
1027
993
  /**
1028
994
  * Read GSD-WORKFLOW.md and dispatch it to the LLM with a contextual note.
1029
995
  * This is the only way the wizard triggers work — everything else is the LLM's job.
@@ -1039,13 +1005,20 @@ async function dispatchWorkflow(
1039
1005
  customType = "gsd-run",
1040
1006
  ctx?: ExtensionContext,
1041
1007
  unitType?: string,
1008
+ options?: DispatchWorkflowOptions,
1042
1009
  ): Promise<void> {
1010
+ const resolvedOptions = options ?? {};
1011
+ const projectRoot = resolveGuidedDispatchProjectRoot(resolvedOptions.basePath);
1012
+ const loadPreferences = resolvedOptions.deps?.loadPreferences ?? loadEffectiveGSDPreferences;
1013
+ const selectModel = resolvedOptions.deps?.selectModel ?? selectAndApplyModel;
1014
+ const getTransportSupportError = resolvedOptions.deps?.getTransportSupportError ?? getWorkflowTransportSupportError;
1015
+
1043
1016
  // Route through the dynamic routing pipeline (complexity classification,
1044
1017
  // tier downgrade, fallback chains) — same path as auto-mode dispatches (#2958).
1045
1018
  if (ctx && unitType) {
1046
- const prefs = loadEffectiveGSDPreferences()?.preferences;
1047
- const result = await selectAndApplyModel(
1048
- ctx, pi, unitType, /* unitId */ "", /* basePath */ process.cwd(),
1019
+ const prefs = loadPreferences(projectRoot)?.preferences;
1020
+ const result = await selectModel(
1021
+ ctx, pi, unitType, /* unitId */ "", projectRoot,
1049
1022
  prefs, /* verbose */ false, /* autoModeStartModel */ null,
1050
1023
  /* retryContext */ undefined, /* isAutoMode */ false,
1051
1024
  );
@@ -1057,11 +1030,11 @@ async function dispatchWorkflow(
1057
1030
  });
1058
1031
  }
1059
1032
 
1060
- const compatibilityError = getWorkflowTransportSupportError(
1033
+ const compatibilityError = getTransportSupportError(
1061
1034
  result.appliedModel?.provider ?? ctx.model?.provider,
1062
1035
  getRequiredWorkflowToolsForGuidedUnit(unitType),
1063
1036
  {
1064
- projectRoot: process.cwd(),
1037
+ projectRoot,
1065
1038
  surface: "guided flow",
1066
1039
  unitType,
1067
1040
  authMode: result.appliedModel?.provider
@@ -1070,6 +1043,7 @@ async function dispatchWorkflow(
1070
1043
  ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
1071
1044
  : undefined,
1072
1045
  baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
1046
+ activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
1073
1047
  },
1074
1048
  );
1075
1049
  if (compatibilityError) {
@@ -1119,14 +1093,20 @@ async function dispatchWorkflow(
1119
1093
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(gsdHome(), "agent", "GSD-WORKFLOW.md");
1120
1094
  const workflow = readFileSync(workflowPath, "utf-8");
1121
1095
 
1122
- pi.sendMessage(
1123
- {
1124
- customType,
1125
- content: buildWorkflowDispatchContent({ workflow, workflowPath, task: note }),
1126
- display: false,
1127
- },
1128
- { triggerTurn: true },
1129
- );
1096
+ if (unitType) setGuidedUnitContext(projectRoot, unitType);
1097
+ try {
1098
+ pi.sendMessage(
1099
+ {
1100
+ customType,
1101
+ content: buildWorkflowDispatchContent({ workflow, workflowPath, task: note }),
1102
+ display: false,
1103
+ },
1104
+ { triggerTurn: true },
1105
+ );
1106
+ } catch (err) {
1107
+ clearGuidedUnitContext(projectRoot);
1108
+ throw err;
1109
+ }
1130
1110
  } finally {
1131
1111
  // Restore full tool set after the message is queued. The LLM turn has
1132
1112
  // already captured the scoped set — restoring prevents the narrowed
@@ -1357,7 +1337,7 @@ export async function showHeadlessMilestoneCreation(
1357
1337
  // model/tool routing to skip discuss-flow tool scoping and
1358
1338
  // `checkAutoStartAfterDiscuss` guardrails that rely on the
1359
1339
  // "discuss-"-prefixed unitType.
1360
- await dispatchWorkflow(pi, prompt, "gsd-run", ctx, "discuss-milestone");
1340
+ await dispatchWorkflow(pi, prompt, "gsd-run", ctx, "discuss-milestone", { basePath });
1361
1341
  }
1362
1342
 
1363
1343
 
@@ -1493,7 +1473,7 @@ export async function showDiscuss(
1493
1473
  // No active milestone (or corrupted milestone with undefined id) —
1494
1474
  // check for pending milestones to discuss instead
1495
1475
  if (!state.activeMilestone?.id) {
1496
- const pendingMilestones = state.registry.filter(m => m.status === "pending");
1476
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
1497
1477
  if (pendingMilestones.length === 0) {
1498
1478
  ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
1499
1479
  return;
@@ -1548,7 +1528,7 @@ export async function showDiscuss(
1548
1528
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1549
1529
  : basePrompt;
1550
1530
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1551
- await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1531
+ await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone", { basePath });
1552
1532
  } else if (choice === "discuss_fresh") {
1553
1533
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1554
1534
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
@@ -1558,7 +1538,7 @@ export async function showDiscuss(
1558
1538
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1559
1539
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
1560
1540
  fastPathInstruction: "",
1561
- }), "gsd-discuss", ctx, "discuss-milestone");
1541
+ }), "gsd-discuss", ctx, "discuss-milestone", { basePath });
1562
1542
  } else if (choice === "skip_milestone") {
1563
1543
  const { ensureDbOpen } = await import("./bootstrap/dynamic-tools.js");
1564
1544
  await ensureDbOpen(basePath);
@@ -1566,7 +1546,7 @@ export async function showDiscuss(
1566
1546
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1567
1547
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1568
1548
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
1569
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1549
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone", { basePath });
1570
1550
  }
1571
1551
  return;
1572
1552
  }
@@ -1605,7 +1585,7 @@ export async function showDiscuss(
1605
1585
 
1606
1586
  if (pendingSlices.length === 0) {
1607
1587
  // All slices complete — but queued milestones may still need discussion (#3150)
1608
- const pendingMilestones = state.registry.filter(m => m.status === "pending");
1588
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
1609
1589
  if (pendingMilestones.length > 0) {
1610
1590
  await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
1611
1591
  return;
@@ -1629,7 +1609,7 @@ export async function showDiscuss(
1629
1609
  // If all pending slices are discussed, check for queued milestones before exiting (#3150)
1630
1610
  const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
1631
1611
  if (allDiscussed) {
1632
- const pendingMilestones = state.registry.filter(m => m.status === "pending");
1612
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
1633
1613
  if (pendingMilestones.length > 0) {
1634
1614
  await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
1635
1615
  return;
@@ -1665,7 +1645,7 @@ export async function showDiscuss(
1665
1645
  });
1666
1646
 
1667
1647
  // Offer access to queued milestones when any exist
1668
- const pendingMilestones = state.registry.filter(m => m.status === "pending");
1648
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
1669
1649
  if (pendingMilestones.length > 0) {
1670
1650
  actions.push({
1671
1651
  id: "discuss_queued_milestone",
@@ -1714,7 +1694,7 @@ export async function showDiscuss(
1714
1694
 
1715
1695
  const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
1716
1696
  const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
1717
- await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
1697
+ await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice", { basePath });
1718
1698
 
1719
1699
  // Wait for the discuss session to finish, then loop back to the picker
1720
1700
  await ctx.waitForIdle();
@@ -1740,10 +1720,11 @@ async function showDiscussQueuedMilestone(
1740
1720
  const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
1741
1721
  const hasDraft = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
1742
1722
  const contextStatus = hasContext ? "context ✓" : hasDraft ? "draft context" : "no context yet";
1723
+ const statusLabel = m.status === "planned" ? "planned" : "queued";
1743
1724
  return {
1744
1725
  id: m.id,
1745
1726
  label: `${m.id}: ${m.title}`,
1746
- description: `[queued] · ${contextStatus}`,
1727
+ description: `[${statusLabel}] · ${contextStatus}`,
1747
1728
  recommended: i === 0,
1748
1729
  };
1749
1730
  });
@@ -1835,7 +1816,7 @@ async function dispatchDiscussForMilestone(
1835
1816
  const prompt = draftContent
1836
1817
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1837
1818
  : basePrompt;
1838
- await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-milestone");
1819
+ await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-milestone", { basePath });
1839
1820
  }
1840
1821
 
1841
1822
  // ─── Smart Entry Point ────────────────────────────────────────────────────────
@@ -1978,7 +1959,7 @@ async function handleMilestoneActions(
1978
1959
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1979
1960
  `New milestone ${nextId}.`,
1980
1961
  basePath
1981
- ), "gsd-run", ctx, "discuss-milestone");
1962
+ ), "gsd-run", ctx, "discuss-milestone", { basePath });
1982
1963
  return true;
1983
1964
  }
1984
1965
 
@@ -2124,18 +2105,19 @@ export async function showSmartEntry(
2124
2105
 
2125
2106
  if (interrupted.classification !== "recoverable") {
2126
2107
  try {
2127
- const { autoImportMarkdownHierarchyIfDbMismatch } = await import("./migration-auto-check.js");
2128
- const result = await autoImportMarkdownHierarchyIfDbMismatch(basePath);
2129
- if (result.action === "imported") {
2108
+ const { checkMarkdownHierarchyAgainstDb } = await import("./migration-auto-check.js");
2109
+ const result = await checkMarkdownHierarchyAgainstDb(basePath);
2110
+ if (result.action === "recovery-required") {
2130
2111
  ctx.ui.notify(
2131
- `Recovered migrated planning state into gsd.db (${result.reason}): ${result.afterDb.milestones} milestone(s), ${result.afterDb.slices} slice(s), ${result.afterDb.tasks} task(s).`,
2132
- "info",
2112
+ result.message ??
2113
+ `Markdown planning artifacts do not match the authoritative DB. Run \`${result.recoveryCommand ?? "gsd recover"}\` to import markdown explicitly.`,
2114
+ "warning",
2133
2115
  );
2134
2116
  }
2135
2117
  } catch (err) {
2136
2118
  const message = err instanceof Error ? err.message : String(err);
2137
- ctx.ui.notify(`GSD could not auto-import existing planning state into gsd.db: ${message}`, "warning");
2138
- logWarning("guided", `planning state auto-import failed: ${message}`, { file: "guided-flow.ts" });
2119
+ ctx.ui.notify(`GSD could not compare markdown planning artifacts with gsd.db: ${message}`, "warning");
2120
+ logWarning("guided", `planning state DB/markdown comparison failed: ${message}`, { file: "guided-flow.ts" });
2139
2121
  }
2140
2122
  }
2141
2123
 
@@ -2176,17 +2158,17 @@ export async function showSmartEntry(
2176
2158
  // Both /gsd and /gsd auto reach this branch when no milestone exists yet.
2177
2159
  // Without this guard, every subsequent /gsd call overwrites the pending auto-start
2178
2160
  // and fires another dispatchWorkflow, resetting the conversation mid-interview.
2179
- if (pendingAutoStartMap.has(basePath)) {
2161
+ if (hasPendingAutoStart(basePath)) {
2180
2162
  // #3274: If /clear interrupted the discussion, the pending entry is stale.
2181
2163
  // Detect staleness: no manifest, no milestone CONTEXT artifact, AND entry is older than
2182
2164
  // 30s (avoids race between .set() and LLM writing first artifact).
2183
- const entry = pendingAutoStartMap.get(basePath)!;
2165
+ const entry = _getPendingAutoStart(basePath)!;
2184
2166
  const ageMs = Date.now() - (entry.createdAt || 0);
2185
2167
  const manifestExists = existsSync(join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json"));
2186
2168
  const milestoneHasContext = !!resolveMilestoneFile(basePath, entry.milestoneId, "CONTEXT");
2187
2169
  if (!manifestExists && !milestoneHasContext && ageMs > 30_000) {
2188
2170
  // Stale entry from an interrupted discussion — clear and continue
2189
- pendingAutoStartMap.delete(basePath);
2171
+ deletePendingAutoStart(basePath);
2190
2172
  } else {
2191
2173
  ctx.ui.notify("Discussion already in progress — answer the question above to continue.", "info");
2192
2174
  return;
@@ -2226,7 +2208,7 @@ export async function showSmartEntry(
2226
2208
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2227
2209
  `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
2228
2210
  basePath
2229
- ), "gsd-run", ctx, "discuss-milestone");
2211
+ ), "gsd-run", ctx, "discuss-milestone", { basePath });
2230
2212
  } else {
2231
2213
  const choice = await showNextAction(ctx, {
2232
2214
  title: "GSD — Get Shit Done",
@@ -2255,7 +2237,7 @@ export async function showSmartEntry(
2255
2237
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2256
2238
  `New milestone ${nextId}.`,
2257
2239
  basePath
2258
- ), "gsd-run", ctx, "discuss-milestone");
2240
+ ), "gsd-run", ctx, "discuss-milestone", { basePath });
2259
2241
  }
2260
2242
  }
2261
2243
  return;
@@ -2277,6 +2259,7 @@ export async function showSmartEntry(
2277
2259
  "gsd-discuss",
2278
2260
  ctx,
2279
2261
  "discuss-milestone",
2262
+ { basePath },
2280
2263
  );
2281
2264
  return;
2282
2265
  }
@@ -2318,7 +2301,7 @@ export async function showSmartEntry(
2318
2301
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2319
2302
  `New milestone ${nextId}.`,
2320
2303
  basePath
2321
- ), "gsd-run", ctx, "discuss-milestone");
2304
+ ), "gsd-run", ctx, "discuss-milestone", { basePath });
2322
2305
  } else if (choice === "status") {
2323
2306
  const { fireStatusViaCommand } = await import("./commands.js");
2324
2307
  await fireStatusViaCommand(ctx);
@@ -2368,7 +2351,7 @@ export async function showSmartEntry(
2368
2351
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
2369
2352
  : basePrompt;
2370
2353
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
2371
- await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
2354
+ await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone", { basePath });
2372
2355
  } else if (choice === "discuss_fresh") {
2373
2356
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
2374
2357
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
@@ -2378,7 +2361,7 @@ export async function showSmartEntry(
2378
2361
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
2379
2362
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
2380
2363
  fastPathInstruction: "",
2381
- }), "gsd-discuss", ctx, "discuss-milestone");
2364
+ }), "gsd-discuss", ctx, "discuss-milestone", { basePath });
2382
2365
  } else if (choice === "skip_milestone") {
2383
2366
  const milestoneIds = findMilestoneIds(basePath);
2384
2367
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
@@ -2387,7 +2370,7 @@ export async function showSmartEntry(
2387
2370
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2388
2371
  `New milestone ${nextId}.`,
2389
2372
  basePath
2390
- ), "gsd-run", ctx, "discuss-milestone");
2373
+ ), "gsd-run", ctx, "discuss-milestone", { basePath });
2391
2374
  }
2392
2375
  return;
2393
2376
  }
@@ -2462,6 +2445,7 @@ export async function showSmartEntry(
2462
2445
  "gsd-run",
2463
2446
  ctx,
2464
2447
  "plan-milestone",
2448
+ { basePath },
2465
2449
  );
2466
2450
  } else if (choice === "discuss") {
2467
2451
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
@@ -2471,7 +2455,7 @@ export async function showSmartEntry(
2471
2455
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
2472
2456
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
2473
2457
  fastPathInstruction: "",
2474
- }), "gsd-run", ctx, "discuss-milestone");
2458
+ }), "gsd-run", ctx, "discuss-milestone", { basePath });
2475
2459
  } else if (choice === "skip_milestone") {
2476
2460
  const milestoneIds = findMilestoneIds(basePath);
2477
2461
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
@@ -2480,7 +2464,7 @@ export async function showSmartEntry(
2480
2464
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2481
2465
  `New milestone ${nextId}.`,
2482
2466
  basePath
2483
- ), "gsd-run", ctx, "discuss-milestone");
2467
+ ), "gsd-run", ctx, "discuss-milestone", { basePath });
2484
2468
  } else if (choice === "discard_milestone") {
2485
2469
  const confirmed = await showConfirm(ctx, {
2486
2470
  title: "Discard milestone?",
@@ -2595,10 +2579,11 @@ export async function showSmartEntry(
2595
2579
  "gsd-run",
2596
2580
  ctx,
2597
2581
  "plan-slice",
2582
+ { basePath },
2598
2583
  );
2599
2584
  } else if (choice === "discuss") {
2600
2585
  const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
2601
- await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
2586
+ await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice", { basePath });
2602
2587
  } else if (choice === "research") {
2603
2588
  const researchTemplates = inlineTemplate("research", "Research");
2604
2589
  await dispatchWorkflow(pi, loadPrompt("guided-research-slice", {
@@ -2613,7 +2598,7 @@ export async function showSmartEntry(
2613
2598
  sliceTitle,
2614
2599
  extraContext: [researchTemplates],
2615
2600
  }),
2616
- }), "gsd-run", ctx, "research-slice");
2601
+ }), "gsd-run", ctx, "research-slice", { basePath });
2617
2602
  } else if (choice === "status") {
2618
2603
  const { fireStatusViaCommand } = await import("./commands.js");
2619
2604
  await fireStatusViaCommand(ctx);
@@ -2658,6 +2643,7 @@ export async function showSmartEntry(
2658
2643
  "gsd-run",
2659
2644
  ctx,
2660
2645
  "complete-slice",
2646
+ { basePath },
2661
2647
  );
2662
2648
  } else if (choice === "status") {
2663
2649
  const { fireStatusViaCommand } = await import("./commands.js");
@@ -2714,12 +2700,20 @@ export async function showSmartEntry(
2714
2700
  notYetMessage: "Run /gsd when ready.",
2715
2701
  });
2716
2702
 
2717
- if (choice === "auto") {
2718
- startAutoDetached(ctx, pi, basePath, false);
2703
+ if (choice === "not_yet") return;
2704
+
2705
+ const route = resolveActiveTaskChoiceRoute({
2706
+ choice: choice as ActiveTaskChoice,
2707
+ isolationMode: getIsolationMode(basePath),
2708
+ milestoneId,
2709
+ });
2710
+
2711
+ if (route.kind === "auto-bootstrap") {
2712
+ startAutoDetached(ctx, pi, basePath, route.verboseMode, route.options);
2719
2713
  return;
2720
2714
  }
2721
2715
 
2722
- if (choice === "execute") {
2716
+ if (route.kind === "guided-dispatch") {
2723
2717
  ctx.ui.setStatus("gsd-step", "Executing Task · follow progress above");
2724
2718
  if (hasInterrupted) {
2725
2719
  await dispatchWorkflow(pi, loadPrompt("guided-resume-task", {
@@ -2732,7 +2726,7 @@ export async function showSmartEntry(
2732
2726
  taskId,
2733
2727
  taskTitle,
2734
2728
  }),
2735
- }), "gsd-run", ctx, "execute-task");
2729
+ }), "gsd-run", ctx, "execute-task", { basePath });
2736
2730
  } else {
2737
2731
  await dispatchWorkflow(
2738
2732
  pi,
@@ -2740,12 +2734,13 @@ export async function showSmartEntry(
2740
2734
  "gsd-run",
2741
2735
  ctx,
2742
2736
  "execute-task",
2737
+ { basePath },
2743
2738
  );
2744
2739
  }
2745
- } else if (choice === "status") {
2740
+ } else if (route.kind === "status") {
2746
2741
  const { fireStatusViaCommand } = await import("./commands.js");
2747
2742
  await fireStatusViaCommand(ctx);
2748
- } else if (choice === "milestone_actions") {
2743
+ } else if (route.kind === "milestone-actions") {
2749
2744
  const acted = await handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneTitle, options);
2750
2745
  if (acted) return showSmartEntry(ctx, pi, basePath, options);
2751
2746
  }
@@ -0,0 +1,30 @@
1
+ // GSD-2 — Guided workflow Unit context.
2
+ // Tracks the guided Unit whose queued turn should use manifest Tool Contract policy.
3
+
4
+ export interface GuidedUnitContext {
5
+ basePath: string;
6
+ unitType: string;
7
+ startedAt: number;
8
+ }
9
+
10
+ const guidedUnitContextByBasePath = new Map<string, GuidedUnitContext>();
11
+
12
+ export function setGuidedUnitContext(basePath: string, unitType: string): GuidedUnitContext {
13
+ const context = { basePath, unitType, startedAt: Date.now() };
14
+ guidedUnitContextByBasePath.set(basePath, context);
15
+ return context;
16
+ }
17
+
18
+ export function getGuidedUnitContext(basePath?: string): GuidedUnitContext | null {
19
+ if (basePath) return guidedUnitContextByBasePath.get(basePath) ?? null;
20
+ if (guidedUnitContextByBasePath.size === 1) return guidedUnitContextByBasePath.values().next().value!;
21
+ return null;
22
+ }
23
+
24
+ export function clearGuidedUnitContext(basePath?: string): void {
25
+ if (basePath) {
26
+ guidedUnitContextByBasePath.delete(basePath);
27
+ } else {
28
+ guidedUnitContextByBasePath.clear();
29
+ }
30
+ }