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
@@ -7,11 +7,11 @@
7
7
  */
8
8
  import { showNextAction } from "../shared/tui.js";
9
9
  import { loadFile, saveFile } from "./files.js";
10
- import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
10
+ import { isDbAvailable, getMilestone, getMilestoneSlices } from "./gsd-db.js";
11
11
  import { parseRoadmapSlices } from "./roadmap-slices.js";
12
12
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
13
13
  import { buildCompleteSlicePrompt, buildDiscussMilestonePrompt, buildExecuteTaskPrompt, buildPlanMilestonePrompt, buildPlanSlicePrompt, buildSkillActivationBlock, } from "./auto-prompts.js";
14
- import { deriveState } from "./state.js";
14
+ import { deriveState, isGhostMilestone } from "./state.js";
15
15
  import { invalidateAllCaches } from "./cache.js";
16
16
  import { startAutoDetached } from "./auto.js";
17
17
  import { clearLock } from "./crash-recovery.js";
@@ -43,10 +43,41 @@ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
43
43
  import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForGuidedUnit, supportsStructuredQuestions, } from "./workflow-mcp.js";
44
44
  import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, } from "./preparation.js";
45
45
  import { verifyExpectedArtifact } from "./auto-recovery.js";
46
+ import { createWorkspace, scopeMilestone } from "./workspace.js";
46
47
  // ─── Re-exports (preserve public API for existing importers) ────────────────
47
48
  export { MILESTONE_ID_RE, generateMilestoneSuffix, nextMilestoneId, extractMilestoneSeq, parseMilestoneId, milestoneIdSort, maxMilestoneNum, findMilestoneIds, reserveMilestoneId, claimReservedId, getReservedMilestoneIds, clearReservedMilestoneIds, } from "./milestone-ids.js";
48
49
  export { showQueue, handleQueueReorder, showQueueAdd, buildExistingMilestonesContext, } from "./guided-flow-queue.js";
49
50
  import { logWarning } from "./workflow-logger.js";
