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
@@ -1,5 +1,5 @@
1
1
  // GSD Extension — State Derivation
2
- // DB-primary state derivation with filesystem fallback for unmigrated projects.
2
+ // DB-authoritative runtime derivation with explicit legacy filesystem fallback.
3
3
  // Pure TypeScript, zero Pi dependencies.
4
4
  import { parseRoadmap, parsePlan, } from './parsers-legacy.js';
5
5
  import { parseSummary, loadFile, parseRequirementCounts, parseContextDependsOn, } from './files.js';
@@ -9,14 +9,13 @@ import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
9
9
  import { isClosedStatus, isDeferredStatus } from './status-guards.js';
10
10
  import { nativeBatchParseGsdFiles } from './native-parser-bridge.js';
11
11
  import { join, resolve } from 'path';
12
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
12
+ import { existsSync, readdirSync } from 'node:fs';
13
13
  import { debugCount, debugTime } from './debug-logger.js';
14
- import { logWarning, logError } from './workflow-logger.js';
14
+ import { logWarning } from './workflow-logger.js';
15
15
  import { extractVerdict } from './verdict-parser.js';
16
- import { loadEffectiveGSDPreferences } from './preferences.js';
17
16
  import { detectPendingEscalation } from './escalation.js';
18
17
  import { isTerminalMilestoneSummaryContent } from './milestone-summary-classifier.js';
19
- import { isDbAvailable, wasDbOpenAttempted, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice, insertMilestone, insertSlice, insertTask, updateSliceStatus, updateTaskStatus, getPendingGateCountForTurn, autoHealSketchFlags, } from './gsd-db.js';
18
+ import { isDbAvailable, wasDbOpenAttempted, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice, getRequirementCounts, getLatestAssessmentByScope, getPendingGateCountForTurn, } from './gsd-db.js';
20
19
  /**
21
20
  * A "ghost" milestone directory contains only META.json (and no substantive
22
21
  * files like CONTEXT, CONTEXT-DRAFT, ROADMAP, or SUMMARY). These appear when
@@ -145,9 +144,16 @@ export function invalidateStateCache() {
145
144
  * Returns the ID of the first incomplete milestone, or null if all are complete.
146
145
  */
