gsd-pi 2.78.1-dev.e9d88a536 → 2.78.1-dev.eccf86e27

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 (212) hide show
  1. package/README.md +5 -7
  2. package/dist/help-text.js +1 -1
  3. package/dist/resource-loader.js +6 -1
  4. package/dist/resources/.managed-resources-content-hash +1 -1
  5. package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
  6. package/dist/resources/extensions/gsd/auto/loop.js +235 -36
  7. package/dist/resources/extensions/gsd/auto/phases.js +14 -7
  8. package/dist/resources/extensions/gsd/auto/session.js +36 -0
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
  11. package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
  12. package/dist/resources/extensions/gsd/auto.js +139 -49
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  16. package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
  17. package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
  18. package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
  19. package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
  20. package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
  21. package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
  22. package/dist/resources/extensions/gsd/db-writer.js +96 -16
  23. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  24. package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  25. package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
  26. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
  27. package/dist/resources/extensions/gsd/doctor.js +12 -2
  28. package/dist/resources/extensions/gsd/gsd-db.js +355 -3
  29. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  30. package/dist/resources/extensions/gsd/guided-flow.js +116 -26
  31. package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
  32. package/dist/resources/extensions/gsd/metrics.js +287 -1
  33. package/dist/resources/extensions/gsd/paths.js +79 -8
  34. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  35. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  36. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  37. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  38. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  39. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  40. package/dist/resources/extensions/gsd/state.js +21 -6
  41. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  42. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  43. package/dist/resources/extensions/gsd/workspace.js +59 -0
  44. package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
  45. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  46. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  47. package/dist/web/standalone/.next/BUILD_ID +1 -1
  48. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  49. package/dist/web/standalone/.next/build-manifest.json +2 -2
  50. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  51. package/dist/web/standalone/.next/required-server-files.json +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.html +1 -1
  69. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  76. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  78. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  79. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  80. package/dist/web/standalone/server.js +1 -1
  81. package/package.json +1 -1
  82. package/packages/mcp-server/README.md +2 -11
  83. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  84. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  85. package/packages/mcp-server/dist/remote-questions.js +28 -0
  86. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  87. package/packages/mcp-server/dist/server.d.ts +28 -0
  88. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  89. package/packages/mcp-server/dist/server.js +94 -4
  90. package/packages/mcp-server/dist/server.js.map +1 -1
  91. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  92. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  93. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  94. package/packages/mcp-server/src/remote-questions.ts +35 -0
  95. package/packages/mcp-server/src/server.ts +129 -6
  96. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  97. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  98. package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
  99. package/src/resources/extensions/gsd/auto/loop.ts +263 -41
  100. package/src/resources/extensions/gsd/auto/phases.ts +15 -7
  101. package/src/resources/extensions/gsd/auto/session.ts +40 -0
  102. package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
  103. package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
  104. package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
  105. package/src/resources/extensions/gsd/auto.ts +166 -43
  106. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  107. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
  108. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  109. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  110. package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
  111. package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
  112. package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
  113. package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
  114. package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
  115. package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
  116. package/src/resources/extensions/gsd/db-writer.ts +113 -17
  117. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  118. package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  119. package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
  120. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
  121. package/src/resources/extensions/gsd/doctor.ts +10 -2
  122. package/src/resources/extensions/gsd/gsd-db.ts +354 -3
  123. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  124. package/src/resources/extensions/gsd/guided-flow.ts +152 -26
  125. package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
  126. package/src/resources/extensions/gsd/metrics.ts +321 -1
  127. package/src/resources/extensions/gsd/paths.ts +67 -8
  128. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  129. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  130. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  131. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  132. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  133. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  134. package/src/resources/extensions/gsd/state.ts +44 -6
  135. package/src/resources/extensions/gsd/templates/project.md +10 -0
  136. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  137. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
  138. package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
  139. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  140. package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
  141. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  142. package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
  143. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
  144. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
  145. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  146. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  147. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  148. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  149. package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
  150. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  151. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  152. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  153. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  154. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  155. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  156. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  157. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  158. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  159. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  160. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
  161. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
  162. package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
  163. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
  164. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
  165. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
  166. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
  167. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  168. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  169. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  170. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  171. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  172. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  173. package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
  174. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  175. package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
  176. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  177. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  178. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  179. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
  180. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  181. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
  182. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
  183. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  184. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
  185. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  186. package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
  187. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
  188. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
  189. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
  190. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
  191. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  192. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
  193. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  194. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  195. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
  196. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
  197. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  198. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  199. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  200. package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
  201. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  202. package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
  203. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  204. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  205. package/src/resources/extensions/gsd/workspace.ts +95 -0
  206. package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
  207. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  208. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
  209. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
  210. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
  211. /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
  212. /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
