@opengsd/gsd-pi 1.1.1-dev.a5a2de8 → 1.1.1-dev.b2556262

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 (325) hide show
  1. package/dist/headless-recover.js +56 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
  4. package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
  5. package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
  6. package/dist/resources/extensions/browser-tools/index.js +68 -24
  7. package/dist/resources/extensions/browser-tools/state.js +12 -0
  8. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  9. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  10. package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
  11. package/dist/resources/extensions/gsd/auto/loop.js +4 -2
  12. package/dist/resources/extensions/gsd/auto/phases.js +87 -12
  13. package/dist/resources/extensions/gsd/auto/session.js +22 -1
  14. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +81 -13
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +154 -9
  17. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
  18. package/dist/resources/extensions/gsd/auto-prompts.js +26 -21
  19. package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
  20. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  21. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  22. package/dist/resources/extensions/gsd/auto-timers.js +24 -10
  23. package/dist/resources/extensions/gsd/auto.js +40 -15
  24. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +192 -77
  26. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  27. package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
  28. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
  29. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
  30. package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
  31. package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
  33. package/dist/resources/extensions/gsd/config-overlay.js +1 -0
  34. package/dist/resources/extensions/gsd/context-masker.js +129 -5
  35. package/dist/resources/extensions/gsd/db-writer.js +35 -0
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  37. package/dist/resources/extensions/gsd/gsd-db.js +480 -172
  38. package/dist/resources/extensions/gsd/guided-flow.js +4 -1
  39. package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
  40. package/dist/resources/extensions/gsd/md-importer.js +38 -3
  41. package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
  42. package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
  43. package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
  44. package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
  45. package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
  46. package/dist/resources/extensions/gsd/preferences-models.js +111 -43
  47. package/dist/resources/extensions/gsd/preferences-types.js +13 -0
  48. package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
  49. package/dist/resources/extensions/gsd/preferences.js +4 -1
  50. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  51. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  52. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
  55. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  56. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
  57. package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
  58. package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
  59. package/dist/resources/extensions/gsd/source-observations.js +306 -0
  60. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
  61. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
  62. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
  63. package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
  64. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
  65. package/dist/resources/extensions/gsd/state.js +7 -3
  66. package/dist/resources/extensions/gsd/tool-contract.js +15 -1
  67. package/dist/resources/extensions/gsd/tool-presentation-plan.js +24 -2
  68. package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -0
  69. package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
  70. package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
  71. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +62 -406
  72. package/dist/resources/extensions/gsd/uat-policy.js +130 -0
  73. package/dist/resources/extensions/gsd/uat-run.js +414 -0
  74. package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
  75. package/dist/resources/extensions/gsd/unit-tool-contracts.js +38 -14
  76. package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
  77. package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
  78. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -3
  79. package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
  80. package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
  81. package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
  82. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
  83. package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
  84. package/dist/resources/extensions/subagent/agents.js +1 -0
  85. package/dist/resources/extensions/subagent/index.js +27 -12
  86. package/dist/resources/extensions/subagent/launch.js +7 -2
  87. package/dist/web/standalone/.next/BUILD_ID +1 -1
  88. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  89. package/dist/web/standalone/.next/build-manifest.json +2 -2
  90. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  91. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.html +1 -1
  108. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  115. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  116. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  118. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  119. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  120. package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
  121. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  122. package/package.json +4 -4
  123. package/packages/cloud-mcp-gateway/package.json +2 -2
  124. package/packages/contracts/package.json +1 -1
  125. package/packages/daemon/package.json +4 -4
  126. package/packages/gsd-agent-core/package.json +5 -5
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  138. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  140. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  143. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  144. package/packages/gsd-agent-modes/package.json +7 -7
  145. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  146. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  147. package/packages/mcp-server/package.json +3 -3
  148. package/packages/native/dist/native.js +22 -0
  149. package/packages/native/package.json +1 -1
  150. package/packages/pi-agent-core/package.json +1 -1
  151. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  152. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  153. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  154. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  155. package/packages/pi-ai/dist/models.generated.d.ts +174 -29
  156. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/models.generated.js +178 -54
  158. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  159. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  160. package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
  161. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  162. package/packages/pi-ai/package.json +1 -1
  163. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  164. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  165. package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
  166. package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
  167. package/packages/pi-coding-agent/package.json +7 -7
  168. package/packages/pi-tui/dist/utils.d.ts +11 -0
  169. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  170. package/packages/pi-tui/dist/utils.js +119 -6
  171. package/packages/pi-tui/dist/utils.js.map +1 -1
  172. package/packages/pi-tui/package.json +2 -1
  173. package/packages/rpc-client/package.json +2 -2
  174. package/pkg/dist/theme/themes.js +1 -1
  175. package/pkg/dist/theme/themes.js.map +1 -1
  176. package/pkg/package.json +1 -1
  177. package/scripts/install/handoff.js +16 -3
  178. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
  179. package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
  180. package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
  181. package/src/resources/extensions/browser-tools/index.ts +75 -27
  182. package/src/resources/extensions/browser-tools/state.ts +13 -0
  183. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
  184. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  185. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
  186. package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
  187. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  188. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  189. package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
  190. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  191. package/src/resources/extensions/gsd/auto/loop.ts +4 -2
  192. package/src/resources/extensions/gsd/auto/phases.ts +89 -15
  193. package/src/resources/extensions/gsd/auto/session.ts +24 -1
  194. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
  195. package/src/resources/extensions/gsd/auto-dispatch.ts +117 -12
  196. package/src/resources/extensions/gsd/auto-model-selection.ts +190 -12
  197. package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
  198. package/src/resources/extensions/gsd/auto-prompts.ts +25 -22
  199. package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
  200. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  201. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  202. package/src/resources/extensions/gsd/auto-timers.ts +25 -9
  203. package/src/resources/extensions/gsd/auto.ts +41 -14
  204. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  205. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +250 -78
  206. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  207. package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
  208. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
  209. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
  210. package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
  211. package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
  212. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
  213. package/src/resources/extensions/gsd/config-overlay.ts +1 -0
  214. package/src/resources/extensions/gsd/context-masker.ts +152 -5
  215. package/src/resources/extensions/gsd/db-writer.ts +38 -0
  216. package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  217. package/src/resources/extensions/gsd/gsd-db.ts +564 -186
  218. package/src/resources/extensions/gsd/guided-flow.ts +4 -1
  219. package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
  220. package/src/resources/extensions/gsd/md-importer.ts +49 -2
  221. package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
  222. package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
  223. package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
  224. package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
  225. package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
  226. package/src/resources/extensions/gsd/preferences-models.ts +113 -43
  227. package/src/resources/extensions/gsd/preferences-types.ts +47 -0
  228. package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
  229. package/src/resources/extensions/gsd/preferences.ts +5 -0
  230. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  231. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  232. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  233. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  234. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
  235. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  236. package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
  237. package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
  238. package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
  239. package/src/resources/extensions/gsd/source-observations.ts +402 -0
  240. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
  241. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
  242. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
  243. package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
  244. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
  245. package/src/resources/extensions/gsd/state.ts +7 -4
  246. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +114 -0
  247. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
  248. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
  249. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
  250. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
  251. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
  252. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
  253. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
  254. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
  255. package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
  256. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
  257. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
  258. package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
  259. package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
  260. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +17 -2
  261. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
  262. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
  263. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
  264. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
  265. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
  266. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +4 -1
  267. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
  268. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -0
  269. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
  270. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
  271. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
  273. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
  274. package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
  275. package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
  276. package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
  277. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
  278. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +133 -0
  279. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
  280. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
  281. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
  282. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +28 -0
  283. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
  284. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
  285. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
  286. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
  287. package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
  288. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
  289. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
  290. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
  291. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
  292. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
  293. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  294. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
  295. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
  296. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +260 -5
  297. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
  298. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
  299. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
  300. package/src/resources/extensions/gsd/tool-contract.ts +29 -1
  301. package/src/resources/extensions/gsd/tool-presentation-plan.ts +41 -6
  302. package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -0
  303. package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
  304. package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
  305. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +71 -489
  306. package/src/resources/extensions/gsd/types.ts +1 -0
  307. package/src/resources/extensions/gsd/uat-policy.ts +191 -0
  308. package/src/resources/extensions/gsd/uat-run.ts +550 -0
  309. package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
  310. package/src/resources/extensions/gsd/unit-tool-contracts.ts +38 -14
  311. package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
  312. package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
  313. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -3
  314. package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
  315. package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
  316. package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
  317. package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
  318. package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
  319. package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
  320. package/src/resources/extensions/subagent/agents.ts +4 -0
  321. package/src/resources/extensions/subagent/index.ts +28 -3
  322. package/src/resources/extensions/subagent/launch.ts +8 -0
  323. package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
  324. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
  325. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