147
146
  export async function getActiveMilestoneId(basePath) {
148
- // Parallel worker isolation
149
- const milestoneLock = process.env.GSD_MILESTONE_LOCK;
147
+ // Parallel worker isolation. Normal DB state derivation remains DB-only;
148
+ // lock env vars are execution routing for explicit worker processes.
149
+ const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
150
150
  if (milestoneLock) {
151
+ if (isDbAvailable()) {
152
+ const locked = getAllMilestones().find(m => m.id === milestoneLock);
153
+ if (!locked || isClosedStatus(locked.status) || locked.status === "parked")
154
+ return null;
155
+ return locked.id;
156
+ }
151
157
  const milestoneIds = findMilestoneIds(basePath);
152
158
  if (!milestoneIds.includes(milestoneLock))
153
159
  return null;
@@ -160,14 +166,7 @@ export async function getActiveMilestoneId(basePath) {
160
166
  if (isDbAvailable()) {
161
167
  const allMilestones = getAllMilestones();
162
168
  if (allMilestones.length > 0) {
163
- // Respect queue-order.json so /gsd queue reordering is honored (#2556).
164
- // Without this, the DB path uses lexicographic sort while the dispatch
165
- // guard uses queue order — causing a deadlock.
166
- const customOrder = loadQueueOrder(basePath);
167
- const sortedIds = sortByQueueOrder(allMilestones.map(m => m.id), customOrder);
168
- const byId = new Map(allMilestones.map(m => [m.id, m]));
169
- for (const id of sortedIds) {
170
- const m = byId.get(id);
169
+ for (const m of allMilestones) {
171
170
  if (isClosedStatus(m.status) || m.status === "parked")
172
171
  continue;
173
172
  return m.id;
@@ -202,12 +201,12 @@ export async function getActiveMilestoneId(basePath) {
202
201
  return null;
203
202
  }
204
203
  /**
205
- * Reconstruct GSD state from DB (primary) or filesystem (fallback).
204
+ * Reconstruct GSD state from the authoritative DB.
206
205
  * STATE.md is a rendered cache of this output.
207
206
  *
208
207
  * When DB is available, queries milestone/slice/task tables directly.
209
- * Falls back to filesystem parsing for unmigrated projects or when DB
210
- * has zero milestones (e.g. first run before migration).
208
+ * Legacy filesystem parsing is available only through an explicit opt-in for
209
+ * tests/recovery flows; runtime must not silently infer state from markdown.
211
210
  */
212
211
  export async function deriveState(basePath) {
213
212
  // Return cached result if within the TTL window for the same basePath
@@ -218,47 +217,39 @@ export async function deriveState(basePath) {
218
217
  }
219
218
  const stopTimer = debugTime("derive-state-impl");
220
219
  let result;
221
- // Dual-path: try DB-backed derivation first when hierarchy tables are populated
220
+ // DB-backed derivation is authoritative whenever the DB is open.
221
+ // Markdown fallback is explicit-only; runtime degrade must not infer state
222
+ // from ROADMAP.md, PLAN.md, SUMMARY.md, REQUIREMENTS.md, or flag files.
222
223
  if (isDbAvailable()) {
223
- let dbMilestones = getAllMilestones();
224
- // Disk→DB reconciliation when DB is empty but disk has milestones (#2631).
225
- // deriveStateFromDb() does its own reconciliation, but deriveState() skips
226
- // it entirely when the DB is empty. Sync here so the DB path is used when
227
- // disk milestones exist but haven't been migrated yet.
228
- if (dbMilestones.length === 0) {
229
- const diskIds = findMilestoneIds(basePath);
230
- let synced = false;
231
- for (const diskId of diskIds) {
232
- if (!isGhostMilestone(basePath, diskId)) {
233
- insertMilestone(diskMilestoneInsert(basePath, diskId));
234
- synced = true;
235
- }
236
- }
237
- if (synced)
238
- dbMilestones = getAllMilestones();
239
- }
240
- if (dbMilestones.length > 0) {
241
- const stopDbTimer = debugTime("derive-state-db");
242
- result = await deriveStateFromDb(basePath);
243
- stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
244
- _telemetry.dbDeriveCount++;
245
- }
246
- else {
247
- // DB open but no milestones on disk either — use filesystem path
248
- result = await _deriveStateImpl(basePath);
249
- _telemetry.markdownDeriveCount++;
250
- }
224
+ const stopDbTimer = debugTime("derive-state-db");
225
+ result = await deriveStateFromDb(basePath);
226
+ stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
227
+ _telemetry.dbDeriveCount++;
251
228
  }
252
- else {
253
- // Only warn when DB initialization was attempted and failed — not when
254
- // the DB simply hasn't been opened yet (e.g. during before_agent_start
255
- // context injection which runs before any tool invocation opens the DB).
229
+ else if (process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK === "1") {
256
230
  if (wasDbOpenAttempted()) {
257
- logWarning("state", "DB unavailable — using filesystem state derivation (degraded mode)");
231
+ logWarning("state", "DB unavailable — using explicit legacy filesystem state derivation");
258
232
  }
259
233
  result = await _deriveStateImpl(basePath);
260
234
  _telemetry.markdownDeriveCount++;
261
235
  }
236
+ else {
237
+ if (wasDbOpenAttempted()) {
238
+ logWarning("state", "DB unavailable — refusing implicit markdown state derivation");
239
+ }
240
+ result = {
241
+ activeMilestone: null,
242
+ activeSlice: null,
243
+ activeTask: null,
244
+ phase: "pre-planning",
245
+ recentDecisions: [],
246
+ blockers: ["DB unavailable — runtime markdown state derivation is disabled"],
247
+ nextAction: "Open or create the canonical GSD database before deriving workflow state.",
248
+ registry: [],
249
+ requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
250
+ progress: { milestones: { done: 0, total: 0 } },
251
+ };
252
+ }
262
253
  stopTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
263
254
  debugCount("deriveStateCalls");
264
255
  _stateCache = { basePath, result, timestamp: Date.now() };
@@ -288,111 +279,11 @@ function extractContextTitle(content, fallback) {
288
279
  // isStatusDone replaced by isClosedStatus from status-guards.ts (single source of truth).
289
280
  // Alias kept for backward compatibility within this file.
290
281
  const isStatusDone = isClosedStatus;
291
- function loadSync(path) {
292
- if (!path)
293
- return null;
294
- try {
295
- return readFileSync(path, "utf-8");
296
- }
297
- catch {
298
- return null;
299
- }
300
- }
301
- function diskMilestoneInsert(basePath, mid) {
302
- const contextContent = loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT"));
303
- const draftContent = !contextContent ? loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT")) : null;
304
- const roadmapContent = loadSync(resolveMilestoneFile(basePath, mid, "ROADMAP"));
305
- const summaryContent = loadSync(resolveMilestoneFile(basePath, mid, "SUMMARY"));
306
- const roadmap = roadmapContent ? parseRoadmap(roadmapContent) : null;
307
- const summary = summaryContent ? parseSummary(summaryContent) : null;
308
- const summaryTerminal = summaryContent != null && isTerminalMilestoneSummaryContent(summaryContent);
309
- const parked = resolveMilestoneFile(basePath, mid, "PARKED") !== null;
310
- return {
311
- id: mid,
312
- title: roadmap
313
- ? stripMilestonePrefix(roadmap.title)
314
- : (contextContent || draftContent)
315
- ? extractContextTitle(contextContent || draftContent, mid)
316
- : (summary?.title || mid),
317
- status: parked ? "parked" : summaryTerminal ? "complete" : "active",
318
- depends_on: parseContextDependsOn(contextContent ?? draftContent),
319
- };
320
- }
321
282
  /**
322
283
  * Derive GSD state from the milestones/slices/tasks DB tables.
323
- * Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
324
- * are still checked on the filesystem since they aren't in DB tables.
325
- * Requirements also stay file-based via parseRequirementCounts().
326
- *
327
- * Must produce field-identical GSDState to _deriveStateImpl() for the same project.
284
+ * Markdown files are projections only in this path; they are never imported,
285
+ * reconciled, or used as completion signals.
328
286
  */
329
- function reconcileDiskToDb(basePath) {
330
- let allMilestones = getAllMilestones();
331
- const dbIdSet = new Set(allMilestones.map(m => m.id));
332
- const diskIds = findMilestoneIds(basePath);
333
- let synced = false;
334
- for (const diskId of diskIds) {
335
- if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
336
- insertMilestone(diskMilestoneInsert(basePath, diskId));
337
- synced = true;
338
- }
339
- }
340
- if (synced)
341
- allMilestones = getAllMilestones();
342
- for (const mid of diskIds) {
343
- if (isGhostMilestone(basePath, mid))
344
- continue;
345
- const roadmapPath = resolveMilestoneFile(basePath, mid, "ROADMAP");
346
- if (!roadmapPath)
347
- continue;
348
- const dbSlices = getMilestoneSlices(mid);
349
- const dbSliceIds = new Set(dbSlices.map(s => s.id));
350
- let roadmapContent;
351
- try {
352
- roadmapContent = readFileSync(roadmapPath, "utf-8");
353
- }
354
- catch (err) {
355
- logWarning("state", "reconcileDiskToDb: roadmap read failed, skipping milestone", {
356
- mid,
357
- error: err.message,
358
- });
359
- continue;
360
- }
361
- const parsed = parseRoadmap(roadmapContent);
362
- for (const s of parsed.slices) {
363
- if (dbSliceIds.has(s.id))
364
- continue;
365
- const summaryPath = resolveSliceFile(basePath, mid, s.id, "SUMMARY");
366
- const sliceStatus = (s.done || summaryPath) ? "complete" : "pending";
367
- insertSlice({
368
- id: s.id, milestoneId: mid, title: s.title,
369
- status: sliceStatus, risk: s.risk,
370
- depends: s.depends, demo: s.demo,
371
- });
372
- }
373
- // Reconcile stale *existing* slice rows (#3599): a slice row may exist in
374
- // the DB with status "pending" even though disk artifacts (SUMMARY) prove
375
- // completion — the same class of desync that task-level reconciliation
376
- // (further below) already handles. Without this, the dependency resolver
377
- // builds doneSliceIds from stale DB rows and downstream slices stay blocked
378
- // forever with "No slice eligible".
379
- for (const dbSlice of dbSlices) {
380
- if (isStatusDone(dbSlice.status))
381
- continue;
382
- const summaryPath = resolveSliceFile(basePath, mid, dbSlice.id, "SUMMARY");
383
- if (summaryPath) {
384
- try {
385
- updateSliceStatus(mid, dbSlice.id, "complete");
386
- logWarning("reconcile", `slice ${mid}/${dbSlice.id} status reconciled from "${dbSlice.status}" to "complete" (#3599)`, { mid, sid: dbSlice.id });
387
- }
388
- catch (e) {
389
- logError("reconcile", `failed to update slice ${dbSlice.id}`, { sid: dbSlice.id, error: e.message });
390
- }
391
- }
392
- }
393
- }
394
- return allMilestones;
395
- }
396
287
  function buildCompletenessSet(basePath, milestones) {
397
288
  const completeMilestoneIds = new Set();
398
289
  const parkedMilestoneIds = new Set();
@@ -401,8 +292,7 @@ function buildCompletenessSet(basePath, milestones) {
401
292
  // (crashed complete-milestone turn, partial merge, manual edit) must not
402
293
  // flip derived state to complete and cascade into a false auto-merge (#4179).
403
294
  for (const m of milestones) {
404
- const parkedFile = resolveMilestoneFile(basePath, m.id, "PARKED");
405
- if (parkedFile || m.status === 'parked') {
295
+ if (m.status === 'parked') {
406
296
  parkedMilestoneIds.add(m.id);
407
297
  continue;
408
298
  }
@@ -426,37 +316,16 @@ async function buildRegistryAndFindActive(basePath, milestones, completeMileston
426
316
  continue;
427
317
  }
428
318
  const slices = getMilestoneSlices(m.id);
429
- if (slices.length === 0 && !isStatusDone(m.status) && m.status !== 'queued') {
430
- if (isGhostMilestone(basePath, m.id))
431
- continue;
432
- }
433
319
  // DB-authoritative completeness (#4179): only trust completeMilestoneIds,
434
320
  // which is itself derived from DB status. SUMMARY-file presence alone must
435
- // not imply completion. The summary file may still be consulted below as a
436
- // title source for legitimately-complete milestones whose DB row has no title.
321
+ // not imply completion.
437
322
  if (completeMilestoneIds.has(m.id)) {
438
- let title = stripMilestonePrefix(m.title) || m.id;
439
- if (!m.title) {
440
- const summaryFile = resolveMilestoneFile(basePath, m.id, "SUMMARY");
441
- if (summaryFile) {
442
- const summaryContent = await loadFile(summaryFile);
443
- if (summaryContent) {
444
- title = parseSummary(summaryContent).title || m.id;
445
- }
446
- }
447
- }
323
+ const title = stripMilestonePrefix(m.title) || m.id;
448
324
  registry.push({ id: m.id, title, status: 'complete' });
449
325
  continue;
450
326
  }
451
327
  const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
452
- let title = stripMilestonePrefix(m.title) || m.id;
453
- if (title === m.id) {
454
- const contextFile = resolveMilestoneFile(basePath, m.id, "CONTEXT");
455
- const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
456
- const contextContent = contextFile ? await loadFile(contextFile) : null;
457
- const draftContent = draftFile && !contextContent ? await loadFile(draftFile) : null;
458
- title = extractContextTitle(contextContent || draftContent, m.id);
459
- }
328
+ const title = stripMilestonePrefix(m.title) || m.id;
460
329
  if (!activeMilestoneFound) {
461
330
  const deps = m.depends_on;
462
331
  const depsUnmet = deps.some(dep => !completeMilestoneIds.has(dep));
@@ -465,38 +334,20 @@ async function buildRegistryAndFindActive(basePath, milestones, completeMileston
465
334
  continue;
466
335
  }
467
336
  if (m.status === 'queued' && slices.length === 0) {
468
- const contextFile = resolveMilestoneFile(basePath, m.id, "CONTEXT");
469
- const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
470
- if (!contextFile && !draftFile) {
471
- if (!firstDeferredQueuedShell) {
472
- firstDeferredQueuedShell = { id: m.id, title, deps };
473
- }
474
- registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
475
- continue;
337
+ if (!firstDeferredQueuedShell) {
338
+ firstDeferredQueuedShell = { id: m.id, title, deps };
476
339
  }
340
+ registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
341
+ continue;
477
342
  }
478
343
  if (allSlicesDone) {
479
- const validationFile = resolveMilestoneFile(basePath, m.id, "VALIDATION");
480
- const validationContent = validationFile ? await loadFile(validationFile) : null;
481
- const validationTerminal = validationContent ? isValidationTerminal(validationContent) : false;
482
- // DB-authoritative (#4179): completeness is already decided by
483
- // completeMilestoneIds above. If we reached this branch, the DB says
484
- // the milestone is NOT complete — so any SUMMARY file on disk is an
485
- // orphan (crashed complete-milestone, partial merge, manual edit) and
486
- // must not short-circuit this path. When validation is terminal, fall
487
- // through to the default active-push below so `complete-milestone` can
488
- // re-run idempotently.
489
- if (!validationTerminal) {
490
- activeMilestone = { id: m.id, title };
491
- activeMilestoneSlices = slices;
492
- activeMilestoneFound = true;
493
- registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
494
- continue;
495
- }
344
+ activeMilestone = { id: m.id, title };
345
+ activeMilestoneSlices = slices;
346
+ activeMilestoneFound = true;
347
+ registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
348
+ continue;
496
349
  }
497
- const contextFile = resolveMilestoneFile(basePath, m.id, "CONTEXT");
498
- const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
499
- if (!contextFile && draftFile)
350
+ if (m.status === 'needs-discussion')
500
351
  activeMilestoneHasDraft = true;
501
352
  activeMilestone = { id: m.id, title };
502
353
  activeMilestoneSlices = slices;
@@ -575,10 +426,9 @@ function handleNoActiveMilestone(registry, requirements, milestoneProgress) {
575
426
  };
576
427
  }
577
428
  async function handleAllSlicesDone(basePath, activeMilestone, registry, requirements, milestoneProgress, sliceProgress) {
578
- const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
579
- const validationContent = validationFile ? await loadFile(validationFile) : null;
580
- const validationTerminal = validationContent ? isValidationTerminal(validationContent) : false;
581
- const verdict = validationContent ? extractVerdict(validationContent) : undefined;
429
+ const validation = getLatestAssessmentByScope(activeMilestone.id, "milestone-validation");
430
+ const verdict = typeof validation?.status === "string" ? validation.status : undefined;
431
+ const validationTerminal = verdict != null && verdict !== "";
582
432
  if (!validationTerminal) {
583
433
  return {
584
434
  activeMilestone, activeSlice: null, activeTask: null,
@@ -617,7 +467,7 @@ async function handleAllSlicesDone(basePath, activeMilestone, registry, requirem
617
467
  }
618
468
  function resolveSliceDependencies(activeMilestoneSlices) {
619
469
  const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id));
620
- const sliceLock = process.env.GSD_SLICE_LOCK;
470
+ const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
621
471
  if (sliceLock) {
622
472
  const lockedSlice = activeMilestoneSlices.find(s => s.id === sliceLock);
623
473
  if (lockedSlice) {
@@ -639,111 +489,23 @@ function resolveSliceDependencies(activeMilestoneSlices) {
639
489
  }
640
490
  return { activeSlice: null, activeSliceRow: null };
641
491
  }
642
- async function reconcileSliceTasks(basePath, milestoneId, sliceId, planFile) {
643
- let tasks = getSliceTasks(milestoneId, sliceId);
644
- // #3600/#4974: import missing plan-file tasks even when the DB already has
645
- // a partial task set. Existing DB task statuses stay authoritative.
646
- if (planFile) {
647
- try {
648
- const planContent = await loadFile(planFile);
649
- if (planContent) {
650
- const diskPlan = parsePlan(planContent);
651
- if (diskPlan.tasks.length > 0) {
652
- const dbTaskIds = new Set(tasks.map(t => t.id));
653
- let inserted = 0;
654
- for (let i = 0; i < diskPlan.tasks.length; i++) {
655
- const t = diskPlan.tasks[i];
656
- if (dbTaskIds.has(t.id))
657
- continue;
658
- try {
659
- insertTask({
660
- id: t.id,
661
- sliceId,
662
- milestoneId,
663
- title: t.title,
664
- status: t.done ? 'complete' : 'pending',
665
- sequence: i + 1,
666
- });
667
- inserted++;
668
- }
669
- catch (insertErr) {
670
- logWarning("reconcile", `failed to insert task ${t.id} from plan file: ${insertErr instanceof Error ? insertErr.message : String(insertErr)}`);
671
- }
672
- }
673
- if (inserted > 0) {
674
- tasks = getSliceTasks(milestoneId, sliceId);
675
- logWarning("reconcile", `imported ${inserted} missing task(s) from plan file for ${milestoneId}/${sliceId}`, { mid: milestoneId, sid: sliceId });
676
- }
677
- }
678
- }
679
- }
680
- catch (err) {
681
- logError("reconcile", `plan-file task import failed for ${milestoneId}/${sliceId}: ${err instanceof Error ? err.message : String(err)}`);
682
- }
683
- }
684
- let reconciled = false;
685
- for (const t of tasks) {
686
- if (isStatusDone(t.status))
687
- continue;
688
- const summaryPath = resolveTaskFile(basePath, milestoneId, sliceId, t.id, "SUMMARY");
689
- if (summaryPath && existsSync(summaryPath)) {
690
- try {
691
- updateTaskStatus(milestoneId, sliceId, t.id, "complete", new Date().toISOString());
692
- logWarning("reconcile", `task ${milestoneId}/${sliceId}/${t.id} status reconciled from "${t.status}" to "complete" (#2514)`, { mid: milestoneId, sid: sliceId, tid: t.id });
693
- reconciled = true;
694
- }
695
- catch (e) {
696
- logError("reconcile", `failed to update task ${t.id}`, { tid: t.id, error: e.message });
697
- }
698
- }
699
- }
700
- if (reconciled) {
701
- tasks = getSliceTasks(milestoneId, sliceId);
702
- }
703
- return tasks;
704
- }
705
492
  async function detectBlockers(basePath, milestoneId, sliceId, tasks) {
706
493
  const completedTasks = tasks.filter(t => isStatusDone(t.status));
707
494
  for (const ct of completedTasks) {
708
495
  if (ct.blocker_discovered) {
709
496
  return ct.id;
710
497
  }
711
- const summaryFile = resolveTaskFile(basePath, milestoneId, sliceId, ct.id, "SUMMARY");
712
- if (!summaryFile)
713
- continue;
714
- const summaryContent = await loadFile(summaryFile);
715
- if (!summaryContent)
716
- continue;
717
- const summary = parseSummary(summaryContent);
718
- if (summary.frontmatter.blocker_discovered) {
719
- return ct.id;
720
- }
721
498
  }
722
499
  return null;
723
500
  }
724
501
  function checkReplanTrigger(basePath, milestoneId, sliceId) {
725
502
  const sliceRow = getSlice(milestoneId, sliceId);
726
- const dbTriggered = !!sliceRow?.replan_triggered_at;
727
- const diskTriggered = !dbTriggered &&
728
- !!resolveSliceFile(basePath, milestoneId, sliceId, "REPLAN-TRIGGER");
729
- return dbTriggered || diskTriggered;
730
- }
731
- async function checkInterruptedWork(basePath, milestoneId, sliceId) {
732
- const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
733
- const continueFile = sDir ? resolveSliceFile(basePath, milestoneId, sliceId, "CONTINUE") : null;
734
- return !!(continueFile && await loadFile(continueFile)) ||
735
- !!(sDir && await loadFile(join(sDir, "continue.md")));
503
+ return !!sliceRow?.replan_triggered_at;
736
504
  }
737
505
  export async function deriveStateFromDb(basePath) {
738
- const requirements = parseRequirementCounts(await loadFile(resolveGsdRootFile(basePath, "REQUIREMENTS")));
739
- let allMilestones = reconcileDiskToDb(basePath);
740
- const customOrder = loadQueueOrder(basePath);
741
- const sortedIds = sortByQueueOrder(allMilestones.map(m => m.id), customOrder);
742
- const byId = new Map(allMilestones.map(m => [m.id, m]));
743
- allMilestones.length = 0;
744
- for (const id of sortedIds)
745
- allMilestones.push(byId.get(id));
746
- const milestoneLock = process.env.GSD_MILESTONE_LOCK;
506
+ const requirements = getRequirementCounts();
507
+ const allMilestones = getAllMilestones();
508
+ const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
747
509
  const milestones = milestoneLock
748
510
  ? allMilestones.filter(m => m.id === milestoneLock)
749
511
  : allMilestones;
@@ -766,26 +528,16 @@ export async function deriveStateFromDb(basePath) {
766
528
  if (!activeMilestone) {
767
529
  return handleNoActiveMilestone(registry, requirements, milestoneProgress);
768
530
  }
769
- const hasRoadmap = resolveMilestoneFile(basePath, activeMilestone.id, "ROADMAP") !== null;
770
531
  if (activeMilestoneSlices.length === 0) {
771
- if (!hasRoadmap) {
772
- const phase = activeMilestoneHasDraft ? 'needs-discussion' : 'pre-planning';
773
- const nextAction = activeMilestoneHasDraft
774
- ? `Discuss draft context for milestone ${activeMilestone.id}.`
775
- : `Plan milestone ${activeMilestone.id}.`;
776
- return {
777
- activeMilestone, activeSlice: null, activeTask: null,
778
- phase, recentDecisions: [], blockers: [],
779
- nextAction, registry, requirements,
780
- progress: { milestones: milestoneProgress },
781
- };
782
- }
532
+ const phase = activeMilestoneHasDraft ? 'needs-discussion' : 'pre-planning';
533
+ const nextAction = activeMilestoneHasDraft
534
+ ? `Discuss draft context for milestone ${activeMilestone.id}.`
535
+ : `Plan milestone ${activeMilestone.id}.`;
783
536
  return {
784
537
  activeMilestone, activeSlice: null, activeTask: null,
785
- phase: 'pre-planning', recentDecisions: [], blockers: [],
786
- nextAction: `Milestone ${activeMilestone.id} has a roadmap but no slices defined. Add slices to the roadmap.`,
787
- registry, requirements,
788
- progress: { milestones: milestoneProgress, slices: { done: 0, total: 0 } },
538
+ phase, recentDecisions: [], blockers: [],
539
+ nextAction, registry, requirements,
540
+ progress: { milestones: milestoneProgress },
789
541
  };
790
542
  }
791
543
  const allSlicesDone = activeMilestoneSlices.every(s => isStatusDone(s.status));
@@ -796,19 +548,14 @@ export async function deriveStateFromDb(basePath) {
796
548
  if (allSlicesDone) {
797
549
  return handleAllSlicesDone(basePath, activeMilestone, registry, requirements, milestoneProgress, sliceProgress);
798
550
  }
799
- // ADR-011 auto-heal: if a slice has a PLAN on disk but is still flagged is_sketch=1
800
- // (e.g. a crash between plan-slice write and the sketch flip), reconcile before
801
- // running phase derivation so the flag doesn't misroute state.
802
- autoHealSketchFlags(activeMilestone.id, (sid) => !!resolveSliceFile(basePath, activeMilestone.id, sid, "PLAN"));
803
- // Re-read slices after auto-heal so downstream reads see fresh is_sketch values.
804
- const healedSlices = getMilestoneSlices(activeMilestone.id);
805
- const activeSliceContext = resolveSliceDependencies(healedSlices);
551
+ const activeSliceContext = resolveSliceDependencies(activeMilestoneSlices);
806
552
  if (!activeSliceContext.activeSlice) {
807
553
  // If locked slice wasn't found, it returns null but logs warning, we need to return 'blocked'
808
- if (process.env.GSD_SLICE_LOCK) {
554
+ const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
555
+ if (sliceLock) {
809
556
  return {
810
557
  activeMilestone, activeSlice: null, activeTask: null,
811
- phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${process.env.GSD_SLICE_LOCK} not found in active milestone slices`],
558
+ phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${sliceLock} not found in active milestone slices`],
812
559
  nextAction: 'Slice lock references a non-existent slice — check orchestrator dispatch.',
813
560
  registry, requirements,
814
561
  progress: { milestones: milestoneProgress, slices: sliceProgress },
@@ -823,31 +570,19 @@ export async function deriveStateFromDb(basePath) {
823
570
  };
824
571
  }
825
572
  const { activeSlice, activeSliceRow } = activeSliceContext;
826
- const planFile = resolveSliceFile(basePath, activeMilestone.id, activeSlice.id, "PLAN");
827
- if (!planFile) {
828
- // ADR-011: sketch slices with progressive_planning enabled enter the
829
- // `refining` phase a refine-slice unit expands the sketch into a full plan
830
- // before execution. When the flag is off, sketches are indistinguishable
831
- // from a missing plan and fall through to the normal `planning` phase.
832
- const progressive = loadEffectiveGSDPreferences()?.preferences?.phases?.progressive_planning === true;
833
- if (progressive && activeSliceRow?.is_sketch === 1) {
834
- return {
835
- activeMilestone, activeSlice, activeTask: null,
836
- phase: 'refining', recentDecisions: [], blockers: [],
837
- nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
838
- registry, requirements,
839
- progress: { milestones: milestoneProgress, slices: sliceProgress },
840
- };
841
- }
573
+ // ADR-011: DB slice metadata is authoritative for sketch refinement.
574
+ // PLAN.md and preference flags are projections/configuration and are
575
+ // deliberately not used to infer whether the slice itself is a sketch.
576
+ if (activeSliceRow?.is_sketch === 1) {
842
577
  return {
843
578
  activeMilestone, activeSlice, activeTask: null,
844
- phase: 'planning', recentDecisions: [], blockers: [],
845
- nextAction: `Plan slice ${activeSlice.id} (${activeSlice.title}).`,
579
+ phase: 'refining', recentDecisions: [], blockers: [],
580
+ nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
846
581
  registry, requirements,
847
582
  progress: { milestones: milestoneProgress, slices: sliceProgress },
848
583
  };
849
584
  }
850
- const tasks = await reconcileSliceTasks(basePath, activeMilestone.id, activeSlice.id, planFile);
585
+ const tasks = getSliceTasks(activeMilestone.id, activeSlice.id);
851
586
  const taskProgress = {
852
587
  done: tasks.filter(t => isStatusDone(t.status)).length,
853
588
  total: tasks.length,
@@ -866,25 +601,12 @@ export async function deriveStateFromDb(basePath) {
866
601
  return {
867
602
  activeMilestone, activeSlice, activeTask: null,
868
603
  phase: 'planning', recentDecisions: [], blockers: [],
869
- nextAction: `Slice ${activeSlice.id} has a plan file but no tasks. Add tasks to the plan.`,
604
+ nextAction: `Slice ${activeSlice.id} has no DB tasks. Plan slice tasks before execution.`,
870
605
  registry, requirements,
871
606
  progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
872
607
  };
873
608
  }
874
609
  const activeTask = { id: activeTaskRow.id, title: activeTaskRow.title };
875
- const tasksDir = resolveTasksDir(basePath, activeMilestone.id, activeSlice.id);
876
- if (tasksDir && existsSync(tasksDir) && tasks.length > 0) {
877
- const allFiles = readdirSync(tasksDir).filter(f => f.endsWith(".md"));
878
- if (allFiles.length === 0) {
879
- return {
880
- activeMilestone, activeSlice, activeTask: null,
881
- phase: 'planning', recentDecisions: [], blockers: [],
882
- nextAction: `Task plan files missing for ${activeSlice.id}. Run plan-slice to generate task plans.`,
883
- registry, requirements,
884
- progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
885
- };
886
- }
887
- }
888
610
  // ── Quality gate evaluation check ──────────────────────────────────
889
611
  // Pause before execution only when gates owned by the `gate-evaluate`
890
612
  // turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
@@ -956,13 +678,10 @@ export async function deriveStateFromDb(basePath) {
956
678
  }
957
679
  }
958
680
  }
959
- const hasInterrupted = await checkInterruptedWork(basePath, activeMilestone.id, activeSlice.id);
960
681
  return {
961
682
  activeMilestone, activeSlice, activeTask,
962
683
  phase: 'executing', recentDecisions: [], blockers: [],
963
- nextAction: hasInterrupted
964
- ? `Resume interrupted work on ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}. Read continue.md first.`
965
- : `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
684
+ nextAction: `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
966
685
  registry, requirements,
967
686
  progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
968
687
  };
@@ -975,12 +694,12 @@ export async function _deriveStateImpl(basePath) {
975
694
  const customOrder = loadQueueOrder(basePath);
976
695
  const milestoneIds = sortByQueueOrder(diskIds, customOrder);
977
696
  // ── Parallel worker isolation ──────────────────────────────────────────
978
- // When GSD_MILESTONE_LOCK is set, this process is a parallel worker
697
+ // When GSD_PARALLEL_WORKER and GSD_MILESTONE_LOCK are set, this process is a parallel worker
979
698
  // scoped to a single milestone. Filter the milestone list so this worker
980
699
  // only sees its assigned milestone (all others are treated as if they
981
700
  // don't exist). This gives each worker complete isolation without
982
701
  // modifying any other state derivation logic.
983
- const milestoneLock = process.env.GSD_MILESTONE_LOCK;
702
+ const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
984
703
  if (milestoneLock && milestoneIds.includes(milestoneLock)) {
985
704
  milestoneIds.length = 0;
986
705
  milestoneIds.push(milestoneLock);
@@ -1429,8 +1148,8 @@ export async function _deriveStateImpl(basePath) {
1429
1148
  const doneSliceIds = new Set(activeRoadmap.slices.filter(s => s.done).map(s => s.id));
1430
1149
  let activeSlice = null;
1431
1150
  // ── Slice-level parallel worker isolation ─────────────────────────────
1432
- // When GSD_SLICE_LOCK is set, override activeSlice to only the locked slice.
1433
- const sliceLockLegacy = process.env.GSD_SLICE_LOCK;
1151
+ // When GSD_PARALLEL_WORKER and GSD_SLICE_LOCK are set, override activeSlice to only the locked slice.
1152
+ const sliceLockLegacy = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
1434
1153
  if (sliceLockLegacy) {
1435
1154
  const lockedSlice = activeRoadmap.slices.find(s => s.id === sliceLockLegacy);
1436
1155
  if (lockedSlice) {