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
@@ -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";
@@ -19,7 +19,7 @@ import { assessInterruptedSession, formatInterruptedSessionRunningMessage, forma
19
19
  import { listUnitRuntimeRecords, clearUnitRuntimeRecord } from "./unit-runtime.js";
20
20
  import { resolveExpectedArtifactPath } from "./auto.js";
21
21
  import { gsdHome } from "./gsd-home.js";
22
- import { gsdRoot, milestonesDir, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, } from "./paths.js";
22
+ import { gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, clearPathCache, } from "./paths.js";
23
23
  import { join } from "node:path";
24
24
  import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
25
25
  import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
@@ -43,10 +43,39 @@ 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
+ // ─── Scope-based validator wrappers ──────────────────────────────────────────
52
+ // These thin wrappers accept a MilestoneScope so callers that already hold a
53
+ // pinned scope never have to re-derive (basePath, milestoneId) separately.
54
+ // The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
55
+ // state.ts are unchanged — only the call surface in guided-flow.ts is migrated.
56
+ /**
57
+ * Scope-based overload of verifyExpectedArtifact.
58
+ * Uses scope.workspace.projectRoot as the authoritative base path, making
59
+ * the check immune to cwd-drift and worktree-path divergence.
60
+ */
61
+ export function verifyExpectedArtifactForScope(scope, unitType, unitId) {
62
+ return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
63
+ }
64
+ /**
65
+ * Scope-based overload of resolveExpectedArtifactPath.
66
+ * Returns the canonical absolute path (or null) using the scope's projectRoot.
67
+ */
68
+ export function resolveExpectedArtifactPathForScope(scope, unitType, unitId) {
69
+ return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
70
+ }
71
+ /**
72
+ * Scope-based overload of isGhostMilestone.
73
+ * Binds basePath and milestoneId from the scope, ensuring path resolution
74
+ * uses the canonical project root regardless of the cwd at call time.
75
+ */
76
+ export function isGhostMilestoneByScope(scope) {
77
+ return isGhostMilestone(scope.workspace.projectRoot, scope.milestoneId);
78
+ }
50
79
  function needsPlanV2Gate(state) {
51
80
  return state.phase === "executing"
52
81
  || state.phase === "summarizing"
@@ -77,6 +106,10 @@ function buildDocsCommitInstruction(_message) {
77
106
  // #4573: cap for how many times we nudge the LLM after a premature ready
78
107
  // phrase before giving up and asking the user to re-run /gsd.
79
108
  const MAX_READY_REJECTS = 2;
109
+ // H1 (#5012): cap for Gate 1b plan-blocked recovery hints. After this many
110
+ // consecutive recovery attempts the loop is stopped and the user is directed
111
+ // to investigate manually.
112
+ const MAX_PLAN_BLOCKED_RECOVERIES = 3;
80
113
  // #4573: matches the canonical ready phrase the discuss prompt asks the LLM
81
114
  // to emit. Accepts any M-prefixed milestone ID (three digits + optional
82
115
  // suffix) with optional trailing punctuation.
@@ -110,9 +143,9 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
110
143
  /**
111
144
  * Backward-compat bridge: returns a mutable reference to the entry matching
112
145
  * basePath, or the sole entry when only one session exists.
113
- * Internal use onlyexternal code should use the Map directly.
146
+ * Exported for testinginternal use only in production code.
114
147
  */
115
- function _getPendingAutoStart(basePath) {
148
+ export function _getPendingAutoStart(basePath) {
116
149
  if (basePath)
117
150
  return pendingAutoStartMap.get(basePath) ?? null;
118
151
  if (pendingAutoStartMap.size === 1)
@@ -157,7 +190,9 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath, entries) {
157
190
  * Exported for testing (#2985).
158
191
  */
159
192
  export function setPendingAutoStart(basePath, entry) {
160
- pendingAutoStartMap.set(basePath, { createdAt: Date.now(), ...entry });
193
+ const ws = createWorkspace(entry.basePath);
194
+ const scope = scopeMilestone(ws, entry.milestoneId);
195
+ pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope });
161
196
  }
162
197
  /**
163
198
  * Clear pending auto-start state.
@@ -249,6 +284,10 @@ export async function checkDeepProjectSetupAfterTurn(_event, ctx, basePath) {
249
284
  if (!entry)
250
285
  return false;
251
286
  if (entry.currentUnitType && entry.currentUnitId) {
287
+ // TODO(C-future): PendingDeepProjectSetupEntry does not carry a MilestoneScope
288
+ // because deep-project-setup units span non-milestone unit types (discuss-project,
289
+ // discuss-requirements, etc.). Migrate to verifyExpectedArtifactForScope once
290
+ // PendingDeepProjectSetupEntry is extended with a scope field.
252
291
  const artifactReady = verifyExpectedArtifact(entry.currentUnitType, entry.currentUnitId, entry.basePath);
253
292
  if (!artifactReady) {
254
293
  return false;
@@ -320,17 +359,61 @@ export function checkAutoStartAfterDiscuss() {
320
359
  const { ctx, pi, basePath, milestoneId, step } = entry;
321
360
  // Gate 1: Primary milestone must have CONTEXT.md or ROADMAP.md
322
361
  // 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");
362
+ // Use pinned scope (immune to cwd-drift) for existence checks.
363
+ const contextFilePath = entry.scope.contextFile();
364
+ const roadmapFilePath = entry.scope.roadmapFile();
365
+ const contextFile = existsSync(contextFilePath) ? contextFilePath : null;
366
+ const roadmapFile = existsSync(roadmapFilePath) ? roadmapFilePath : null;
325
367
  if (!contextFile && !roadmapFile)
326
368
  return false; // neither artifact yet — keep waiting
369
+ // Gate 1b: Discriminate plan-blocked from discuss-incomplete when the DB row is queued.
370
+ // If the DB is available and the row is still "queued" but CONTEXT.md already exists on
371
+ // disk, the discuss phase completed but gsd_plan_milestone was hard-blocked by the
372
+ // depth-verification gate. Emit a recovery hint so the next agent turn can retry
373
+ // gsd_plan_milestone, then return false (keep blocking auto-start).
374
+ // If CONTEXT.md does not exist (discuss-incomplete), Gate 1 already blocked above.
375
+ if (isDbAvailable()) {
376
+ const dbRow = getMilestone(milestoneId);
377
+ if (dbRow?.status === "queued" && contextFile) {
378
+ if (entry.planBlockedRecoveryCount >= MAX_PLAN_BLOCKED_RECOVERIES) {
379
+ // H1: recovery loop cap reached — stop triggering new turns, escalate to user.
380
+ logWarning("guided", `Gate 1b: milestone ${milestoneId} plan-blocked recovery limit reached ` +
381
+ `(${entry.planBlockedRecoveryCount}/${MAX_PLAN_BLOCKED_RECOVERIES}); escalating to user`);
382
+ ctx.ui.notify(`Milestone ${milestoneId} plan_milestone has been blocked ${entry.planBlockedRecoveryCount} times. ` +
383
+ `Re-run /gsd to reset the recovery counter, or run /gsd-debug to diagnose without resetting.`, "error");
384
+ return false;
385
+ }
386
+ logWarning("guided", `Gate 1b: milestone ${milestoneId} queued with CONTEXT.md present — ` +
387
+ `plan_milestone was blocked; emitting recovery hint ` +
388
+ `(attempt ${entry.planBlockedRecoveryCount + 1}/${MAX_PLAN_BLOCKED_RECOVERIES})`);
389
+ ctx.ui.notify(`Milestone ${milestoneId}: context file exists but milestone is still queued. ` +
390
+ `Retrying gsd_plan_milestone to complete the blocked planning step.`, "warning");
391
+ try {
392
+ pi.sendMessage({
393
+ customType: "gsd-plan-milestone-blocked-recovery",
394
+ content: `Milestone ${milestoneId} has ${contextFile} on disk but its DB row is still ` +
395
+ `"queued". The gsd_plan_milestone tool was previously blocked by the ` +
396
+ `depth-verification gate. Call gsd_plan_milestone now to complete the ` +
397
+ `planning phase.`,
398
+ display: false,
399
+ }, { triggerTurn: true });
400
+ // Increment only after a successful dispatch so transient sendMessage
401
+ // failures do not consume recovery budget.
402
+ entry.planBlockedRecoveryCount += 1;
403
+ }
404
+ catch (e) {
405
+ logWarning("guided", `Gate 1b recovery sendMessage failed: ${e.message}`);
406
+ }
407
+ return false;
408
+ }
409
+ }
327
410
  // Gate 2: STATE.md must exist — written as the last step in the discuss
328
411
  // output phase. This prevents auto-start from firing during Phase 3
329
412
  // (sequential readiness gates for remaining milestones) in multi-milestone
330
413
  // discussions, where M001-CONTEXT.md exists but M002/M003 haven't been
331
414
  // processed yet.
332
- const stateFile = resolveGsdRootFile(basePath, "STATE");
333
- if (!stateFile)
415
+ const stateFilePath = entry.scope.stateFile();
416
+ if (!existsSync(stateFilePath))
334
417
  return false; // discussion not finalized yet
335
418
  // Gate 3: Multi-milestone completeness warning
336
419
  // Parse PROJECT.md for milestone sequence, warn if any are missing context.
@@ -362,7 +445,7 @@ export function checkAutoStartAfterDiscuss() {
362
445
  // The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
363
446
  // When it exists, validate it before auto-starting. Project history alone is
364
447
  // not a reliable signal for the current discussion mode.
365
- const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
448
+ const manifestPath = join(entry.scope.workspace.contract.projectGsd, "DISCUSSION-MANIFEST.json");
366
449
  if (existsSync(manifestPath)) {
367
450
  try {
368
451
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -486,12 +569,35 @@ export function maybeHandleReadyPhraseWithoutFiles(event) {
486
569
  const text = extractAssistantText(lastMsg);
487
570
  if (!READY_PHRASE_RE.test(text))
488
571
  return false;
572
+ // Bust paths.ts cached dir listings before checking for fresh writes. The
573
+ // LLM's Write tool calls do not invalidate paths.ts caches, so a stale
574
+ // listing taken before the milestone dir or its CONTEXT/ROADMAP files
575
+ // existed would falsely report the artifacts as missing and trigger the
576
+ // 3-strike "ready without files" abort even though the writes succeeded.
577
+ clearPathCache();
489
578
  // Gate: artifacts must still be missing — if they exist, the happy path
490
579
  // already fired and we have nothing to do.
491
580
  const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
492
581
  const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
493
582
  if (contextFile || roadmapFile)
494
583
  return false;
584
+ // Diagnostic: when the cached resolver reports both files missing, also probe
585
+ // the canonical paths with uncached existsSync so we can tell whether the
586
+ // recovery is firing on real-missing files or a path-resolution miss
587
+ // (basePath/symlink mismatch, stale cache despite agent-end-recovery flush,
588
+ // legacy descriptor dir not matching, etc.).
589
+ try {
590
+ const mDir = resolveMilestonePath(basePath, milestoneId);
591
+ const canonicalCtx = mDir ? join(mDir, `${milestoneId}-CONTEXT.md`) : null;
592
+ const canonicalRoadmap = mDir ? join(mDir, `${milestoneId}-ROADMAP.md`) : null;
593
+ logWarning("guided", `ready-phrase-reject diagnostic mid=${milestoneId} basePath=${basePath} ` +
594
+ `mDir=${mDir ?? "null"} ` +
595
+ `canonical-ctx=${canonicalCtx ?? "null"} ctx-exists=${canonicalCtx ? existsSync(canonicalCtx) : "n/a"} ` +
596
+ `canonical-roadmap=${canonicalRoadmap ?? "null"} roadmap-exists=${canonicalRoadmap ? existsSync(canonicalRoadmap) : "n/a"}`);
597
+ }
598
+ catch (e) {
599
+ logWarning("guided", `ready-phrase-reject diagnostic failed: ${e.message}`);
600
+ }
495
601
  entry.readyRejectCount = (entry.readyRejectCount ?? 0) + 1;
496
602
  if (entry.readyRejectCount > MAX_READY_REJECTS) {
497
603
  // Give up: clear state and tell the user to re-run /gsd. Avoids an
@@ -576,14 +682,14 @@ export function maybeHandleEmptyIntentTurn(event, isAuto) {
576
682
  // path, handled by maybeHandleReadyPhraseWithoutFiles.
577
683
  if (READY_PHRASE_RE.test(text))
578
684
  return false;
579
- // Skip if the LLM is clearly handing back to the user. Last-line `?` is
580
- // the strongest signal, but discuss flows often end with a freeform
581
- // question followed by a closing remark ("…what should we build? I'll
582
- // pick one if you don't care."). Treat ANY non-empty line ending in `?`
583
- // as a question-asked signal false negatives here auto-reply to the
685
+ // Skip if the LLM is clearly handing back to the user. Discuss flows
686
+ // often pose a question and follow it with a conditional intent on the
687
+ // same line ("Did I capture that correctly? If so, I'll write the
688
+ // requirements."). A line-trailing `?` check misses these because the
689
+ // line ends in `.`. Match any sentence-terminating `?` (followed by
690
+ // whitespace or end-of-text) — false negatives here auto-reply to the
584
691
  // user, which is a much worse failure mode than a missed nudge.
585
- const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
586
- if (lines.some((l) => l.endsWith("?")))
692
+ if (/\?(?:\s|$)/.test(text))
587
693
  return false;
588
694
  // Must contain a commit-intent phrase — this is the stall we care about.
589
695
  if (!COMMIT_INTENT_RE.test(text))
@@ -873,7 +979,7 @@ export async function showHeadlessMilestoneCreation(ctx, pi, basePath, seedConte
873
979
  // Build and dispatch the headless discuss prompt
874
980
  const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
875
981
  // Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
876
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, createdAt: Date.now() });
982
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId });
877
983
  // Dispatch as discuss-milestone. The LLM writes PROJECT.md, REQUIREMENTS.md,
878
984
  // and CONTEXT.md, then calls gsd_plan_milestone — this is semantically the
879
985
  // discuss path, just non-interactive. Using "plan-milestone" here caused
@@ -1039,13 +1145,13 @@ export async function showDiscuss(ctx, pi, basePath) {
1039
1145
  const seed = draftContent
1040
1146
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1041
1147
  : basePrompt;
1042
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
1148
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1043
1149
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1044
1150
  }
1045
1151
  else if (choice === "discuss_fresh") {
1046
1152
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1047
1153
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1048
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
1154
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
1049
1155
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1050
1156
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1051
1157
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
@@ -1058,7 +1164,7 @@ export async function showDiscuss(ctx, pi, basePath) {
1058
1164
  const milestoneIds = findMilestoneIds(basePath);
1059
1165
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1060
1166
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1061
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
1167
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
1062
1168
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1063
1169
  }
1064
1170
  return;
@@ -1312,6 +1418,9 @@ function selfHealRuntimeRecords(basePath, ctx) {
1312
1418
  for (const record of records) {
1313
1419
  const { unitType, unitId, phase } = record;
1314
1420
  // Clear records whose expected artifact already exists (completed but not cleaned up)
1421
+ // TODO(C-future): selfHealRuntimeRecords iterates across all unit types (not just milestone
1422
+ // units), so it cannot be converted to resolveExpectedArtifactPathForScope without
1423
+ // first establishing a per-record scope. Migrate once unit runtime records carry scope info.
1315
1424
  const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
1316
1425
  if (artifactPath && existsSync(artifactPath)) {
1317
1426
  clearUnitRuntimeRecord(basePath, unitType, unitId);
@@ -1414,7 +1523,7 @@ async function handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneT
1414
1523
  const milestoneIds = findMilestoneIds(basePath);
1415
1524
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1416
1525
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1417
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1526
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1418
1527
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1419
1528
  return true;
1420
1529
  }
@@ -1619,7 +1728,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1619
1728
  const isFirst = milestoneIds.length === 0;
1620
1729
  if (isFirst) {
1621
1730
  // First ever — skip wizard, just ask directly
1622
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1731
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1623
1732
  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");
1624
1733
  }
1625
1734
  else {
@@ -1637,7 +1746,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1637
1746
  notYetMessage: "Run /gsd when ready.",
1638
1747
  });
1639
1748
  if (choice === "new_milestone") {
1640
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1749
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1641
1750
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1642
1751
  }
1643
1752
  }
@@ -1646,7 +1755,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1646
1755
  const milestoneId = state.activeMilestone.id;
1647
1756
  const milestoneTitle = state.activeMilestone.title;
1648
1757
  if (planV2GateDecision === "recover-missing-context") {
1649
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1758
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1650
1759
  await dispatchWorkflow(pi, await buildDiscussMilestonePrompt(milestoneId, milestoneTitle, basePath, getStructuredQuestionsAvailability(pi, ctx)), "gsd-discuss", ctx, "discuss-milestone");
1651
1760
  return;
1652
1761
  }
@@ -1674,7 +1783,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1674
1783
  const milestoneIds = findMilestoneIds(basePath);
1675
1784
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1676
1785
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1677
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1786
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1678
1787
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1679
1788
  }
1680
1789
  else if (choice === "status") {
@@ -1721,13 +1830,13 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1721
1830
  const seed = draftContent
1722
1831
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1723
1832
  : basePrompt;
1724
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1833
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1725
1834
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1726
1835
  }
1727
1836
  else if (choice === "discuss_fresh") {
1728
1837
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1729
1838
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1730
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1839
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1731
1840
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1732
1841
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1733
1842
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -1738,7 +1847,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1738
1847
  const milestoneIds = findMilestoneIds(basePath);
1739
1848
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1740
1849
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1741
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1850
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1742
1851
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1743
1852
  }
1744
1853
  return;
@@ -1794,7 +1903,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1794
1903
  notYetMessage: "Run /gsd when ready.",
1795
1904
  });
1796
1905
  if (choice === "plan") {
1797
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1906
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1798
1907
  await dispatchWorkflow(pi, await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath), "gsd-run", ctx, "plan-milestone");
1799
1908
  }
1800
1909
  else if (choice === "discuss") {
@@ -1810,7 +1919,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1810
1919
  const milestoneIds = findMilestoneIds(basePath);
1811
1920
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1812
1921
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
1813
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1922
+ setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1814
1923
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1815
1924
  }
1816
1925
  else if (choice === "discard_milestone") {
@@ -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.
@@ -60,45 +59,16 @@ function taskSummaryForSlicePlan(description) {
60
59
  return firstBlock || beforeHeading;
61
60
  }
62
61
  /**
63
- * Load artifact content from DB first, falling back to reading from disk.
64
- * On disk fallback, stores the content in the artifacts table for future use.
65
- * Returns null if content is unavailable from both sources.
62
+ * Load artifact content from the DB. Markdown projections are not authoritative
63
+ * during runtime; when the artifact row is missing, callers regenerate from DB
64
+ * rows instead of patching disk fallback content and storing it back.
66
65
  */
67
- function loadArtifactContent(artifactPath, absPath, opts) {
68
- // Try DB first
66
+ function loadArtifactContent(artifactPath) {
69
67
  const artifact = getArtifact(artifactPath);
70
68
  if (artifact && artifact.full_content) {
71
69
  return artifact.full_content;
72
70
  }
73
- // Fall back to disk
74
- if (!absPath) {
75
- process.stderr.write(`markdown-renderer: artifact not found in DB or on disk: ${artifactPath}\n`);
76
- return null;
77
- }
78
- let content;
79
- try {
80
- content = readFileSync(absPath, "utf-8");
81
- }
82
- catch {
83
- logWarning("renderer", `cannot read file from disk: ${absPath}`);
84
- return null;
85
- }
86
- // Store in DB for future use (graceful degradation path)
87
- try {
88
- insertArtifact({
89
- path: artifactPath,
90
- artifact_type: opts.artifact_type,
91
- milestone_id: opts.milestone_id,
92
- slice_id: opts.slice_id ?? null,
93
- task_id: opts.task_id ?? null,
94
- full_content: content,
95
- });
96
- }
97
- catch {
98
- // Non-fatal: we have the content, DB storage is best-effort
99
- logWarning("renderer", `failed to store disk fallback in DB: ${artifactPath}`);
100
- }
101
- return content;
71
+ return null;
102
72
  }
103
73
  /**
104
74
  * Write rendered content to disk and update the artifacts table.
@@ -401,17 +371,14 @@ export async function renderRoadmapCheckboxes(basePath, milestoneId) {
401
371
  }
402
372
  const absPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
403
373
  const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
404
- // Load content from DB (with disk fallback)
374
+ // Load content from DB; regenerate from DB rows when the artifact is absent.
405
375
  let content = null;
406
376
  if (artifactPath) {
407
- content = loadArtifactContent(artifactPath, absPath, {
408
- artifact_type: "ROADMAP",
409
- milestone_id: milestoneId,
410
- });
377
+ content = loadArtifactContent(artifactPath);
411
378
  }
412
379
  if (!content) {
413
- process.stderr.write(`markdown-renderer: no roadmap content available for ${milestoneId}\n`);
414
- return false;
380
+ await renderRoadmapFromDb(basePath, milestoneId);
381
+ return true;
415
382
  }
416
383
  // Apply checkbox patches for each slice
417
384
  let updated = content;
@@ -454,15 +421,11 @@ export async function renderPlanCheckboxes(basePath, milestoneId, sliceId) {
454
421
  const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
455
422
  let content = null;
456
423
  if (artifactPath) {
457
- content = loadArtifactContent(artifactPath, absPath, {
458
- artifact_type: "PLAN",
459
- milestone_id: milestoneId,
460
- slice_id: sliceId,
461
- });
424
+ content = loadArtifactContent(artifactPath);
462
425
  }
463
426
  if (!content) {
464
- process.stderr.write(`markdown-renderer: no plan content available for ${milestoneId}/${sliceId}\n`);
465
- return false;
427
+ await renderPlanFromDb(basePath, milestoneId, sliceId);
428
+ return true;
466
429
  }
467
430
  // Apply checkbox patches for each task
468
431
  let updated = content;