@@ -865,7 +865,10 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType,
865
865
  ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
866
866
  : undefined,
867
867
  baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
868
- activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
868
+ // Guided flow starts the MCP workflow server as part of dispatch, so the
869
+ // parent session's activeTools doesn't include MCP tools yet. The MCP
870
+ // launch config check (detectWorkflowMcpLaunchConfig) is the right gate
871
+ // here — not whether MCP tools are pre-registered in the parent session.
869
872
  });
870
873
  if (compatibilityError) {
871
874
  ctx.ui.notify(compatibilityError, "error");
@@ -11,9 +11,9 @@
11
11
  import { readFileSync, existsSync, mkdirSync } from "node:fs";
12
12
  import { logWarning } from "./workflow-logger.js";
13
13
  import { isClosedStatus } from "./status-guards.js";
14
- import { join, relative } from "node:path";
14
+ import { dirname, join, relative } from "node:path";
15
15
  import { createRequire } from "node:module";
16
- import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice, getArtifact, insertArtifact, getGateResults, } from "./gsd-db.js";
16
+ import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice, insertArtifact, getGateResults, } from "./gsd-db.js";
17
17
  import { resolveDir, resolveFile, resolveSliceFile, resolveSlicePath, gsdProjectionRoot, gsdRoot, buildMilestoneFileName, buildTaskFileName, buildSliceFileName, } from "./paths.js";
18
18
  import { saveFile, clearParseCache } from "./files.js";
19
19
  import { invalidateStateCache } from "./state.js";
@@ -78,18 +78,6 @@ function sanitizeInlineRoadmapText(value) {
78
78
  .replace(/\s+/g, " ")
79
79
  .trim();
80
80
  }
