gsd-pi 2.78.1-dev.b0759e59b → 2.78.1-dev.d8826a445

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 (300) hide show
  1. package/README.md +8 -5
  2. package/dist/headless-recover.d.ts +23 -0
  3. package/dist/headless-recover.js +93 -0
  4. package/dist/headless.js +9 -0
  5. package/dist/help-text.js +1 -0
  6. package/dist/resources/.managed-resources-content-hash +1 -1
  7. package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
  8. package/dist/resources/extensions/gsd/auto/phases.js +7 -2
  9. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +7 -58
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +14 -28
  12. package/dist/resources/extensions/gsd/auto-start.js +1 -8
  13. package/dist/resources/extensions/gsd/auto-worktree.js +244 -216
  14. package/dist/resources/extensions/gsd/auto.js +86 -7
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  16. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
  18. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  19. package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
  20. package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
  21. package/dist/resources/extensions/gsd/commands-logs.js +2 -2
  22. package/dist/resources/extensions/gsd/commands-scan.js +2 -2
  23. package/dist/resources/extensions/gsd/commands-ship.js +2 -2
  24. package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
  25. package/dist/resources/extensions/gsd/db-writer.js +106 -95
  26. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  27. package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
  28. package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
  29. package/dist/resources/extensions/gsd/gsd-db.js +268 -8
  30. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  31. package/dist/resources/extensions/gsd/guided-flow.js +141 -32
  32. package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
  33. package/dist/resources/extensions/gsd/metrics.js +287 -1
  34. package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
  35. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
  36. package/dist/resources/extensions/gsd/paths.js +114 -9
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  39. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  40. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  41. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  42. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  44. package/dist/resources/extensions/gsd/queue-order.js +6 -1
  45. package/dist/resources/extensions/gsd/rethink.js +2 -2
  46. package/dist/resources/extensions/gsd/state.js +91 -372
  47. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  48. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
  49. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
  50. package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
  51. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
  52. package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
  53. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
  54. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  55. package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
  56. package/dist/resources/extensions/gsd/workspace.js +59 -0
  57. package/dist/resources/extensions/gsd/worktree-command.js +4 -3
  58. package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
  59. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  60. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  61. package/dist/web/standalone/.next/BUILD_ID +1 -1
  62. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  63. package/dist/web/standalone/.next/build-manifest.json +2 -2
  64. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  65. package/dist/web/standalone/.next/required-server-files.json +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  83. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  108. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  112. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/index.html +1 -1
  115. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  122. package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
  123. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  124. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  126. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  127. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  128. package/dist/web/standalone/server.js +1 -1
  129. package/package.json +1 -1
  130. package/packages/mcp-server/README.md +2 -11
  131. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  132. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/remote-questions.js +28 -0
  134. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  135. package/packages/mcp-server/dist/server.d.ts +28 -0
  136. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  137. package/packages/mcp-server/dist/server.js +94 -4
  138. package/packages/mcp-server/dist/server.js.map +1 -1
  139. package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
  140. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  141. package/packages/mcp-server/dist/workflow-tools.js +56 -2
  142. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  143. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  144. package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
  145. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  146. package/packages/mcp-server/src/remote-questions.ts +35 -0
  147. package/packages/mcp-server/src/server.ts +129 -6
  148. package/packages/mcp-server/src/workflow-tools.ts +62 -3
  149. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  150. package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
  151. package/src/resources/extensions/gsd/auto/phases.ts +8 -2
  152. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  153. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -62
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +15 -27
  155. package/src/resources/extensions/gsd/auto-start.ts +1 -8
  156. package/src/resources/extensions/gsd/auto-worktree.ts +286 -251
  157. package/src/resources/extensions/gsd/auto.ts +102 -7
  158. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  159. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
  160. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
  161. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  162. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  163. package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
  164. package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
  165. package/src/resources/extensions/gsd/commands-logs.ts +2 -2
  166. package/src/resources/extensions/gsd/commands-scan.ts +2 -2
  167. package/src/resources/extensions/gsd/commands-ship.ts +2 -2
  168. package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
  169. package/src/resources/extensions/gsd/db-writer.ts +123 -94
  170. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  171. package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
  172. package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
  173. package/src/resources/extensions/gsd/gsd-db.ts +269 -8
  174. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  175. package/src/resources/extensions/gsd/guided-flow.ts +181 -32
  176. package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
  177. package/src/resources/extensions/gsd/metrics.ts +321 -1
  178. package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
  179. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
  180. package/src/resources/extensions/gsd/paths.ts +122 -9
  181. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  182. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  183. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  184. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  185. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  186. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
  188. package/src/resources/extensions/gsd/queue-order.ts +6 -1
  189. package/src/resources/extensions/gsd/rethink.ts +2 -2
  190. package/src/resources/extensions/gsd/state.ts +91 -389
  191. package/src/resources/extensions/gsd/templates/project.md +10 -0
  192. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
  193. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  194. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
  195. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
  196. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  197. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  198. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
  199. package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
  200. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
  201. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
  202. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  203. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  204. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  205. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  206. package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
  207. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  208. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
  209. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
  210. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
  211. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
  212. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
  213. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
  214. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  215. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
  216. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
  217. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
  218. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  219. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
  220. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
  221. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  222. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  223. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  224. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  225. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  226. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
  227. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  228. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  229. package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
  230. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  231. package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
  232. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
  233. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
  234. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
  235. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
  236. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
  237. package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
  238. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  239. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  240. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  241. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  242. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  243. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  244. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  245. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
  246. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  247. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  248. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  249. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  250. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
  251. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
  252. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  253. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
  254. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
  255. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
  256. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
  257. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
  258. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  259. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
  260. package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
  261. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
  262. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  263. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
  264. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
  265. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
  266. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
  267. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
  268. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  269. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
  270. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  271. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
  273. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
  274. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  275. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
  276. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
  277. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +26 -3
  278. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  279. package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
  280. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
  281. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
  282. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
  283. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  284. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
  285. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
  287. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
  288. package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
  289. package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
  290. package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
  291. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
  292. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  293. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
  294. package/src/resources/extensions/gsd/workspace.ts +95 -0
  295. package/src/resources/extensions/gsd/worktree-command.ts +4 -3
  296. package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
  297. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  298. package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
  299. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
  300. /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_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";