51
+ import { deleteRuntimeKv } from "./db/runtime-kv.js";
52
+ import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
53
+ // ─── Scope-based validator wrappers ──────────────────────────────────────────
54
+ // These thin wrappers accept a MilestoneScope so callers that already hold a
55
+ // pinned scope never have to re-derive (basePath, milestoneId) separately.
56
+ // The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
57
+ // state.ts are unchanged — only the call surface in guided-flow.ts is migrated.
58
+ /**
59
+ * Scope-based overload of verifyExpectedArtifact.
60
+ * Uses scope.workspace.projectRoot as the authoritative base path, making
61
+ * the check immune to cwd-drift and worktree-path divergence.
62
+ */
63
+ export function verifyExpectedArtifactForScope(scope, unitType, unitId) {
64
+ return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
65
+ }
66
+ /**
67
+ * Scope-based overload of resolveExpectedArtifactPath.
68
+ * Returns the canonical absolute path (or null) using the scope's projectRoot.
69
+ */
70
+ export function resolveExpectedArtifactPathForScope(scope, unitType, unitId) {
71
+ return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
72
+ }
73
+ /**
74
+ * Scope-based overload of isGhostMilestone.
75
+ * Binds basePath and milestoneId from the scope, ensuring path resolution
76
+ * uses the canonical project root regardless of the cwd at call time.
77
+ */
78
+ export function isGhostMilestoneByScope(scope) {
79
+ return isGhostMilestone(scope.workspace.projectRoot, scope.milestoneId);
80
+ }
50
81
  function needsPlanV2Gate(state) {
51
82
  return state.phase === "executing"
52
83
  || state.phase === "summarizing"
@@ -77,6 +108,10 @@ function buildDocsCommitInstruction(_message) {
77
108
  // #4573: cap for how many times we nudge the LLM after a premature ready
78
109
  // phrase before giving up and asking the user to re-run /gsd.
79
110
  const MAX_READY_REJECTS = 2;
111
+ // H1 (#5012): cap for Gate 1b plan-blocked recovery hints. After this many
112
+ // consecutive recovery attempts the loop is stopped and the user is directed
113
+ // to investigate manually.
114
+ const MAX_PLAN_BLOCKED_RECOVERIES = 3;
80
115
  // #4573: matches the canonical ready phrase the discuss prompt asks the LLM
81
116
  // to emit. Accepts any M-prefixed milestone ID (three digits + optional
82
117
  // suffix) with optional trailing punctuation.
@@ -110,9 +145,9 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
110
145
  /**
111
146
  * Backward-compat bridge: returns a mutable reference to the entry matching
112
147
  * basePath, or the sole entry when only one session exists.
113
- * Internal use onlyexternal code should use the Map directly.
148
+ * Exported for testinginternal use only in production code.
114
149
  */
115
- function _getPendingAutoStart(basePath) {
150
+ export function _getPendingAutoStart(basePath) {
116
151
  if (basePath)
117
152
  return pendingAutoStartMap.get(basePath) ?? null;
118
153
  if (pendingAutoStartMap.size === 1)
@@ -157,7 +192,9 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath, entries) {
157
192
  * Exported for testing (#2985).
158
193
  */
159
194
  export function setPendingAutoStart(basePath, entry) {
160
- pendingAutoStartMap.set(basePath, { createdAt: Date.now(), ...entry });
195
+ const ws = createWorkspace(entry.basePath);
196
+ const scope = scopeMilestone(ws, entry.milestoneId);
197
+ pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope });
161
198
  }
162
199
  /**
163
200
  * Clear pending auto-start state.
@@ -249,6 +286,10 @@ export async function checkDeepProjectSetupAfterTurn(_event, ctx, basePath) {
249
286
  if (!entry)
250
287
  return false;
251
288
  if (entry.currentUnitType && entry.currentUnitId) {
289
+ // TODO(C-future): PendingDeepProjectSetupEntry does not carry a MilestoneScope
290
+ // because deep-project-setup units span non-milestone unit types (discuss-project,
291
+ // discuss-requirements, etc.). Migrate to verifyExpectedArtifactForScope once
292
+ // PendingDeepProjectSetupEntry is extended with a scope field.
252
293
  const artifactReady = verifyExpectedArtifact(entry.currentUnitType, entry.currentUnitId, entry.basePath);
253
294
  if (!artifactReady) {
254
295
  return false;
@@ -320,17 +361,61 @@ export function checkAutoStartAfterDiscuss() {
320
361
  const { ctx, pi, basePath, milestoneId, step } = entry;
321
362
  // Gate 1: Primary milestone must have CONTEXT.md or ROADMAP.md
322
363
  // The "discuss" path creates CONTEXT.md; the "plan" path creates ROADMAP.md.
323
- const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
324
- const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
364
+ // Use pinned scope (immune to cwd-drift) for existence checks.
365
+ const contextFilePath = entry.scope.contextFile();
366
+ const roadmapFilePath = entry.scope.roadmapFile();
367
+ const contextFile = existsSync(contextFilePath) ? contextFilePath : null;
368
+ const roadmapFile = existsSync(roadmapFilePath) ? roadmapFilePath : null;
325
369
  if (!contextFile && !roadmapFile)
326
370
  return false; // neither artifact yet — keep waiting
371
+ // Gate 1b: Discriminate plan-blocked from discuss-incomplete when the DB row is queued.
372
+ // If the DB is available and the row is still "queued" but CONTEXT.md already exists on
373
+ // disk, the discuss phase completed but gsd_plan_milestone was hard-blocked by the
374
+ // depth-verification gate. Emit a recovery hint so the next agent turn can retry
375
+ // gsd_plan_milestone, then return false (keep blocking auto-start).
376
+ // If CONTEXT.md does not exist (discuss-incomplete), Gate 1 already blocked above.
377
+ if (isDbAvailable()) {
378
+ const dbRow = getMilestone(milestoneId);
379
+ if (dbRow?.status === "queued" && contextFile) {
380
+ if (entry.planBlockedRecoveryCount >= MAX_PLAN_BLOCKED_RECOVERIES) {
381
+ // H1: recovery loop cap reached — stop triggering new turns, escalate to user.
382
+ logWarning("guided", `Gate 1b: milestone ${milestoneId} plan-blocked recovery limit reached ` +
383
+ `(${entry.planBlockedRecoveryCount}/${MAX_PLAN_BLOCKED_RECOVERIES}); escalating to user`);
384
+ ctx.ui.notify(`Milestone ${milestoneId} plan_milestone has been blocked ${entry.planBlockedRecoveryCount} times. ` +
385
+ `Re-run /gsd to reset the recovery counter, or run /gsd-debug to diagnose without resetting.`, "error");
386
+ return false;
387
+ }
388
+ logWarning("guided", `Gate 1b: milestone ${milestoneId} queued with CONTEXT.md present — ` +
389
+ `plan_milestone was blocked; emitting recovery hint ` +
390
+ `(attempt ${entry.planBlockedRecoveryCount + 1}/${MAX_PLAN_BLOCKED_RECOVERIES})`);
391
+ ctx.ui.notify(`Milestone ${milestoneId}: context file exists but milestone is still queued. ` +
392
+ `Retrying gsd_plan_milestone to complete the blocked planning step.`, "warning");
393
+ try {
394
+ pi.sendMessage({
395
+ customType: "gsd-plan-milestone-blocked-recovery",
396
+ content: `Milestone ${milestoneId} has ${contextFile} on disk but its DB row is still ` +
397
+ `"queued". The gsd_plan_milestone tool was previously blocked by the ` +
398
+ `depth-verification gate. Call gsd_plan_milestone now to complete the ` +
399
+ `planning phase.`,
400
+ display: false,
401
+ }, { triggerTurn: true });
402
+ // Increment only after a successful dispatch so transient sendMessage
403
+ // failures do not consume recovery budget.
404
+ entry.planBlockedRecoveryCount += 1;
405
+ }
406
+ catch (e) {
407
+ logWarning("guided", `Gate 1b recovery sendMessage failed: ${e.message}`);
408
+ }
409
+ return false;
410
+ }
411
+ }
327
412
  // Gate 2: STATE.md must exist — written as the last step in the discuss
328
413
  // output phase. This prevents auto-start from firing during Phase 3
329
414
  // (sequential readiness gates for remaining milestones) in multi-milestone
330
415
  // discussions, where M001-CONTEXT.md exists but M002/M003 haven't been
331
416
  // processed yet.
332
- const stateFile = resolveGsdRootFile(basePath, "STATE");
333
- if (!stateFile)
417
+ const stateFilePath = entry.scope.stateFile();
418
+ if (!existsSync(stateFilePath))
334
419
  return false; // discussion not finalized yet
335
420
  // Gate 3: Multi-milestone completeness warning
336
421
  // Parse PROJECT.md for milestone sequence, warn if any are missing context.
@@ -362,7 +447,7 @@ export function checkAutoStartAfterDiscuss() {
362
447
  // The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
363
448
  // When it exists, validate it before auto-starting. Project history alone is
364
449
  // not a reliable signal for the current discussion mode.
365
- const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
450
+ const manifestPath = join(entry.scope.workspace.contract.projectGsd, "DISCUSSION-MANIFEST.json");
366
451
  if (existsSync(manifestPath)) {
367
452
  try {
368
453
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -896,7 +981,7 @@ export async function showHeadlessMilestoneCreation(ctx, pi, basePath, seedConte
896
981
  // Build and dispatch the headless discuss prompt
897
982
  const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
898
983
  // Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
899
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, createdAt: Date.now() });
984
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId });
900
985
  // Dispatch as discuss-milestone. The LLM writes PROJECT.md, REQUIREMENTS.md,
901
986
  // and CONTEXT.md, then calls gsd_plan_milestone — this is semantically the
902
987
  // discuss path, just non-interactive. Using "plan-milestone" here caused
@@ -1062,13 +1147,13 @@ export async function showDiscuss(ctx, pi, basePath) {
1062
1147
  const seed = draftContent
1063
1148
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1064
1149
  : basePrompt;
1065
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
1150
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1066
1151
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1067
1152
  }
1068
1153
  else if (choice === "discuss_fresh") {
1069
1154
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1070
1155
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1071
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
1156
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1072
1157
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1073
1158
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1074
1159
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
@@ -1081,7 +1166,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1081
1166
  const milestoneIds = findMilestoneIds(basePath);
1082
1167
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1083
1168
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1084
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
1169
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
1085
1170
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1086
1171
  }
1087
1172
  return;
@@ -1335,6 +1420,9 @@ function selfHealRuntimeRecords(basePath, ctx) {
1335
1420
  for (const record of records) {
1336
1421
  const { unitType, unitId, phase } = record;
1337
1422
  // Clear records whose expected artifact already exists (completed but not cleaned up)
1423
+ // TODO(C-future): selfHealRuntimeRecords iterates across all unit types (not just milestone
1424
+ // units), so it cannot be converted to resolveExpectedArtifactPathForScope without
1425
+ // first establishing a per-record scope. Migrate once unit runtime records carry scope info.
1338
1426
  const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
1339
1427
  if (artifactPath && existsSync(artifactPath)) {
1340
1428
  clearUnitRuntimeRecord(basePath, unitType, unitId);
@@ -1437,7 +1525,7 @@ async function handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneT
1437
1525
  const milestoneIds = findMilestoneIds(basePath);
1438
1526
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1439
1527
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1440
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1528
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1441
1529
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1442
1530
  return true;
1443
1531
  }
@@ -1535,11 +1623,13 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1535
1623
  if (interrupted.classification === "stale") {
1536
1624
  clearLock(basePath);
1537
1625
  if (interrupted.pausedSession) {
1626
+ // Phase C pt 2: paused-session.json migrated to runtime_kv
1627
+ // (global scope, key PAUSED_SESSION_KV_KEY).
1538
1628
  try {
1539
- unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"));
1629
+ deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
1540
1630
  }
1541
1631
  catch (e) {
1542
- logWarning("guided", `stale pause file cleanup failed: ${e.message}`, { file: "guided-flow.ts" });
1632
+ logWarning("guided", `stale paused-session DB cleanup failed: ${e.message}`, { file: "guided-flow.ts" });
1543
1633
  }
1544
1634
  }
1545
1635
  }
@@ -1642,7 +1732,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1642
1732
  const isFirst = milestoneIds.length === 0;
1643
1733
  if (isFirst) {
1644
1734
  // First ever — skip wizard, just ask directly
1645
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1735
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1646
1736
  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");
1647
1737
  }
1648
1738
  else {
@@ -1660,7 +1750,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1660
1750
  notYetMessage: "Run /gsd when ready.",
1661
1751
  });
1662
1752
  if (choice === "new_milestone") {
1663
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1753
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1664
1754
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1665
1755
  }
1666
1756
  }
@@ -1669,7 +1759,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1669
1759
  const milestoneId = state.activeMilestone.id;
1670
1760
  const milestoneTitle = state.activeMilestone.title;
1671
1761
  if (planV2GateDecision === "recover-missing-context") {
1672
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1762
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1673
1763
  await dispatchWorkflow(pi, await buildDiscussMilestonePrompt(milestoneId, milestoneTitle, basePath, getStructuredQuestionsAvailability(pi, ctx)), "gsd-discuss", ctx, "discuss-milestone");
1674
1764
  return;
1675
1765
  }
@@ -1697,7 +1787,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1697
1787
  const milestoneIds = findMilestoneIds(basePath);
1698
1788
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1699
1789
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1700
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1790
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1701
1791
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1702
1792
  }
1703
1793
  else if (choice === "status") {
@@ -1744,13 +1834,13 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1744
1834
  const seed = draftContent
1745
1835
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1746
1836
  : basePrompt;
1747
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1837
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1748
1838
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1749
1839
  }
1750
1840
  else if (choice === "discuss_fresh") {
1751
1841
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1752
1842
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1753
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1843
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1754
1844
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1755
1845
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1756
1846
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -1761,7 +1851,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1761
1851
  const milestoneIds = findMilestoneIds(basePath);
1762
1852
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1763
1853
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1764
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1854
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1765
1855
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1766
1856
  }
1767
1857
  return;
@@ -1817,7 +1907,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1817
1907
  notYetMessage: "Run /gsd when ready.",
1818
1908
  });
1819
1909
  if (choice === "plan") {
1820
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1910
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1821
1911
  await dispatchWorkflow(pi, await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath), "gsd-run", ctx, "plan-milestone");
1822
1912
  }
1823
1913
  else if (choice === "discuss") {
@@ -1833,7 +1923,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1833
1923
  const milestoneIds = findMilestoneIds(basePath);
1834
1924
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1835
1925
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1836
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1926
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1837
1927
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1838
1928
  }
1839
1929
  else if (choice === "discard_milestone") {
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, unlinkSync } from "node:fs";
1
+ import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { verifyExpectedArtifact } from "./auto-recovery.js";
4
4
  import { formatCrashInfo, isLockProcessAlive, readCrashLock, } from "./crash-recovery.js";
@@ -6,6 +6,7 @@ import { gsdRoot } from "./paths.js";
6
6
  import { MILESTONE_ID_RE } from "./milestone-ids.js";
7
7
  import { synthesizeCrashRecovery, } from "./session-forensics.js";
8
8
  import { deriveState } from "./state.js";
9
+ import { getRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
9
10
  const LEGACY_DEEP_SETUP_UNITS = new Set([
10
11
  "workflow-preferences:WORKFLOW-PREFS",
11
12
  "discuss-project:PROJECT",
@@ -32,24 +33,26 @@ function isStalePseudoMilestonePause(meta) {
32
33
  && typeof meta.unitId === "string"
33
34
  && LEGACY_DEEP_SETUP_UNITS.has(`${meta.unitType}:${meta.unitId}`);
34
35
  }
36
+ /**
37
+ * runtime_kv key (global scope) that stores the most recent paused-session
38
+ * metadata. Phase C pt 2: replaces runtime/paused-session.json. The key is
39
+ * project-wide (not worker-scoped) because the paused state represents the
40
+ * last time auto-mode paused on this project — there is at most one paused
41
+ * session per project at a time.
42
+ */
43
+ export const PAUSED_SESSION_KV_KEY = "paused_session";
35
44
  export function readPausedSessionMetadata(basePath) {
36
- const pausedPath = join(gsdRoot(basePath), "runtime", "paused-session.json");
37
- if (!existsSync(pausedPath))
45
+ // basePath is unused now (the DB is workspace-scoped via the connection
46
+ // openDatabase opened on it) but kept in the signature for callers.
47
+ void basePath;
48
+ const meta = getRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
49
+ if (!meta)
38
50
  return null;
39
- try {
40
- const meta = JSON.parse(readFileSync(pausedPath, "utf-8"));
41
- if (isStalePseudoMilestonePause(meta)) {
42
- try {
43
- unlinkSync(pausedPath);
44
- }
45
- catch { /* non-fatal */ }
46
- return null;
47
- }
48
- return meta;
49
- }
50
- catch {
51
+ if (isStalePseudoMilestonePause(meta)) {
52
+ deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
51
53
  return null;
52
54
  }
55
+ return meta;
53
56
  }
54
57
  export function isBootstrapCrashLock(lock) {
55
58
  return !!(lock &&