81
- /**
82
- * Load artifact content from the DB. Markdown projections are not authoritative
83
- * during runtime; when the artifact row is missing, callers regenerate from DB
84
- * rows instead of patching disk fallback content and storing it back.
85
- */
86
- function loadArtifactContent(artifactPath) {
87
- const artifact = getArtifact(artifactPath);
88
- if (artifact && artifact.full_content) {
89
- return artifact.full_content;
90
- }
91
- return null;
92
- }
93
81
  function resolveRoadmapProjectionPath(basePath, milestoneId) {
94
82
  const projectionMilestonesDir = join(gsdProjectionRoot(basePath), "milestones");
95
83
  const milestoneDirName = resolveDir(projectionMilestonesDir, milestoneId) ?? milestoneId;
@@ -324,7 +312,7 @@ function renderSlicePlanMarkdown(slice, tasks, gates = []) {
324
312
  }
325
313
  return `${lines.join("\n").trimEnd()}\n`;
326
314
  }
327
- export async function renderPlanFromDb(basePath, milestoneId, sliceId) {
315
+ export async function renderPlanFromDb(basePath, milestoneId, sliceId, outputPath) {
328
316
  const slice = getSlice(milestoneId, sliceId);
329
317
  if (!slice) {
330
318
  throw new Error(`slice ${milestoneId}/${sliceId} not found`);
@@ -333,9 +321,9 @@ export async function renderPlanFromDb(basePath, milestoneId, sliceId) {
333
321
  if (tasks.length === 0) {
334
322
  throw new Error(`no tasks found for ${milestoneId}/${sliceId}`);
335
323
  }
336
- const slicePath = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId);
337
- mkdirSync(slicePath, { recursive: true });
338
- const absPath = join(slicePath, `${sliceId}-PLAN.md`);
324
+ const defaultPlanPath = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId, `${sliceId}-PLAN.md`);
325
+ const absPath = outputPath ?? defaultPlanPath;
326
+ mkdirSync(dirname(absPath), { recursive: true });
339
327
  const artifactPath = toArtifactPath(absPath, basePath);
340
328
  const sliceGates = getGateResults(milestoneId, sliceId, "slice");
341
329
  const content = renderSlicePlanMarkdown(slice, tasks, sliceGates);
@@ -406,48 +394,24 @@ export async function renderRoadmapCheckboxes(basePath, milestoneId) {
406
394
  /**
407
395
  * Render plan checkbox states from DB.
408
396
  *
409
- * For each task in the slice, sets [x] if status === 'done',
410
- * [ ] otherwise. Bidirectional.
397
+ * Compatibility wrapper for legacy callers that used to patch plan checkboxes
398
+ * in-place. Plans are now fully regenerated from DB rows (mirroring
399
+ * renderRoadmapCheckboxes) so the projection always reflects the complete
400
+ * current task set and statuses. The previous regex-patch approach reused the
401
+ * cached PLAN artifact as the render input, which silently dropped tasks added
402
+ * to the DB after the artifact was first written — producing a lossy
403
+ * projection (the 4S/0T-vs-5S/13T drift class). The artifacts table is an
404
+ * output sink, never a render input.
411
405
  *
412
406
  * @returns true if the plan was written, false on skip/error
413
407
  */
414
- export async function renderPlanCheckboxes(basePath, milestoneId, sliceId) {
408
+ export async function renderPlanCheckboxes(basePath, milestoneId, sliceId, outputPath) {
415
409
  const tasks = getSliceTasks(milestoneId, sliceId);
416
410
  if (tasks.length === 0) {
417
411
  process.stderr.write(`markdown-renderer: no tasks found for ${milestoneId}/${sliceId}\n`);
418
412
  return false;
419
413
  }
420
- const absPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
421
- const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
422
- let content = null;
423
- if (artifactPath) {
424
- content = loadArtifactContent(artifactPath);
425
- }
426
- if (!content) {
427
- await renderPlanFromDb(basePath, milestoneId, sliceId);
428
- return true;
429
- }
430
- // Apply checkbox patches for each task
431
- let updated = content;
432
- for (const task of tasks) {
433
- const isDone = isClosedStatus(task.status);
434
- const tid = task.id;
435
- if (isDone) {
436
- // Set [x]
437
- updated = updated.replace(new RegExp(`^(\\s*-\\s+)\\[ \\]\\s+\\*\\*${tid}:`, "m"), `$1[x] **${tid}:`);
438
- }
439
- else {
440
- // Set [ ]
441
- updated = updated.replace(new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${tid}:`, "mi"), `$1[ ] **${tid}:`);
442
- }
443
- }
444
- if (!absPath)
445
- return false;
446
- await writeAndStore(absPath, artifactPath, updated, {
447
- artifact_type: "PLAN",
448
- milestone_id: milestoneId,
449
- slice_id: sliceId,
450
- });
414
+ await renderPlanFromDb(basePath, milestoneId, sliceId, outputPath);
451
415
  return true;
452
416
  }
453
417
  // ─── Task Summary Rendering ───────────────────────────────────────────────
@@ -584,6 +548,18 @@ export async function renderAllFromDb(basePath) {
584
548
  }
585
549
  }
586
550
  }
551
+ // Re-project root DECISIONS.md from the authoritative decision records so a
552
+ // full DB → markdown re-projection (recover, rebuild) also heals decisions
553
+ // drift — e.g. a worktree merge that accepted one branch's DECISIONS.md while
554
+ // the DB holds the union of both branches' decisions.
555
+ try {
556
+ const { regenerateDecisionsMarkdown } = await import("./db-writer.js");
557
+ await regenerateDecisionsMarkdown(basePath);
558
+ result.rendered++;
559
+ }
560
+ catch (err) {
561
+ result.errors.push(`decisions: ${err.message}`);
562
+ }
587
563
  return result;
588
564
  }
589
565
  /**
@@ -659,8 +635,16 @@ export function detectStaleRenders(basePath) {
659
635
  for (const task of tasks) {
660
636
  const isDoneInDb = isClosedStatus(task.status);
661
637
  const planTask = parsed.tasks.find((t) => t.id === task.id);
662
- if (!planTask)
638
+ if (!planTask) {
639
+ // DB has a task the plan markdown lacks: the projection is
640
+ // lossy (e.g. tasks added after the PLAN artifact was first
641
+ // written). Flag it so the plan is re-rendered from DB rows.
642
+ stale.push({
643
+ path: planPath,
644
+ reason: `${task.id} exists in DB but is missing in plan`,
645
+ });
663
646
  continue;
647
+ }
664
648
  if (isDoneInDb && !planTask.done) {
665
649
  stale.push({
666
650
  path: planPath,
@@ -3,9 +3,9 @@
3
3
  // then upserts everything into the SQLite database.
4
4
  //
5
5
  // Exports: parseDecisionsTable, parseRequirementsSections, migrateFromMarkdown
6
- import { readFileSync, readdirSync, existsSync } from 'node:fs';
6
+ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
- import { upsertDecision, upsertRequirement, insertArtifact, insertMilestone, insertSlice, insertTask, openDatabase, transaction, updateSliceStatus, _getAdapter, } from './gsd-db.js';
8
+ import { upsertDecision, upsertRequirement, insertArtifact, insertMilestone, insertSlice, insertTask, openDatabase, transaction, updateMilestoneStatus, updateSliceStatus, _getAdapter, } from './gsd-db.js';
9
9
  import { resolveGsdRootFile, resolveMilestoneFile, resolveSliceFile, resolveTasksDir, milestonesDir, gsdRoot, resolveTaskFiles, } from './paths.js';
10
10
  import { findMilestoneIds } from './guided-flow.js';
11
11
  import { parseRoadmap, parsePlan } from './parsers-legacy.js';
@@ -13,6 +13,26 @@ import { parseContextDependsOn } from './files.js';
13
13
  import { logWarning } from './workflow-logger.js';
14
14
  // ─── DECISIONS.md Parser ───────────────────────────────────────────────────
15
15
  const VALID_MADE_BY = new Set(['human', 'agent', 'collaborative']);
16
+ const IMPORT_COMPLETE_STATUSES = new Set(['complete', 'done']);
17
+ /**
18
+ * Completion timestamp for an entity imported as complete: the SUMMARY.md mtime
19
+ * when one exists (deterministic, matches the completion drift handler), else
20
+ * the import time. Crucially this never returns null, so a complete entity is
21
+ * never left with completed_at=null — which would otherwise be permanent,
22
+ * undetectable drift when no SUMMARY file is present.
23
+ */
24
+ function importCompletionTimestamp(summaryPath) {
25
+ if (summaryPath && existsSync(summaryPath)) {
26
+ try {
27
+ return statSync(summaryPath).mtime.toISOString();
28
+ }
29
+ catch (err) {
30
+ // Fall through to import time.
31
+ logWarning('projection', `summary mtime read failed for ${summaryPath}: ${err.message}`);
32
+ }
33
+ }
34
+ return new Date().toISOString();
35
+ }
16
36
  /**
17
37
  * Parse a DECISIONS.md markdown table into Decision objects (without seq).
18
38
  * Detects `(amends DXXX)` in the Decision column to build supersession info.
@@ -493,6 +513,14 @@ export function migrateHierarchyToDb(basePath) {
493
513
  boundaryMapMarkdown: boundaryMapSection,
494
514
  },
495
515
  });
516
+ // insertMilestone never sets completed_at; backfill it now for milestones
517
+ // imported as complete so the DB is internally coherent immediately after
518
+ // import (rather than relying on a later reconciliation pass that can't even
519
+ // see the drift when no SUMMARY file exists).
520
+ if (IMPORT_COMPLETE_STATUSES.has(milestoneStatus)) {
521
+ const summaryPath = resolveMilestoneFile(basePath, milestoneId, 'SUMMARY');
522
+ updateMilestoneStatus(milestoneId, milestoneStatus, importCompletionTimestamp(summaryPath));
523
+ }
496
524
  counts.milestones++;
497
525
  // Parse roadmap for slices
498
526
  if (!roadmap)
@@ -517,10 +545,17 @@ export function migrateHierarchyToDb(basePath) {
517
545
  depends: sliceEntry.depends,
518
546
  demo: sliceEntry.demo,
519
547
  sequence: si + 1, // Preserve roadmap parse order (#3356)
548
+ isSketch: sliceEntry.isSketch ?? false, // ADR-011: preserve the `[sketch]` flag on re-import
520
549
  planning: {
521
550
  goal: plan?.goal ?? '',
522
551
  },
523
552
  });
553
+ // insertSlice never sets completed_at; backfill it for slices imported as
554
+ // complete (same rationale as milestones above).
555
+ if (IMPORT_COMPLETE_STATUSES.has(sliceStatus)) {
556
+ const sliceSummary = resolveSliceFile(basePath, milestoneId, sliceEntry.id, 'SUMMARY');
557
+ updateSliceStatus(milestoneId, sliceEntry.id, sliceStatus, importCompletionTimestamp(sliceSummary));
558
+ }
524
559
  counts.slices++;
525
560
  // Insert tasks from parsed plan
526
561
  if (!plan)
@@ -572,7 +607,7 @@ export function migrateHierarchyToDb(basePath) {
572
607
  });
573
608
  if (allTasksDone && hasSliceSummary) {
574
609
  if (_getAdapter()) {
575
- updateSliceStatus(milestoneId, sliceEntry.id, 'complete');
610
+ updateSliceStatus(milestoneId, sliceEntry.id, 'complete', importCompletionTimestamp(sliceSummaryPath));
576
611
  process.stderr.write(`gsd-migrate: ${milestoneId}/${sliceEntry.id} all tasks + slice summary complete — upgrading slice to complete\n`);
577
612
  }
578
613
  }
@@ -6,59 +6,117 @@ import { milestonesDir, resolveMilestoneFile, resolveSliceFile, } from "./paths.
6
6
  function zeroCounts() {
7
7
  return { milestones: 0, slices: 0, tasks: 0 };
8
8
  }
9
+ function emptyScan() {
10
+ return { counts: zeroCounts(), milestones: new Set(), slices: new Set(), tasks: new Set() };
11
+ }
9
12
  function sameCounts(a, b) {
10
13
  return a.milestones === b.milestones && a.slices === b.slices && a.tasks === b.tasks;
11
14
  }
12
- export function countMarkdownHierarchy(basePath) {
15
+ function setsEqual(a, b) {
16
+ if (a.size !== b.size)
17
+ return false;
18
+ for (const value of a)
19
+ if (!b.has(value))
20
+ return false;
21
+ return true;
22
+ }
23
+ function scanIdentitiesMatch(a, b) {
24
+ return (setsEqual(a.milestones, b.milestones) &&
25
+ setsEqual(a.slices, b.slices) &&
26
+ setsEqual(a.tasks, b.tasks));
27
+ }
28
+ /** True if any element of `a` is absent from `b`. */
29
+ function hasExtra(a, b) {
30
+ for (const value of a)
31
+ if (!b.has(value))
32
+ return true;
33
+ return false;
34
+ }
35
+ function scanHasExtraIdentities(a, b) {
36
+ return (hasExtra(a.milestones, b.milestones) ||
37
+ hasExtra(a.slices, b.slices) ||
38
+ hasExtra(a.tasks, b.tasks));
39
+ }
40
+ /**
41
+ * True when the DB holds any milestone/slice/task identity the markdown lacks —
42
+ * i.e. a `/gsd recover --confirm` (markdown → DB) would DELETE authoritative DB
43
+ * rows. This is identity-based, so it catches equal-count divergence (e.g. DB
44
+ * slice `S99` vs markdown `S01`) that a cardinality-only check misses. Used by
45
+ * the recover data-loss guard.
46
+ */
47
+ export function recoverWouldDeleteDbRows(basePath) {
48
+ return scanHasExtraIdentities(scanDbHierarchy(), scanMarkdownHierarchy(basePath));
49
+ }
50
+ export function scanMarkdownHierarchy(basePath) {
13
51
  const root = milestonesDir(basePath);
14
52
  if (!existsSync(root))
15
- return zeroCounts();
16
- const counts = zeroCounts();
53
+ return emptyScan();
54
+ const scan = emptyScan();
17
55
  for (const entry of readdirSync(root, { withFileTypes: true })) {
18
56
  if (!entry.isDirectory() || !/^M\d+/.test(entry.name))
19
57
  continue;
20
- counts.milestones++;
21
- const roadmapPath = resolveMilestoneFile(basePath, entry.name, "ROADMAP");
58
+ // Use the CANONICAL milestone id (e.g. "M001" or "M001-a1b2c3"), matching
59
+ // scanDbHierarchy's milestone.id not the raw directory name, which may
60
+ // carry a legacy descriptor (e.g. "M001-some-descriptor"). The canonical id
61
+ // both keys the identity sets AND resolves files correctly: resolveFile's
62
+ // prefix/legacy-pattern matching handles the descriptor dir either way.
63
+ const milestoneId = entry.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/)?.[1] ?? entry.name;
64
+ scan.counts.milestones++;
65
+ scan.milestones.add(milestoneId);
66
+ const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
22
67
  if (!roadmapPath || !existsSync(roadmapPath))
23
68
  continue;
24
69
  const roadmap = parseRoadmap(readFileSync(roadmapPath, "utf-8"));
25
- counts.slices += roadmap.slices.length;
70
+ scan.counts.slices += roadmap.slices.length;
26
71
  for (const slice of roadmap.slices) {
27
- const planPath = resolveSliceFile(basePath, entry.name, slice.id, "PLAN");
72
+ scan.slices.add(`${milestoneId}/${slice.id}`);
73
+ const planPath = resolveSliceFile(basePath, milestoneId, slice.id, "PLAN");
28
74
  if (!planPath || !existsSync(planPath))
29
75
  continue;
30
76
  const plan = parsePlan(readFileSync(planPath, "utf-8"));
31
- counts.tasks += plan.tasks.length;
77
+ scan.counts.tasks += plan.tasks.length;
78
+ for (const task of plan.tasks) {
79
+ scan.tasks.add(`${milestoneId}/${slice.id}/${task.id}`);
80
+ }
32
81
  }
33
82
  }
34
- return counts;
83
+ return scan;
35
84
  }
36
- export function countDbHierarchy() {
85
+ export function scanDbHierarchy() {
37
86
  if (!isDbAvailable())
38
- return zeroCounts();
39
- const counts = zeroCounts();
87
+ return emptyScan();
88
+ const scan = emptyScan();
40
89
  const milestones = getAllMilestones();
41
- counts.milestones = milestones.length;
90
+ scan.counts.milestones = milestones.length;
42
91
  for (const milestone of milestones) {
92
+ scan.milestones.add(milestone.id);
43
93
  const slices = getMilestoneSlices(milestone.id);
44
- counts.slices += slices.length;
94
+ scan.counts.slices += slices.length;
45
95
  for (const slice of slices) {
46
- counts.tasks += getSliceTasks(milestone.id, slice.id).length;
96
+ scan.slices.add(`${milestone.id}/${slice.id}`);
97
+ const tasks = getSliceTasks(milestone.id, slice.id);
98
+ scan.counts.tasks += tasks.length;
99
+ for (const task of tasks) {
100
+ scan.tasks.add(`${milestone.id}/${slice.id}/${task.id}`);
101
+ }
47
102
  }
48
103
  }
49
- return counts;
104
+ return scan;
105
+ }
106
+ export function countMarkdownHierarchy(basePath) {
107
+ return scanMarkdownHierarchy(basePath).counts;
108
+ }
109
+ export function countDbHierarchy() {
110
+ return scanDbHierarchy().counts;
50
111
  }
51
112
  export async function checkMarkdownHierarchyAgainstDb(basePath) {
52
- const markdown = countMarkdownHierarchy(basePath);
53
- if (sameCounts(markdown, zeroCounts())) {
54
- return {
55
- action: "none",
56
- reason: "no-markdown",
57
- markdown,
58
- beforeDb: zeroCounts(),
59
- afterDb: zeroCounts(),
60
- };
61
- }
113
+ const markdownScan = scanMarkdownHierarchy(basePath);
114
+ const markdown = markdownScan.counts;
115
+ // Always open the DB before deciding. An empty markdown tree does NOT imply
116
+ // an empty project — the DB may hold authoritative rows whose markdown was
117
+ // lost, which is itself recoverable drift. The previous early return here
118
+ // skipped the DB entirely and silently hid a populated-DB/empty-markdown
119
+ // project.
62
120
  const opened = await ensureDbOpen(basePath);
63
121
  if (!opened || !isDbAvailable()) {
64
122
  throw new Error(`failed to open or create the GSD database at ${basePath}`);
@@ -67,11 +125,49 @@ export async function checkMarkdownHierarchyAgainstDb(basePath) {
67
125
  // server in another process. Reopen before comparing so startup does not
68
126
  // warn from a stale long-lived SQLite handle.
69
127
  refreshOpenDatabaseFromDisk();
70
- const beforeDb = countDbHierarchy();
71
- if (sameCounts(markdown, beforeDb)) {
128
+ const dbScan = scanDbHierarchy();
129
+ const beforeDb = dbScan.counts;
130
+ const markdownEmpty = sameCounts(markdown, zeroCounts());
131
+ const dbEmpty = sameCounts(beforeDb, zeroCounts());
132
+ // Genuinely empty project: nothing on disk, nothing in the DB.
133
+ if (markdownEmpty && dbEmpty) {
134
+ return { action: "none", reason: "no-markdown", markdown, beforeDb, afterDb: beforeDb };
135
+ }
136
+ // In sync only when both cardinalities AND identities agree. Identity
137
+ // comparison catches drift the counts miss (e.g. a slice deleted from the DB
138
+ // and a different one added nets to the same count but is real divergence,
139
+ // and a missing PLAN.md vs DB tasks shows up as a task-identity gap).
140
+ if (sameCounts(markdown, beforeDb) && scanIdentitiesMatch(markdownScan, dbScan)) {
72
141
  return { action: "none", reason: "in-sync", markdown, beforeDb, afterDb: beforeDb };
73
142
  }
74
- const reason = sameCounts(beforeDb, zeroCounts()) ? "db-empty" : "count-mismatch";
143
+ // Choose the SAFE repair direction by IDENTITY, not cardinality. Recover
144
+ // imports markdown → DB and DELETES any DB row markdown lacks, so it must
145
+ // never be recommended when the DB holds identities the markdown is missing —
146
+ // including equal-count divergence (DB `S99` vs markdown `S01`), which a
147
+ // count-only check would wrongly route to recover. Whenever the DB holds rows
148
+ // markdown lacks, the correct repair is to re-project from the DB (rebuild).
149
+ const dbHasExtra = scanHasExtraIdentities(dbScan, markdownScan);
150
+ const countsLine = `Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
151
+ `do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). `;
152
+ // The DB holds rows markdown lacks (richer, identity-diverged, or markdown
153
+ // entirely missing): re-project from the DB. Recover here would destroy data.
154
+ if (dbHasExtra) {
155
+ return {
156
+ action: "recovery-required",
157
+ reason: markdownEmpty ? "markdown-missing" : "count-mismatch",
158
+ markdown,
159
+ beforeDb,
160
+ afterDb: beforeDb,
161
+ recoveryCommand: "/gsd rebuild markdown",
162
+ message: countsLine +
163
+ "The DB holds rows the markdown lacks, so the markdown projection is stale. " +
164
+ "Run `/gsd rebuild markdown` to re-project from the authoritative DB. " +
165
+ "Do NOT run `/gsd recover --confirm` here — it would delete the extra DB rows.",
166
+ };
167
+ }
168
+ // DB is empty (or markdown is strictly richer): markdown is the surviving
169
+ // source to import.
170
+ const reason = dbEmpty ? "db-empty" : "count-mismatch";
75
171
  return {
76
172
  action: "recovery-required",
77
173
  reason,
@@ -79,8 +175,7 @@ export async function checkMarkdownHierarchyAgainstDb(basePath) {
79
175
  beforeDb,
80
176
  afterDb: beforeDb,
81
177
  recoveryCommand: "/gsd recover --confirm",
82
- message: `Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
83
- `do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). ` +
178
+ message: countsLine +
84
179
  "Runtime startup will not import markdown automatically; run `/gsd recover --confirm` if markdown should repopulate the database.",
85
180
  };
86
181
  }
@@ -46,11 +46,34 @@ registerCacheClearCallback(clearLegacyParseCache);
46
46
  export function parseRoadmap(content) {
47
47
  return cachedParse(content, 'roadmap', _parseRoadmapImpl);
48
48
  }
49
+ /**
50
+ * ADR-011: the roadmap renderer writes a `[sketch]` badge for sketch slices,
51
+ * but the native parser does not surface it. Re-scan the markdown and set
52
+ * isSketch on the matching slice so the flag survives a markdown → DB re-import
53
+ * (e.g. /gsd recover) instead of being silently dropped.
54
+ */
55
+ function applySketchFlags(roadmap, content) {
56
+ const sketchIds = new Set();
57
+ for (const line of content.split("\n")) {
58
+ if (!/\[sketch\]/i.test(line))
59
+ continue;
60
+ const m = line.match(/\*\*([\w.]+):/);
61
+ if (m)
62
+ sketchIds.add(m[1]);
63
+ }
64
+ if (sketchIds.size === 0)
65
+ return;
66
+ for (const slice of roadmap.slices) {
67
+ if (sketchIds.has(slice.id))
68
+ slice.isSketch = true;
69
+ }
70
+ }
49
71
  function _parseRoadmapImpl(content) {
50
72
  const stopTimer = debugTime("parse-roadmap");
51
73
  // Try native parser first for better performance
52
74
  const nativeResult = nativeParseRoadmap(content);
53
75
  if (nativeResult) {
76
+ applySketchFlags(nativeResult, content);
54
77
  stopTimer({ native: true, slices: nativeResult.slices.length, boundaryEntries: nativeResult.boundaryMap.length });
55
78
  debugCount("parseRoadmapCalls");
56
79
  return nativeResult;
@@ -0,0 +1,98 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Optional gsd-planner handoff after milestone planning.
3
+ import { spawn as spawnChild } from "node:child_process";
4
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { gsdRoot } from "./paths.js";
7
+ export const PLANNER_HANDOFF_RULE_NAME = "planning review handoff -> gsd-planner";
8
+ export const GSD_PLANNER_COMMAND = "gsd-planner";
9
+ function handoffDir(basePath) {
10
+ return join(gsdRoot(basePath), "runtime", "planner-handoffs");
11
+ }
12
+ function safeMilestoneFileSegment(milestoneId) {
13
+ return milestoneId.replace(/[^A-Za-z0-9._-]/g, "_") || "unknown";
14
+ }
15
+ function handoffMarkerPath(basePath, milestoneId) {
16
+ return join(handoffDir(basePath), `${safeMilestoneFileSegment(milestoneId)}.json`);
17
+ }
18
+ export function hasPlannerHandoffBeenOffered(basePath, milestoneId) {
19
+ return existsSync(handoffMarkerPath(basePath, milestoneId));
20
+ }
21
+ export function markPlannerHandoffOffered(basePath, milestoneId, source = "auto") {
22
+ mkdirSync(handoffDir(basePath), { recursive: true });
23
+ writeFileSync(handoffMarkerPath(basePath, milestoneId), JSON.stringify({
24
+ milestoneId,
25
+ source,
26
+ offeredAt: new Date().toISOString(),
27
+ }, null, 2) + "\n", "utf-8");
28
+ }
29
+ export function buildGsdPlannerSpawnPlan(input) {
30
+ const args = ["--project", input.basePath];
31
+ const milestoneId = input.milestoneId?.trim();
32
+ if (milestoneId)
33
+ args.push("--milestone", milestoneId);
34
+ args.push(...(input.extraArgs ?? []));
35
+ return {
36
+ command: GSD_PLANNER_COMMAND,
37
+ args,
38
+ cwd: input.basePath,
39
+ };
40
+ }
41
+ function quoteArg(arg) {
42
+ return /^[A-Za-z0-9_./:=@+-]+$/.test(arg) ? arg : JSON.stringify(arg);
43
+ }
44
+ export function formatGsdPlannerCommand(plan) {
45
+ return [plan.command, ...plan.args].map(quoteArg).join(" ");
46
+ }
47
+ export async function launchGsdPlanner(input, deps = {}) {
48
+ const plan = buildGsdPlannerSpawnPlan(input);
49
+ const spawn = deps.spawn ?? spawnChild;
50
+ let child;
51
+ try {
52
+ child = spawn(plan.command, plan.args, {
53
+ cwd: plan.cwd,
54
+ detached: true,
55
+ stdio: "ignore",
56
+ windowsHide: true,
57
+ });
58
+ }
59
+ catch (err) {
60
+ return {
61
+ status: "failed",
62
+ plan,
63
+ error: err instanceof Error ? err : new Error(String(err)),
64
+ };
65
+ }
66
+ return new Promise((resolve) => {
67
+ let settled = false;
68
+ const settle = (result) => {
69
+ if (settled)
70
+ return;
71
+ settled = true;
72
+ resolve(result);
73
+ };
74
+ child.once("error", (err) => {
75
+ settle({
76
+ status: "failed",
77
+ plan,
78
+ error: err instanceof Error ? err : new Error(String(err)),
79
+ });
80
+ });
81
+ child.once("spawn", () => {
82
+ child.unref();
83
+ settle({ status: "launched", plan });
84
+ });
85
+ });
86
+ }
87
+ export function formatPlannerHandoffPauseReason(milestoneId) {
88
+ return [
89
+ `Milestone ${milestoneId} is planned. Review or customize the plan before implementation if needed.`,
90
+ `Run /gsd planner to launch ${GSD_PLANNER_COMMAND}, or run /gsd auto to continue without planner changes.`,
91
+ ].join(" ");
92
+ }
93
+ export function formatPlannerLaunchUnavailable(plan, error) {
94
+ return [
95
+ `Could not launch ${GSD_PLANNER_COMMAND}: ${error.message}`,
96
+ `Install ${GSD_PLANNER_COMMAND} or run it manually: ${formatGsdPlannerCommand(plan)}`,
97
+ ].join("\n");
98
+ }
@@ -10,6 +10,12 @@ function isInsideBase(basePath, candidate) {
10
10
  function isInsideAnyBase(bases, candidate) {
11
11
  return bases.some((base) => isInsideBase(base, candidate));
12
12
  }
13
+ function resolvesInsideRoot(root, candidate) {
14
+ const resolvedCandidate = isAbsolute(candidate)
15
+ ? resolve(candidate)
16
+ : resolve(root, candidate);
17
+ return isInsideBase(root, resolvedCandidate);
18
+ }
13
19
  /**
14
20
  * Planning IO fields are execution contracts. Absolute paths are only safe when
15
21
  * they stay inside the active working directory; in worktree mode, an absolute
@@ -25,13 +31,25 @@ export function validatePlanningPathScope(basePath, fields, allowedAbsoluteRoots
25
31
  if (!shouldValidatePlanningPathReference(trimmed))
26
32
  continue;
27
33
  const candidate = normalizePlannedFileReference(raw);
28
- const resolvedCandidate = isAbsolute(candidate)
29
- ? resolve(candidate)
30
- : resolve(basePath, candidate);
31
- if (isInsideAnyBase(absoluteRoots, resolvedCandidate))
34
+ if (isAbsolute(candidate)) {
35
+ if (isInsideAnyBase(absoluteRoots, resolve(candidate)))
36
+ continue;
37
+ }
38
+ else if (absoluteRoots.some((root) => resolvesInsideRoot(root, candidate))) {
32
39
  continue;
40
+ }
33
41
  return `${field} contains path outside allowed repository roots: ${candidate}. Use a path within one of: ${absoluteRoots.join(", ")}.`;
34
42
  }
35
43
  }
36
44
  return null;
37
45
  }
46
+ export function validatePathOnlyPlanningFields(fields) {
47
+ for (const { field, values } of fields) {
48
+ for (const raw of values) {
49
+ if (shouldValidatePlanningPathReference(raw))
50
+ continue;
51
+ return `${field} must contain only file paths; invalid entry: ${raw}`;
52
+ }
53
+ }
54
+ return null;
55
+ }
@@ -405,6 +405,14 @@ export function shouldValidatePlanningPathReference(raw) {
405
405
  return false;
406
406
  return shouldValidateInputAsPath(raw);
407
407
  }
408
+ export function extractPlanningPathReference(raw) {
409
+ const trimmed = raw.trim();
410
+ if (!trimmed || NON_PATH_SENTINEL_RE.test(trimmed))
411
+ return null;
412
+ if (!shouldValidateInputAsPath(trimmed))
413
+ return null;
414
+ return extractPathFromAnnotation(trimmed);
415
+ }
408
416
  function shouldValidateInputAsPath(raw) {
409
417
  const trimmed = raw.trim();
410
418
  if (!trimmed)
@@ -421,7 +429,8 @@ function shouldValidateInputAsPath(raw) {
421
429
  return false;
422
430
  if (SCP_PATTERN.test(candidate))
423
431
  return false;
424
- if (/^`+[^`]+`+/.test(trimmed)) {
432
+ const explicitlyWrappedPath = /^`+[^`]+`+/.test(trimmed) || /^(["'])([^"']+)\1$/.test(trimmed);
433
+ if (explicitlyWrappedPath && looksLikePathOrUrl(candidate)) {
425
434
  return true;
426
435
  }
427
436
  if (!/\s/.test(candidate)) {
@@ -431,7 +440,6 @@ function shouldValidateInputAsPath(raw) {
431
440
  candidate.startsWith("./") ||
432
441
  candidate.startsWith("../") ||
433
442
  candidate.startsWith("~/") ||
434
- /[\\/]/.test(candidate) ||
435
443
  /[*?[\]{}]/.test(candidate));
436
444
  }
437
445
  function isRuntimeOnlyInput(raw) {