@@ -36,7 +36,7 @@ import { gsdHome } from "./gsd-home.js";
36
36
  import {
37
37
  gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath,
38
38
  resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
39
- relMilestoneFile, relSliceFile,
39
+ relMilestoneFile, relSliceFile, clearPathCache,
40
40
  } from "./paths.js";
41
41
  import { join } from "node:path";
42
42
  import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
@@ -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 {
@@ -83,6 +84,46 @@ export {
83
84
  } from "./guided-flow-queue.js";
84
85
  import { logWarning } from "./workflow-logger.js";
85
86
 
87
+ // ─── Scope-based validator wrappers ──────────────────────────────────────────
88
+ // These thin wrappers accept a MilestoneScope so callers that already hold a
89
+ // pinned scope never have to re-derive (basePath, milestoneId) separately.
90
+ // The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
91
+ // state.ts are unchanged — only the call surface in guided-flow.ts is migrated.
92
+
93
+ /**
94
+ * Scope-based overload of verifyExpectedArtifact.
95
+ * Uses scope.workspace.projectRoot as the authoritative base path, making
96
+ * the check immune to cwd-drift and worktree-path divergence.
97
+ */
98
+ export function verifyExpectedArtifactForScope(
99
+ scope: MilestoneScope,
100
+ unitType: string,
101
+ unitId: string,
102
+ ): boolean {
103
+ return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
104
+ }
105
+
106
+ /**
107
+ * Scope-based overload of resolveExpectedArtifactPath.
108
+ * Returns the canonical absolute path (or null) using the scope's projectRoot.
109
+ */
110
+ export function resolveExpectedArtifactPathForScope(
111
+ scope: MilestoneScope,
112
+ unitType: string,
113
+ unitId: string,
114
+ ): string | null {
115
+ return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
116
+ }
117
+
118
+ /**
119
+ * Scope-based overload of isGhostMilestone.
120
+ * Binds basePath and milestoneId from the scope, ensuring path resolution
121
+ * uses the canonical project root regardless of the cwd at call time.
122
+ */
123
+ export function isGhostMilestoneByScope(scope: MilestoneScope): boolean {
124
+ return isGhostMilestone(scope.workspace.projectRoot, scope.milestoneId);
125
+ }
126
+
86
127
  function needsPlanV2Gate(state: GSDState): boolean {
87
128
  return state.phase === "executing"
88
129
  || state.phase === "summarizing"
@@ -135,6 +176,13 @@ interface PendingAutoStartEntry {
135
176
  // #4573: counter for how many times the LLM emitted the ready phrase
136
177
  // without writing the required artifacts. Cleared on entry delete/recreate.
137
178
  readyRejectCount?: number;
179
+ // C1: scope is pinned at reservation time so path resolution is immune to
180
+ // cwd-drift between discuss and checkAutoStartAfterDiscuss.
181
+ // TODO(C3): basePath becomes redundant once all consumers migrate to scope.
182
+ scope: MilestoneScope;
183
+ // H1: retry counter for Gate 1b plan-blocked recovery. Capped at
184
+ // MAX_PLAN_BLOCKED_RECOVERIES to prevent infinite recovery loops (#5012).
185
+ planBlockedRecoveryCount: number;
138
186
  }
139
187
 
140
188
  interface PendingDeepProjectSetupEntry {
@@ -152,6 +200,11 @@ interface PendingDeepProjectSetupEntry {
152
200
  // phrase before giving up and asking the user to re-run /gsd.
153
201
  const MAX_READY_REJECTS = 2;
154
202
 
203
+ // H1 (#5012): cap for Gate 1b plan-blocked recovery hints. After this many
204
+ // consecutive recovery attempts the loop is stopped and the user is directed
205
+ // to investigate manually.
206
+ const MAX_PLAN_BLOCKED_RECOVERIES = 3;
207
+
155
208
  // #4573: matches the canonical ready phrase the discuss prompt asks the LLM
156
209
  // to emit. Accepts any M-prefixed milestone ID (three digits + optional
157
210
  // suffix) with optional trailing punctuation.
@@ -187,9 +240,9 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
187
240
  /**
188
241
  * Backward-compat bridge: returns a mutable reference to the entry matching
189
242
  * basePath, or the sole entry when only one session exists.
190
- * Internal use onlyexternal code should use the Map directly.
243
+ * Exported for testinginternal use only in production code.
191
244
  */
192
- function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
245
+ export function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
193
246
  if (basePath) return pendingAutoStartMap.get(basePath) ?? null;
194
247
  if (pendingAutoStartMap.size === 1) return pendingAutoStartMap.values().next().value!;
195
248
  return null;
@@ -233,7 +286,9 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath: string, entries: st
233
286
  * Exported for testing (#2985).
234
287
  */
235
288
  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);
289
+ const ws = createWorkspace(entry.basePath);
290
+ const scope = scopeMilestone(ws, entry.milestoneId);
291
+ pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope } as PendingAutoStartEntry);
237
292
  }
238
293
 
239
294
  /**
@@ -343,6 +398,10 @@ export async function checkDeepProjectSetupAfterTurn(
343
398
  if (!entry) return false;
344
399
 
345
400
  if (entry.currentUnitType && entry.currentUnitId) {
401
+ // TODO(C-future): PendingDeepProjectSetupEntry does not carry a MilestoneScope
402
+ // because deep-project-setup units span non-milestone unit types (discuss-project,
403
+ // discuss-requirements, etc.). Migrate to verifyExpectedArtifactForScope once
404
+ // PendingDeepProjectSetupEntry is extended with a scope field.
346
405
  const artifactReady = verifyExpectedArtifact(entry.currentUnitType, entry.currentUnitId, entry.basePath);
347
406
  if (!artifactReady) {
348
407
  return false;
@@ -426,17 +485,77 @@ export function checkAutoStartAfterDiscuss(): boolean {
426
485
 
427
486
  // Gate 1: Primary milestone must have CONTEXT.md or ROADMAP.md
428
487
  // 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");
488
+ // Use pinned scope (immune to cwd-drift) for existence checks.
489
+ const contextFilePath = entry.scope.contextFile();
490
+ const roadmapFilePath = entry.scope.roadmapFile();
491
+ const contextFile = existsSync(contextFilePath) ? contextFilePath : null;
492
+ const roadmapFile = existsSync(roadmapFilePath) ? roadmapFilePath : null;
431
493
  if (!contextFile && !roadmapFile) return false; // neither artifact yet — keep waiting
432
494
 
495
+ // Gate 1b: Discriminate plan-blocked from discuss-incomplete when the DB row is queued.
496
+ // If the DB is available and the row is still "queued" but CONTEXT.md already exists on
497
+ // disk, the discuss phase completed but gsd_plan_milestone was hard-blocked by the
498
+ // depth-verification gate. Emit a recovery hint so the next agent turn can retry
499
+ // gsd_plan_milestone, then return false (keep blocking auto-start).
500
+ // If CONTEXT.md does not exist (discuss-incomplete), Gate 1 already blocked above.
501
+ if (isDbAvailable()) {
502
+ const dbRow = getMilestone(milestoneId);
503
+ if (dbRow?.status === "queued" && contextFile) {
504
+ if (entry.planBlockedRecoveryCount >= MAX_PLAN_BLOCKED_RECOVERIES) {
505
+ // H1: recovery loop cap reached — stop triggering new turns, escalate to user.
506
+ logWarning(
507
+ "guided",
508
+ `Gate 1b: milestone ${milestoneId} plan-blocked recovery limit reached ` +
509
+ `(${entry.planBlockedRecoveryCount}/${MAX_PLAN_BLOCKED_RECOVERIES}); escalating to user`,
510
+ );
511
+ ctx.ui.notify(
512
+ `Milestone ${milestoneId} plan_milestone has been blocked ${entry.planBlockedRecoveryCount} times. ` +
513
+ `Re-run /gsd to reset the recovery counter, or run /gsd-debug to diagnose without resetting.`,
514
+ "error",
515
+ );
516
+ return false;
517
+ }
518
+ logWarning(
519
+ "guided",
520
+ `Gate 1b: milestone ${milestoneId} queued with CONTEXT.md present — ` +
521
+ `plan_milestone was blocked; emitting recovery hint ` +
522
+ `(attempt ${entry.planBlockedRecoveryCount + 1}/${MAX_PLAN_BLOCKED_RECOVERIES})`,
523
+ );
524
+ ctx.ui.notify(
525
+ `Milestone ${milestoneId}: context file exists but milestone is still queued. ` +
526
+ `Retrying gsd_plan_milestone to complete the blocked planning step.`,
527
+ "warning",
528
+ );
529
+ try {
530
+ pi.sendMessage(
531
+ {
532
+ customType: "gsd-plan-milestone-blocked-recovery",
533
+ content:
534
+ `Milestone ${milestoneId} has ${contextFile} on disk but its DB row is still ` +
535
+ `"queued". The gsd_plan_milestone tool was previously blocked by the ` +
536
+ `depth-verification gate. Call gsd_plan_milestone now to complete the ` +
537
+ `planning phase.`,
538
+ display: false,
539
+ },
540
+ { triggerTurn: true },
541
+ );
542
+ // Increment only after a successful dispatch so transient sendMessage
543
+ // failures do not consume recovery budget.
544
+ entry.planBlockedRecoveryCount += 1;
545
+ } catch (e) {
546
+ logWarning("guided", `Gate 1b recovery sendMessage failed: ${(e as Error).message}`);
547
+ }
548
+ return false;
549
+ }
550
+ }
551
+
433
552
  // Gate 2: STATE.md must exist — written as the last step in the discuss
434
553
  // output phase. This prevents auto-start from firing during Phase 3
435
554
  // (sequential readiness gates for remaining milestones) in multi-milestone
436
555
  // discussions, where M001-CONTEXT.md exists but M002/M003 haven't been
437
556
  // processed yet.
438
- const stateFile = resolveGsdRootFile(basePath, "STATE");
439
- if (!stateFile) return false; // discussion not finalized yet
557
+ const stateFilePath = entry.scope.stateFile();
558
+ if (!existsSync(stateFilePath)) return false; // discussion not finalized yet
440
559
 
441
560
  // Gate 3: Multi-milestone completeness warning
442
561
  // Parse PROJECT.md for milestone sequence, warn if any are missing context.
@@ -469,7 +588,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
469
588
  // The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
470
589
  // When it exists, validate it before auto-starting. Project history alone is
471
590
  // not a reliable signal for the current discussion mode.
472
- const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
591
+ const manifestPath = join(entry.scope.workspace.contract.projectGsd, "DISCUSSION-MANIFEST.json");
473
592
  if (existsSync(manifestPath)) {
474
593
  try {
475
594
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -588,12 +707,39 @@ export function maybeHandleReadyPhraseWithoutFiles(event: { messages: any[] }):
588
707
  const text = extractAssistantText(lastMsg);
589
708
  if (!READY_PHRASE_RE.test(text)) return false;
590
709
 
710
+ // Bust paths.ts cached dir listings before checking for fresh writes. The
711
+ // LLM's Write tool calls do not invalidate paths.ts caches, so a stale
712
+ // listing taken before the milestone dir or its CONTEXT/ROADMAP files
713
+ // existed would falsely report the artifacts as missing and trigger the
714
+ // 3-strike "ready without files" abort even though the writes succeeded.
715
+ clearPathCache();
716
+
591
717
  // Gate: artifacts must still be missing — if they exist, the happy path
592
718
  // already fired and we have nothing to do.
593
719
  const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
594
720
  const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
595
721
  if (contextFile || roadmapFile) return false;
596
722
 
723
+ // Diagnostic: when the cached resolver reports both files missing, also probe
724
+ // the canonical paths with uncached existsSync so we can tell whether the
725
+ // recovery is firing on real-missing files or a path-resolution miss
726
+ // (basePath/symlink mismatch, stale cache despite agent-end-recovery flush,
727
+ // legacy descriptor dir not matching, etc.).
728
+ try {
729
+ const mDir = resolveMilestonePath(basePath, milestoneId);
730
+ const canonicalCtx = mDir ? join(mDir, `${milestoneId}-CONTEXT.md`) : null;
731
+ const canonicalRoadmap = mDir ? join(mDir, `${milestoneId}-ROADMAP.md`) : null;
732
+ logWarning(
733
+ "guided",
734
+ `ready-phrase-reject diagnostic mid=${milestoneId} basePath=${basePath} ` +
735
+ `mDir=${mDir ?? "null"} ` +
736
+ `canonical-ctx=${canonicalCtx ?? "null"} ctx-exists=${canonicalCtx ? existsSync(canonicalCtx) : "n/a"} ` +
737
+ `canonical-roadmap=${canonicalRoadmap ?? "null"} roadmap-exists=${canonicalRoadmap ? existsSync(canonicalRoadmap) : "n/a"}`,
738
+ );
739
+ } catch (e) {
740
+ logWarning("guided", `ready-phrase-reject diagnostic failed: ${(e as Error).message}`);
741
+ }
742
+
597
743
  entry.readyRejectCount = (entry.readyRejectCount ?? 0) + 1;
598
744
 
599
745
  if (entry.readyRejectCount > MAX_READY_REJECTS) {
@@ -696,14 +842,14 @@ export function maybeHandleEmptyIntentTurn(
696
842
  // path, handled by maybeHandleReadyPhraseWithoutFiles.
697
843
  if (READY_PHRASE_RE.test(text)) return false;
698
844
 
699
- // Skip if the LLM is clearly handing back to the user. Last-line `?` is
700
- // the strongest signal, but discuss flows often end with a freeform
701
- // question followed by a closing remark ("…what should we build? I'll
702
- // pick one if you don't care."). Treat ANY non-empty line ending in `?`
703
- // as a question-asked signal false negatives here auto-reply to the
845
+ // Skip if the LLM is clearly handing back to the user. Discuss flows
846
+ // often pose a question and follow it with a conditional intent on the
847
+ // same line ("Did I capture that correctly? If so, I'll write the
848
+ // requirements."). A line-trailing `?` check misses these because the
849
+ // line ends in `.`. Match any sentence-terminating `?` (followed by
850
+ // whitespace or end-of-text) — false negatives here auto-reply to the
704
851
  // user, which is a much worse failure mode than a missed nudge.
705
- const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
706
- if (lines.some((l) => l.endsWith("?"))) return false;
852
+ if (/\?(?:\s|$)/.test(text)) return false;
707
853
 
708
854
  // Must contain a commit-intent phrase — this is the stall we care about.
709
855
  if (!COMMIT_INTENT_RE.test(text)) return false;
@@ -1077,7 +1223,7 @@ export async function showHeadlessMilestoneCreation(
1077
1223
  const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
1078
1224
 
1079
1225
  // Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
1080
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, createdAt: Date.now() });
1226
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId });
1081
1227
 
1082
1228
  // Dispatch as discuss-milestone. The LLM writes PROJECT.md, REQUIREMENTS.md,
1083
1229
  // and CONTEXT.md, then calls gsd_plan_milestone — this is semantically the
@@ -1274,12 +1420,12 @@ export async function showDiscuss(
1274
1420
  const seed = draftContent
1275
1421
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1276
1422
  : basePrompt;
1277
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
1423
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1278
1424
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1279
1425
  } else if (choice === "discuss_fresh") {
1280
1426
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1281
1427
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1282
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
1428
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1283
1429
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1284
1430
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1285
1431
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
@@ -1291,7 +1437,7 @@ export async function showDiscuss(
1291
1437
  const milestoneIds = findMilestoneIds(basePath);
1292
1438
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1293
1439
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1294
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
1440
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
1295
1441
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1296
1442
  }
1297
1443
  return;
@@ -1582,6 +1728,9 @@ function selfHealRuntimeRecords(basePath: string, ctx: ExtensionContext): { clea
1582
1728
  for (const record of records) {
1583
1729
  const { unitType, unitId, phase } = record;
1584
1730
  // Clear records whose expected artifact already exists (completed but not cleaned up)
1731
+ // TODO(C-future): selfHealRuntimeRecords iterates across all unit types (not just milestone
1732
+ // units), so it cannot be converted to resolveExpectedArtifactPathForScope without
1733
+ // first establishing a per-record scope. Migrate once unit runtime records carry scope info.
1585
1734
  const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
1586
1735
  if (artifactPath && existsSync(artifactPath)) {
1587
1736
  clearUnitRuntimeRecord(basePath, unitType, unitId);
@@ -1696,7 +1845,7 @@ async function handleMilestoneActions(
1696
1845
  const milestoneIds = findMilestoneIds(basePath);
1697
1846
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1698
1847
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1699
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1848
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1700
1849
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1701
1850
  `New milestone ${nextId}.`,
1702
1851
  basePath
@@ -1924,7 +2073,7 @@ export async function showSmartEntry(
1924
2073
 
1925
2074
  if (isFirst) {
1926
2075
  // First ever — skip wizard, just ask directly
1927
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2076
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1928
2077
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1929
2078
  `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
1930
2079
  basePath
@@ -1945,7 +2094,7 @@ export async function showSmartEntry(
1945
2094
  });
1946
2095
 
1947
2096
  if (choice === "new_milestone") {
1948
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2097
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1949
2098
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1950
2099
  `New milestone ${nextId}.`,
1951
2100
  basePath
@@ -1959,7 +2108,7 @@ export async function showSmartEntry(
1959
2108
  const milestoneTitle = state.activeMilestone.title;
1960
2109
 
1961
2110
  if (planV2GateDecision === "recover-missing-context") {
1962
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
2111
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1963
2112
  await dispatchWorkflow(
1964
2113
  pi,
1965
2114
  await buildDiscussMilestonePrompt(
@@ -2001,7 +2150,7 @@ export async function showSmartEntry(
2001
2150
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
2002
2151
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
2003
2152
 
2004
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2153
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
2005
2154
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2006
2155
  `New milestone ${nextId}.`,
2007
2156
  basePath
@@ -2053,12 +2202,12 @@ export async function showSmartEntry(
2053
2202
  const seed = draftContent
2054
2203
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
2055
2204
  : basePrompt;
2056
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
2205
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
2057
2206
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
2058
2207
  } else if (choice === "discuss_fresh") {
2059
2208
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
2060
2209
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
2061
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
2210
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
2062
2211
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
2063
2212
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
2064
2213
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -2068,7 +2217,7 @@ export async function showSmartEntry(
2068
2217
  const milestoneIds = findMilestoneIds(basePath);
2069
2218
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
2070
2219
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
2071
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2220
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
2072
2221
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2073
2222
  `New milestone ${nextId}.`,
2074
2223
  basePath
@@ -2133,7 +2282,7 @@ export async function showSmartEntry(
2133
2282
  });
2134
2283
 
2135
2284
  if (choice === "plan") {
2136
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
2285
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
2137
2286
  await dispatchWorkflow(
2138
2287
  pi,
2139
2288
  await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath),
@@ -2153,7 +2302,7 @@ export async function showSmartEntry(
2153
2302
  const milestoneIds = findMilestoneIds(basePath);
2154
2303
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
2155
2304
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
2156
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
2305
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
2157
2306
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
2158
2307
  `New milestone ${nextId}.`,
2159
2308
  basePath
@@ -1,9 +1,8 @@
1
1
  // GSD Markdown Renderer — DB → Markdown file generation
2
2
  //
3
3
  // Transforms DB state into correct markdown files on disk.
4
- // Each render function reads from DB (with disk fallback),
5
- // patches content to match DB status, writes atomically to disk,
6
- // stores updated content in the artifacts table, and invalidates caches.
4
+ // Each render function reads from DB, writes a markdown projection to disk,
5
+ // stores generated content in the artifacts table, and invalidates caches.
7
6
  //
8
7
  // Critical invariant: rendered markdown must round-trip through
9
8
  // parseRoadmap(), parsePlan(), parseSummary() in files.ts.
@@ -85,58 +84,19 @@ function taskSummaryForSlicePlan(description: string): string {
85
84
  }
86
85
 
87
86
  /**
88
- * Load artifact content from DB first, falling back to reading from disk.
89
- * On disk fallback, stores the content in the artifacts table for future use.
90
- * Returns null if content is unavailable from both sources.
87
+ * Load artifact content from the DB. Markdown projections are not authoritative
88
+ * during runtime; when the artifact row is missing, callers regenerate from DB
89
+ * rows instead of patching disk fallback content and storing it back.
91
90
  */
92
91
  function loadArtifactContent(
93
92
  artifactPath: string,
94
- absPath: string | null,
95
- opts: {
96
- artifact_type: string;
97
- milestone_id: string;
98
- slice_id?: string;
99
- task_id?: string;
100
- },
101
93
  ): string | null {
102
- // Try DB first
103
94
  const artifact = getArtifact(artifactPath);
104
95
  if (artifact && artifact.full_content) {
105
96
  return artifact.full_content;
106
97
  }
107
98
 
108
- // Fall back to disk
109
- if (!absPath) {
110
- process.stderr.write(
111
- `markdown-renderer: artifact not found in DB or on disk: ${artifactPath}\n`,
112
- );
113
- return null;
114
- }
115
-
116
- let content: string;
117
- try {
118
- content = readFileSync(absPath, "utf-8");
119
- } catch {
120
- logWarning("renderer", `cannot read file from disk: ${absPath}`);
121
- return null;
122
- }
123
-
124
- // Store in DB for future use (graceful degradation path)
125
- try {
126
- insertArtifact({
127
- path: artifactPath,
128
- artifact_type: opts.artifact_type,
129
- milestone_id: opts.milestone_id,
130
- slice_id: opts.slice_id ?? null,
131
- task_id: opts.task_id ?? null,
132
- full_content: content,
133
- });
134
- } catch {
135
- // Non-fatal: we have the content, DB storage is best-effort
136
- logWarning("renderer", `failed to store disk fallback in DB: ${artifactPath}`);
137
- }
138
-
139
- return content;
99
+ return null;
140
100
  }
141
101
 
142
102
  /**
@@ -507,20 +467,15 @@ export async function renderRoadmapCheckboxes(
507
467
  const absPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
508
468
  const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
509
469
 
510
- // Load content from DB (with disk fallback)
470
+ // Load content from DB; regenerate from DB rows when the artifact is absent.
511
471
  let content: string | null = null;
512
472
  if (artifactPath) {
513
- content = loadArtifactContent(artifactPath, absPath, {
514
- artifact_type: "ROADMAP",
515
- milestone_id: milestoneId,
516
- });
473
+ content = loadArtifactContent(artifactPath);
517
474
  }
518
475
 
519
476
  if (!content) {
520
- process.stderr.write(
521
- `markdown-renderer: no roadmap content available for ${milestoneId}\n`,
522
- );
523
- return false;
477
+ await renderRoadmapFromDb(basePath, milestoneId);
478
+ return true;
524
479
  }
525
480
 
526
481
  // Apply checkbox patches for each slice
@@ -582,18 +537,12 @@ export async function renderPlanCheckboxes(
582
537
 
583
538
  let content: string | null = null;
584
539
  if (artifactPath) {
585
- content = loadArtifactContent(artifactPath, absPath, {
586
- artifact_type: "PLAN",
587
- milestone_id: milestoneId,
588
- slice_id: sliceId,
589
- });
540
+ content = loadArtifactContent(artifactPath);
590
541
  }
591
542
 
592
543
  if (!content) {
593
- process.stderr.write(
594
- `markdown-renderer: no plan content available for ${milestoneId}/${sliceId}\n`,
595
- );
596
- return false;
544
+ await renderPlanFromDb(basePath, milestoneId, sliceId);
545
+ return true;
597
546
  }
598
547
 
599
548
  // Apply checkbox patches for each task