@@ -10,7 +10,7 @@ import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@g
10
10
  import type { GSDState } from "./types.js";
11
11
  import { showNextAction } from "../shared/tui.js";
12
12
  import { loadFile, saveFile } from "./files.js";
13
- import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
13
+ import { isDbAvailable, getMilestone, getMilestoneSlices } from "./gsd-db.js";
14
14
  import { parseRoadmapSlices } from "./roadmap-slices.js";
15
15
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
16
16
  import {
@@ -21,7 +21,7 @@ import {
21
21
  buildPlanSlicePrompt,
22
22
  buildSkillActivationBlock,
23
23
  } from "./auto-prompts.js";
24
- import { deriveState } from "./state.js";
24
+ import { deriveState, isGhostMilestone } from "./state.js";
25
25
  import { invalidateAllCaches } from "./cache.js";
26
26
  import { startAutoDetached } from "./auto.js";
27
27
  import { clearLock } from "./crash-recovery.js";
@@ -69,6 +69,7 @@ import {
69
69
  formatPriorContextBrief,
70
70
  } from "./preparation.js";
71
71
  import { verifyExpectedArtifact } from "./auto-recovery.js";
72
+ import { createWorkspace, scopeMilestone, type MilestoneScope } from "./workspace.js";
72
73
 
73
74
  // ─── Re-exports (preserve public API for existing importers) ────────────────
74
75
  export {
@@ -82,6 +83,48 @@ export {
82
83
  buildExistingMilestonesContext,
83
84
  } from "./guided-flow-queue.js";
84
85
  import { logWarning } from "./workflow-logger.js";
86
+ import { deleteRuntimeKv } from "./db/runtime-kv.js";
87
+ import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
88
+
89
+ // ─── Scope-based validator wrappers ──────────────────────────────────────────
90
+ // These thin wrappers accept a MilestoneScope so callers that already hold a
91
+ // pinned scope never have to re-derive (basePath, milestoneId) separately.
92
+ // The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
93
+ // state.ts are unchanged — only the call surface in guided-flow.ts is migrated.
94
+
95
+ /**
96
+ * Scope-based overload of verifyExpectedArtifact.
97
+ * Uses scope.workspace.projectRoot as the authoritative base path, making
98
+ * the check immune to cwd-drift and worktree-path divergence.
99
+ */
100
+ export function verifyExpectedArtifactForScope(
101
+ scope: MilestoneScope,
102
+ unitType: string,
103
+ unitId: string,
104
+ ): boolean {
105
+ return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
106
+ }
107
+
108
+ /**
109
+ * Scope-based overload of resolveExpectedArtifactPath.
110
+ * Returns the canonical absolute path (or null) using the scope's projectRoot.
111
+ */
112
+ export function resolveExpectedArtifactPathForScope(
113
+ scope: MilestoneScope,
114
+ unitType: string,
115
+ unitId: string,
116
+ ): string | null {
117
+ return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
118
+ }
119
+
120
+ /**
121
+ * Scope-based overload of isGhostMilestone.
122
+ * Binds basePath and milestoneId from the scope, ensuring path resolution
123
+ * uses the canonical project root regardless of the cwd at call time.
124
+ */
125
+ export function isGhostMilestoneByScope(scope: MilestoneScope): boolean {
126
+ return isGhostMilestone(scope.workspace.projectRoot, scope.milestoneId);
127
+ }
85
128
 
86
129
  function needsPlanV2Gate(state: GSDState): boolean {
87
130
  return state.phase === "executing"
@@ -135,6 +178,13 @@ interface PendingAutoStartEntry {
135
178
  // #4573: counter for how many times the LLM emitted the ready phrase
136
179
  // without writing the required artifacts. Cleared on entry delete/recreate.
137
180
  readyRejectCount?: number;
181
+ // C1: scope is pinned at reservation time so path resolution is immune to
182
+ // cwd-drift between discuss and checkAutoStartAfterDiscuss.
183
+ // TODO(C3): basePath becomes redundant once all consumers migrate to scope.
184
+ scope: MilestoneScope;
185
+ // H1: retry counter for Gate 1b plan-blocked recovery. Capped at
186
+ // MAX_PLAN_BLOCKED_RECOVERIES to prevent infinite recovery loops (#5012).
187
+ planBlockedRecoveryCount: number;
138
188
  }
139
189
 
140
190
  interface PendingDeepProjectSetupEntry {
@@ -152,6 +202,11 @@ interface PendingDeepProjectSetupEntry {
152
202
  // phrase before giving up and asking the user to re-run /gsd.
153
203
  const MAX_READY_REJECTS = 2;
154
204
 
205
+ // H1 (#5012): cap for Gate 1b plan-blocked recovery hints. After this many
206
+ // consecutive recovery attempts the loop is stopped and the user is directed
207
+ // to investigate manually.
208
+ const MAX_PLAN_BLOCKED_RECOVERIES = 3;
209
+
155
210
  // #4573: matches the canonical ready phrase the discuss prompt asks the LLM
156
211
  // to emit. Accepts any M-prefixed milestone ID (three digits + optional
157
212
  // suffix) with optional trailing punctuation.
@@ -187,9 +242,9 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
187
242
  /**
188
243
  * Backward-compat bridge: returns a mutable reference to the entry matching
189
244
  * basePath, or the sole entry when only one session exists.
190
- * Internal use onlyexternal code should use the Map directly.
245
+ * Exported for testinginternal use only in production code.
191
246
  */
192
- function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
247
+ export function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
193
248
  if (basePath) return pendingAutoStartMap.get(basePath) ?? null;
194
249
  if (pendingAutoStartMap.size === 1) return pendingAutoStartMap.values().next().value!;
195
250
  return null;
@@ -233,7 +288,9 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath: string, entries: st
233
288
  * Exported for testing (#2985).
234
289
  */
235
290
  export function setPendingAutoStart(basePath: string, entry: { basePath: string; milestoneId: string; ctx?: ExtensionCommandContext; pi?: ExtensionAPI; step?: boolean; createdAt?: number }): void {
236
- pendingAutoStartMap.set(basePath, { createdAt: Date.now(), ...entry } as PendingAutoStartEntry);
291
+ const ws = createWorkspace(entry.basePath);
292
+ const scope = scopeMilestone(ws, entry.milestoneId);
293
+ pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope } as PendingAutoStartEntry);
237
294
  }
238
295
 
239
296
  /**
@@ -343,6 +400,10 @@ export async function checkDeepProjectSetupAfterTurn(
343
400
  if (!entry) return false;
344
401
 
345
402
  if (entry.currentUnitType && entry.currentUnitId) {
403
+ // TODO(C-future): PendingDeepProjectSetupEntry does not carry a MilestoneScope
404
+ // because deep-project-setup units span non-milestone unit types (discuss-project,
405
+ // discuss-requirements, etc.). Migrate to verifyExpectedArtifactForScope once
406
+ // PendingDeepProjectSetupEntry is extended with a scope field.
346
407
  const artifactReady = verifyExpectedArtifact(entry.currentUnitType, entry.currentUnitId, entry.basePath);
347
408
  if (!artifactReady) {
348
409
  return false;
@@ -426,17 +487,77 @@ export function checkAutoStartAfterDiscuss(): boolean {
426
487
 
427
488
  // Gate 1: Primary milestone must have CONTEXT.md or ROADMAP.md
428
489
  // The "discuss" path creates CONTEXT.md; the "plan" path creates ROADMAP.md.
429
- const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
430
- const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
490
+ // Use pinned scope (immune to cwd-drift) for existence checks.
491
+ const contextFilePath = entry.scope.contextFile();
492
+ const roadmapFilePath = entry.scope.roadmapFile();
493
+ const contextFile = existsSync(contextFilePath) ? contextFilePath : null;
494
+ const roadmapFile = existsSync(roadmapFilePath) ? roadmapFilePath : null;
431
495
  if (!contextFile && !roadmapFile) return false; // neither artifact yet — keep waiting
432
496
 
497
+ // Gate 1b: Discriminate plan-blocked from discuss-incomplete when the DB row is queued.
498
+ // If the DB is available and the row is still "queued" but CONTEXT.md already exists on
499
+ // disk, the discuss phase completed but gsd_plan_milestone was hard-blocked by the
500
+ // depth-verification gate. Emit a recovery hint so the next agent turn can retry
501
+ // gsd_plan_milestone, then return false (keep blocking auto-start).
502
+ // If CONTEXT.md does not exist (discuss-incomplete), Gate 1 already blocked above.
503
+ if (isDbAvailable()) {
504
+ const dbRow = getMilestone(milestoneId);
505
+ if (dbRow?.status === "queued" && contextFile) {
506
+ if (entry.planBlockedRecoveryCount >= MAX_PLAN_BLOCKED_RECOVERIES) {
507
+ // H1: recovery loop cap reached — stop triggering new turns, escalate to user.
508
+ logWarning(
509
+ "guided",
510
+ `Gate 1b: milestone ${milestoneId} plan-blocked recovery limit reached ` +
511
+ `(${entry.planBlockedRecoveryCount}/${MAX_PLAN_BLOCKED_RECOVERIES}); escalating to user`,
512
+ );
513
+ ctx.ui.notify(
514
+ `Milestone ${milestoneId} plan_milestone has been blocked ${entry.planBlockedRecoveryCount} times. ` +
515
+ `Re-run /gsd to reset the recovery counter, or run /gsd-debug to diagnose without resetting.`,
516
+ "error",
517
+ );
518
+ return false;
519
+ }
520
+ logWarning(
521
+ "guided",
522
+ `Gate 1b: milestone ${milestoneId} queued with CONTEXT.md present — ` +
523
+ `plan_milestone was blocked; emitting recovery hint ` +
524
+ `(attempt ${entry.planBlockedRecoveryCount + 1}/${MAX_PLAN_BLOCKED_RECOVERIES})`,
525
+ );
526
+ ctx.ui.notify(
527
+ `Milestone ${milestoneId}: context file exists but milestone is still queued. ` +
528
+ `Retrying gsd_plan_milestone to complete the blocked planning step.`,
529
+ "warning",
530
+ );
531
+ try {
532
+ pi.sendMessage(
533
+ {
534
+ customType: "gsd-plan-milestone-blocked-recovery",
535
+ content:
536
+ `Milestone ${milestoneId} has ${contextFile} on disk but its DB row is still ` +
537
+ `"queued". The gsd_plan_milestone tool was previously blocked by the ` +
538
+ `depth-verification gate. Call gsd_plan_milestone now to complete the ` +
539
+ `planning phase.`,
540
+ display: false,
541
+ },
542
+ { triggerTurn: true },
543
+ );
544
+ // Increment only after a successful dispatch so transient sendMessage
545
+ // failures do not consume recovery budget.
546
+ entry.planBlockedRecoveryCount += 1;
547
+ } catch (e) {
548
+ logWarning("guided", `Gate 1b recovery sendMessage failed: ${(e as Error).message}`);
549
+ }
550
+ return false;
551
+ }
552
+ }
553
+
433
554
  // Gate 2: STATE.md must exist — written as the last step in the discuss
434
555
  // output phase. This prevents auto-start from firing during Phase 3
435
556
  // (sequential readiness gates for remaining milestones) in multi-milestone
436
557
  // discussions, where M001-CONTEXT.md exists but M002/M003 haven't been
437
558
  // processed yet.
438
- const stateFile = resolveGsdRootFile(basePath, "STATE");
439
- if (!stateFile) return false; // discussion not finalized yet
559
+ const stateFilePath = entry.scope.stateFile();
560
+ if (!existsSync(stateFilePath)) return false; // discussion not finalized yet
440
561
 
441
562
  // Gate 3: Multi-milestone completeness warning
442
563
  // Parse PROJECT.md for milestone sequence, warn if any are missing context.
@@ -469,7 +590,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
469
590
  // The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
470
591
  // When it exists, validate it before auto-starting. Project history alone is
471
592
  // not a reliable signal for the current discussion mode.
472
- const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
593
+ const manifestPath = join(entry.scope.workspace.contract.projectGsd, "DISCUSSION-MANIFEST.json");
473
594
  if (existsSync(manifestPath)) {
474
595
  try {
475
596
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -1104,7 +1225,7 @@ export async function showHeadlessMilestoneCreation(
1104
1225
  const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
1105
1226
 
1106
1227
  // Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
1107
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, createdAt: Date.now() });
1228
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId });
1108
1229
 
1109
1230
  // Dispatch as discuss-milestone. The LLM writes PROJECT.md, REQUIREMENTS.md,
1110
1231
  // and CONTEXT.md, then calls gsd_plan_milestone — this is semantically the
@@ -1301,12 +1422,12 @@ export async function showDiscuss(
1301
1422
  const seed = draftContent
1302
1423
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1303
1424
  : basePrompt;
1304
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
1425
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1305
1426
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1306
1427
  } else if (choice === "discuss_fresh") {
1307
1428
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1308
1429
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1309
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
1430
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1310
1431
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1311
1432
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1312
1433
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
@@ -1318,7 +1439,7 @@ export async function showDiscuss(
1318
1439
  const milestoneIds = findMilestoneIds(basePath);
1319
1440
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1320
1441
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1321
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
1442
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
1322
1443
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1323
1444
  }
1324
1445
  return;
@@ -1609,6 +1730,9 @@ function selfHealRuntimeRecords(basePath: string, ctx: ExtensionContext): { clea
1609
1730
  for (const record of records) {
1610
1731
  const { unitType, unitId, phase } = record;
1611
1732
  // Clear records whose expected artifact already exists (completed but not cleaned up)
1733
+ // TODO(C-future): selfHealRuntimeRecords iterates across all unit types (not just milestone
1734
+ // units), so it cannot be converted to resolveExpectedArtifactPathForScope without
1735
+ // first establishing a per-record scope. Migrate once unit runtime records carry scope info.
1612
1736
  const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
1613
1737
  if (artifactPath && existsSync(artifactPath)) {
1614
1738
  clearUnitRuntimeRecord(basePath, unitType, unitId);
@@ -1723,7 +1847,7 @@ async function handleMilestoneActions(
1723
1847
  const milestoneIds = findMilestoneIds(basePath);
1724
1848
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1725
1849
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1726
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1850
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1727
1851
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1728
1852
  `New milestone ${nextId}.`,
1729
1853
  basePath
@@ -1841,10 +1965,12 @@ export async function showSmartEntry(
1841
1965
  if (interrupted.classification === "stale") {
1842
1966
  clearLock(basePath);
1843
1967
  if (interrupted.pausedSession) {
1968
+ // Phase C pt 2: paused-session.json migrated to runtime_kv
1969
+ // (global scope, key PAUSED_SESSION_KV_KEY).
1844
1970
  try {
1845
- unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"));
1971
+ deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
1846
1972
  } catch (e) {
1847
- logWarning("guided", `stale pause file cleanup failed: ${(e as Error).message}`, { file: "guided-flow.ts" });
1973
+ logWarning("guided", `stale paused-session DB cleanup failed: ${(e as Error).message}`, { file: "guided-flow.ts" });
1848
1974
  }
1849
1975
  }
1850
1976
  } else if (interrupted.classification === "recoverable") {
@@ -1951,7 +2077,7 @@ export async function showSmartEntry(
1951
2077
 
1952
2078
  if (isFirst) {
1953
2079
  // First ever — skip wizard, just ask directly
1954
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2080
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1955
2081
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1956
2082
  `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
1957
2083
  basePath
@@ -1972,7 +2098,7 @@ export async function showSmartEntry(
1972
2098
  });
1973
2099
 
1974
2100
  if (choice === "new_milestone") {
1975
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2101
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1976
2102
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1977
2103
  `New milestone ${nextId}.`,
1978
2104
  basePath
@@ -1986,7 +2112,7 @@ export async function showSmartEntry(
1986
2112
  const milestoneTitle = state.activeMilestone.title;
1987
2113
 
1988
2114
  if (planV2GateDecision === "recover-missing-context") {
1989
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
2115
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1990
2116
  await dispatchWorkflow(
1991
2117
  pi,
1992
2118
  await buildDiscussMilestonePrompt(
@@ -2028,7 +2154,7 @@ export async function showSmartEntry(
2028
2154
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
2029
2155
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
2030
2156
 
2031
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2157
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
2032
2158
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2033
2159
  `New milestone ${nextId}.`,
2034
2160
  basePath
@@ -2080,12 +2206,12 @@ export async function showSmartEntry(
2080
2206
  const seed = draftContent
2081
2207
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
2082
2208
  : basePrompt;
2083
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
2209
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
2084
2210
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
2085
2211
  } else if (choice === "discuss_fresh") {
2086
2212
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
2087
2213
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
2088
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
2214
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
2089
2215
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
2090
2216
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
2091
2217
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -2095,7 +2221,7 @@ export async function showSmartEntry(
2095
2221
  const milestoneIds = findMilestoneIds(basePath);
2096
2222
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
2097
2223
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
2098
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2224
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
2099
2225
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2100
2226
  `New milestone ${nextId}.`,
2101
2227
  basePath
@@ -2160,7 +2286,7 @@ export async function showSmartEntry(
2160
2286
  });
2161
2287
 
2162
2288
  if (choice === "plan") {
2163
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
2289
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
2164
2290
  await dispatchWorkflow(
2165
2291
  pi,
2166
2292
  await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath),
@@ -2180,7 +2306,7 @@ export async function showSmartEntry(
2180
2306
  const milestoneIds = findMilestoneIds(basePath);
2181
2307
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
2182
2308
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
2183
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2309
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
2184
2310
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2185
2311
  `New milestone ${nextId}.`,
2186
2312
  basePath
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, unlinkSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  import { verifyExpectedArtifact } from "./auto-recovery.js";
@@ -16,6 +16,7 @@ import {
16
16
  } from "./session-forensics.js";
17
17
  import { deriveState } from "./state.js";
18
18
  import type { GSDState } from "./types.js";
19
+ import { getRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
19
20
 
20
21
  export type InterruptedSessionClassification =
21
22
  | "none"
@@ -82,22 +83,28 @@ function isStalePseudoMilestonePause(meta: PausedSessionMetadata): boolean {
82
83
  && LEGACY_DEEP_SETUP_UNITS.has(`${meta.unitType}:${meta.unitId}`);
83
84
  }
84
85
 
86
+ /**
87
+ * runtime_kv key (global scope) that stores the most recent paused-session
88
+ * metadata. Phase C pt 2: replaces runtime/paused-session.json. The key is
89
+ * project-wide (not worker-scoped) because the paused state represents the
90
+ * last time auto-mode paused on this project — there is at most one paused
91
+ * session per project at a time.
92
+ */
93
+ export const PAUSED_SESSION_KV_KEY = "paused_session";
94
+
85
95
  export function readPausedSessionMetadata(
86
96
  basePath: string,
87
97
  ): PausedSessionMetadata | null {
88
- const pausedPath = join(gsdRoot(basePath), "runtime", "paused-session.json");
89
- if (!existsSync(pausedPath)) return null;
90
-
91
- try {
92
- const meta = JSON.parse(readFileSync(pausedPath, "utf-8")) as PausedSessionMetadata;
93
- if (isStalePseudoMilestonePause(meta)) {
94
- try { unlinkSync(pausedPath); } catch { /* non-fatal */ }
95
- return null;
96
- }
97
- return meta;
98
- } catch {
98
+ // basePath is unused now (the DB is workspace-scoped via the connection
99
+ // openDatabase opened on it) but kept in the signature for callers.
100
+ void basePath;
101
+ const meta = getRuntimeKv<PausedSessionMetadata>("global", "", PAUSED_SESSION_KV_KEY);
102
+ if (!meta) return null;
103
+ if (isStalePseudoMilestonePause(meta)) {
104
+ deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
99
105
  return null;
100
106
  }
107
+ return meta;
101
108
  }
102
109
 
103
110
  export function isBootstrapCrashLock(lock: LockData | null): boolean {