@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
@@ -1158,7 +1158,10 @@ async function dispatchWorkflow(
1158
1158
  ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
1159
1159
  : undefined,
1160
1160
  baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
1161
- activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
1161
+ // Guided flow starts the MCP workflow server as part of dispatch, so the
1162
+ // parent session's activeTools doesn't include MCP tools yet. The MCP
1163
+ // launch config check (detectWorkflowMcpLaunchConfig) is the right gate
1164
+ // here — not whether MCP tools are pre-registered in the parent session.
1162
1165
  },
1163
1166
  );
1164
1167
  if (compatibilityError) {
@@ -12,7 +12,7 @@
12
12
  import { readFileSync, existsSync, mkdirSync } from "node:fs";
13
13
  import { logWarning } from "./workflow-logger.js";
14
14
  import { isClosedStatus } from "./status-guards.js";
15
- import { join, relative } from "node:path";
15
+ import { dirname, join, relative } from "node:path";
16
16
  import { createRequire } from "node:module";
17
17
  import {
18
18
  getAllMilestones,
@@ -21,7 +21,6 @@ import {
21
21
  getSliceTasks,
22
22
  getTask,
23
23
  getSlice,
24
- getArtifact,
25
24
  insertArtifact,
26
25
  getGateResults,
27
26
  } from "./gsd-db.js";
@@ -109,22 +108,6 @@ function sanitizeInlineRoadmapText(value: string | null | undefined): string {
109
108
  .trim();
110
109
  }
111
110
 
112
- /**
113
- * Load artifact content from the DB. Markdown projections are not authoritative
114
- * during runtime; when the artifact row is missing, callers regenerate from DB
115
- * rows instead of patching disk fallback content and storing it back.
116
- */
117
- function loadArtifactContent(
118
- artifactPath: string,
119
- ): string | null {
120
- const artifact = getArtifact(artifactPath);
121
- if (artifact && artifact.full_content) {
122
- return artifact.full_content;
123
- }
124
-
125
- return null;
126
- }
127
-
128
111
  function resolveRoadmapProjectionPath(basePath: string, milestoneId: string): string {
129
112
  const projectionMilestonesDir = join(gsdProjectionRoot(basePath), "milestones");
130
113
  const milestoneDirName = resolveDir(projectionMilestonesDir, milestoneId) ?? milestoneId;
@@ -397,6 +380,7 @@ export async function renderPlanFromDb(
397
380
  basePath: string,
398
381
  milestoneId: string,
399
382
  sliceId: string,
383
+ outputPath?: string,
400
384
  ): Promise<{ planPath: string; taskPlanPaths: string[]; content: string }> {
401
385
  const slice = getSlice(milestoneId, sliceId);
402
386
  if (!slice) {
@@ -408,9 +392,16 @@ export async function renderPlanFromDb(
408
392
  throw new Error(`no tasks found for ${milestoneId}/${sliceId}`);
409
393
  }
410
394
 
411
- const slicePath = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId);
412
- mkdirSync(slicePath, { recursive: true });
413
- const absPath = join(slicePath, `${sliceId}-PLAN.md`);
395
+ const defaultPlanPath = join(
396
+ gsdProjectionRoot(basePath),
397
+ "milestones",
398
+ milestoneId,
399
+ "slices",
400
+ sliceId,
401
+ `${sliceId}-PLAN.md`,
402
+ );
403
+ const absPath = outputPath ?? defaultPlanPath;
404
+ mkdirSync(dirname(absPath), { recursive: true });
414
405
  const artifactPath = toArtifactPath(absPath, basePath);
415
406
  const sliceGates = getGateResults(milestoneId, sliceId, "slice");
416
407
  const content = renderSlicePlanMarkdown(slice, tasks, sliceGates);
@@ -510,8 +501,14 @@ export async function renderRoadmapCheckboxes(
510
501
  /**
511
502
  * Render plan checkbox states from DB.
512
503
  *
513
- * For each task in the slice, sets [x] if status === 'done',
514
- * [ ] otherwise. Bidirectional.
504
+ * Compatibility wrapper for legacy callers that used to patch plan checkboxes
505
+ * in-place. Plans are now fully regenerated from DB rows (mirroring
506
+ * renderRoadmapCheckboxes) so the projection always reflects the complete
507
+ * current task set and statuses. The previous regex-patch approach reused the
508
+ * cached PLAN artifact as the render input, which silently dropped tasks added
509
+ * to the DB after the artifact was first written — producing a lossy
510
+ * projection (the 4S/0T-vs-5S/13T drift class). The artifacts table is an
511
+ * output sink, never a render input.
515
512
  *
516
513
  * @returns true if the plan was written, false on skip/error
517
514
  */
@@ -519,6 +516,7 @@ export async function renderPlanCheckboxes(
519
516
  basePath: string,
520
517
  milestoneId: string,
521
518
  sliceId: string,
519
+ outputPath?: string,
522
520
  ): Promise<boolean> {
523
521
  const tasks = getSliceTasks(milestoneId, sliceId);
524
522
  if (tasks.length === 0) {
@@ -528,48 +526,7 @@ export async function renderPlanCheckboxes(
528
526
  return false;
529
527
  }
530
528
 
531
- const absPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
532
- const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
533
-
534
- let content: string | null = null;
535
- if (artifactPath) {
536
- content = loadArtifactContent(artifactPath);
537
- }
538
-
539
- if (!content) {
540
- await renderPlanFromDb(basePath, milestoneId, sliceId);
541
- return true;
542
- }
543
-
544
- // Apply checkbox patches for each task
545
- let updated = content;
546
- for (const task of tasks) {
547
- const isDone = isClosedStatus(task.status);
548
- const tid = task.id;
549
-
550
- if (isDone) {
551
- // Set [x]
552
- updated = updated.replace(
553
- new RegExp(`^(\\s*-\\s+)\\[ \\]\\s+\\*\\*${tid}:`, "m"),
554
- `$1[x] **${tid}:`,
555
- );
556
- } else {
557
- // Set [ ]
558
- updated = updated.replace(
559
- new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${tid}:`, "mi"),
560
- `$1[ ] **${tid}:`,
561
- );
562
- }
563
- }
564
-
565
- if (!absPath) return false;
566
-
567
- await writeAndStore(absPath, artifactPath!, updated, {
568
- artifact_type: "PLAN",
569
- milestone_id: milestoneId,
570
- slice_id: sliceId,
571
- });
572
-
529
+ await renderPlanFromDb(basePath, milestoneId, sliceId, outputPath);
573
530
  return true;
574
531
  }
575
532
 
@@ -747,6 +704,18 @@ export async function renderAllFromDb(basePath: string): Promise<RenderAllResult
747
704
  }
748
705
  }
749
706
 
707
+ // Re-project root DECISIONS.md from the authoritative decision records so a
708
+ // full DB → markdown re-projection (recover, rebuild) also heals decisions
709
+ // drift — e.g. a worktree merge that accepted one branch's DECISIONS.md while
710
+ // the DB holds the union of both branches' decisions.
711
+ try {
712
+ const { regenerateDecisionsMarkdown } = await import("./db-writer.js");
713
+ await regenerateDecisionsMarkdown(basePath);
714
+ result.rendered++;
715
+ } catch (err) {
716
+ result.errors.push(`decisions: ${(err as Error).message}`);
717
+ }
718
+
750
719
  return result;
751
720
  }
752
721
 
@@ -832,7 +801,16 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
832
801
  for (const task of tasks) {
833
802
  const isDoneInDb = isClosedStatus(task.status);
834
803
  const planTask = parsed.tasks.find((t: { id: string }) => t.id === task.id);
835
- if (!planTask) continue;
804
+ if (!planTask) {
805
+ // DB has a task the plan markdown lacks: the projection is
806
+ // lossy (e.g. tasks added after the PLAN artifact was first
807
+ // written). Flag it so the plan is re-rendered from DB rows.
808
+ stale.push({
809
+ path: planPath,
810
+ reason: `${task.id} exists in DB but is missing in plan`,
811
+ });
812
+ continue;
813
+ }
836
814
 
837
815
  if (isDoneInDb && !planTask.done) {
838
816
  stale.push({
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // Exports: parseDecisionsTable, parseRequirementsSections, migrateFromMarkdown
6
6
 
7
- import { readFileSync, readdirSync, existsSync } from 'node:fs';
7
+ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
8
8
  import { join, relative } from 'node:path';
9
9
  import type { Decision, Requirement } from './types.js';
10
10
  import {
@@ -16,6 +16,7 @@ import {
16
16
  insertTask,
17
17
  openDatabase,
18
18
  transaction,
19
+ updateMilestoneStatus,
19
20
  updateSliceStatus,
20
21
  _getAdapter,
21
22
  } from './gsd-db.js';
@@ -38,6 +39,27 @@ import { logWarning } from './workflow-logger.js';
38
39
 
39
40
  const VALID_MADE_BY = new Set(['human', 'agent', 'collaborative']);
40
41
 
42
+ const IMPORT_COMPLETE_STATUSES = new Set(['complete', 'done']);
43
+
44
+ /**
45
+ * Completion timestamp for an entity imported as complete: the SUMMARY.md mtime
46
+ * when one exists (deterministic, matches the completion drift handler), else
47
+ * the import time. Crucially this never returns null, so a complete entity is
48
+ * never left with completed_at=null — which would otherwise be permanent,
49
+ * undetectable drift when no SUMMARY file is present.
50
+ */
51
+ function importCompletionTimestamp(summaryPath: string | null): string {
52
+ if (summaryPath && existsSync(summaryPath)) {
53
+ try {
54
+ return statSync(summaryPath).mtime.toISOString();
55
+ } catch (err) {
56
+ // Fall through to import time.
57
+ logWarning('projection', `summary mtime read failed for ${summaryPath}: ${(err as Error).message}`);
58
+ }
59
+ }
60
+ return new Date().toISOString();
61
+ }
62
+
41
63
  /**
42
64
  * Parse a DECISIONS.md markdown table into Decision objects (without seq).
43
65
  * Detects `(amends DXXX)` in the Decision column to build supersession info.
@@ -587,6 +609,14 @@ export function migrateHierarchyToDb(basePath: string): {
587
609
  boundaryMapMarkdown: boundaryMapSection,
588
610
  },
589
611
  });
612
+ // insertMilestone never sets completed_at; backfill it now for milestones
613
+ // imported as complete so the DB is internally coherent immediately after
614
+ // import (rather than relying on a later reconciliation pass that can't even
615
+ // see the drift when no SUMMARY file exists).
616
+ if (IMPORT_COMPLETE_STATUSES.has(milestoneStatus)) {
617
+ const summaryPath = resolveMilestoneFile(basePath, milestoneId, 'SUMMARY');
618
+ updateMilestoneStatus(milestoneId, milestoneStatus, importCompletionTimestamp(summaryPath));
619
+ }
590
620
  counts.milestones++;
591
621
 
592
622
  // Parse roadmap for slices
@@ -614,10 +644,22 @@ export function migrateHierarchyToDb(basePath: string): {
614
644
  depends: sliceEntry.depends,
615
645
  demo: sliceEntry.demo,
616
646
  sequence: si + 1, // Preserve roadmap parse order (#3356)
647
+ isSketch: sliceEntry.isSketch ?? false, // ADR-011: preserve the `[sketch]` flag on re-import
617
648
  planning: {
618
649
  goal: plan?.goal ?? '',
619
650
  },
620
651
  });
652
+ // insertSlice never sets completed_at; backfill it for slices imported as
653
+ // complete (same rationale as milestones above).
654
+ if (IMPORT_COMPLETE_STATUSES.has(sliceStatus)) {
655
+ const sliceSummary = resolveSliceFile(basePath, milestoneId, sliceEntry.id, 'SUMMARY');
656
+ updateSliceStatus(
657
+ milestoneId,
658
+ sliceEntry.id,
659
+ sliceStatus,
660
+ importCompletionTimestamp(sliceSummary),
661
+ );
662
+ }
621
663
  counts.slices++;
622
664
 
623
665
  // Insert tasks from parsed plan
@@ -674,7 +716,12 @@ export function migrateHierarchyToDb(basePath: string): {
674
716
  });
675
717
  if (allTasksDone && hasSliceSummary) {
676
718
  if (_getAdapter()) {
677
- updateSliceStatus(milestoneId, sliceEntry.id, 'complete');
719
+ updateSliceStatus(
720
+ milestoneId,
721
+ sliceEntry.id,
722
+ 'complete',
723
+ importCompletionTimestamp(sliceSummaryPath),
724
+ );
678
725
  process.stderr.write(
679
726
  `gsd-migrate: ${milestoneId}/${sliceEntry.id} all tasks + slice summary complete — upgrading slice to complete\n`,
680
727
  );
@@ -23,7 +23,7 @@ export interface HierarchyCounts {
23
23
 
24
24
  export interface MigrationAutoCheckResult {
25
25
  action: "none" | "recovery-required";
26
- reason: "no-markdown" | "in-sync" | "db-empty" | "count-mismatch";
26
+ reason: "no-markdown" | "in-sync" | "db-empty" | "count-mismatch" | "markdown-missing";
27
27
  markdown: HierarchyCounts;
28
28
  beforeDb: HierarchyCounts;
29
29
  afterDb: HierarchyCounts;
@@ -31,71 +31,146 @@ export interface MigrationAutoCheckResult {
31
31
  message?: string;
32
32
  }
33
33
 
34
+ interface HierarchyScan {
35
+ counts: HierarchyCounts;
36
+ // Fully-qualified identities: milestone "M001", slice "M001/S01",
37
+ // task "M001/S01/T01". Used to detect drift the cardinalities miss (a
38
+ // deleted+added pair nets to the same counts but is real divergence).
39
+ milestones: Set<string>;
40
+ slices: Set<string>;
41
+ tasks: Set<string>;
42
+ }
43
+
34
44
  function zeroCounts(): HierarchyCounts {
35
45
  return { milestones: 0, slices: 0, tasks: 0 };
36
46
  }
37
47
 
48
+ function emptyScan(): HierarchyScan {
49
+ return { counts: zeroCounts(), milestones: new Set(), slices: new Set(), tasks: new Set() };
50
+ }
51
+
38
52
  function sameCounts(a: HierarchyCounts, b: HierarchyCounts): boolean {
39
53
  return a.milestones === b.milestones && a.slices === b.slices && a.tasks === b.tasks;
40
54
  }
41
55
 
42
- export function countMarkdownHierarchy(basePath: string): HierarchyCounts {
56
+ function setsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
57
+ if (a.size !== b.size) return false;
58
+ for (const value of a) if (!b.has(value)) return false;
59
+ return true;
60
+ }
61
+
62
+ function scanIdentitiesMatch(a: HierarchyScan, b: HierarchyScan): boolean {
63
+ return (
64
+ setsEqual(a.milestones, b.milestones) &&
65
+ setsEqual(a.slices, b.slices) &&
66
+ setsEqual(a.tasks, b.tasks)
67
+ );
68
+ }
69
+
70
+ /** True if any element of `a` is absent from `b`. */
71
+ function hasExtra(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
72
+ for (const value of a) if (!b.has(value)) return true;
73
+ return false;
74
+ }
75
+
76
+ function scanHasExtraIdentities(a: HierarchyScan, b: HierarchyScan): boolean {
77
+ return (
78
+ hasExtra(a.milestones, b.milestones) ||
79
+ hasExtra(a.slices, b.slices) ||
80
+ hasExtra(a.tasks, b.tasks)
81
+ );
82
+ }
83
+
84
+ /**
85
+ * True when the DB holds any milestone/slice/task identity the markdown lacks —
86
+ * i.e. a `/gsd recover --confirm` (markdown → DB) would DELETE authoritative DB
87
+ * rows. This is identity-based, so it catches equal-count divergence (e.g. DB
88
+ * slice `S99` vs markdown `S01`) that a cardinality-only check misses. Used by
89
+ * the recover data-loss guard.
90
+ */
91
+ export function recoverWouldDeleteDbRows(basePath: string): boolean {
92
+ return scanHasExtraIdentities(scanDbHierarchy(), scanMarkdownHierarchy(basePath));
93
+ }
94
+
95
+ export function scanMarkdownHierarchy(basePath: string): HierarchyScan {
43
96
  const root = milestonesDir(basePath);
44
- if (!existsSync(root)) return zeroCounts();
97
+ if (!existsSync(root)) return emptyScan();
45
98
 
46
- const counts = zeroCounts();
99
+ const scan = emptyScan();
47
100
  for (const entry of readdirSync(root, { withFileTypes: true })) {
48
101
  if (!entry.isDirectory() || !/^M\d+/.test(entry.name)) continue;
49
- counts.milestones++;
50
-
51
- const roadmapPath = resolveMilestoneFile(basePath, entry.name, "ROADMAP");
102
+ // Use the CANONICAL milestone id (e.g. "M001" or "M001-a1b2c3"), matching
103
+ // scanDbHierarchy's milestone.id — not the raw directory name, which may
104
+ // carry a legacy descriptor (e.g. "M001-some-descriptor"). The canonical id
105
+ // both keys the identity sets AND resolves files correctly: resolveFile's
106
+ // prefix/legacy-pattern matching handles the descriptor dir either way.
107
+ const milestoneId = entry.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/)?.[1] ?? entry.name;
108
+ scan.counts.milestones++;
109
+ scan.milestones.add(milestoneId);
110
+
111
+ const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
52
112
  if (!roadmapPath || !existsSync(roadmapPath)) continue;
53
113
 
54
114
  const roadmap = parseRoadmap(readFileSync(roadmapPath, "utf-8"));
55
- counts.slices += roadmap.slices.length;
115
+ scan.counts.slices += roadmap.slices.length;
56
116
 
57
117
  for (const slice of roadmap.slices) {
58
- const planPath = resolveSliceFile(basePath, entry.name, slice.id, "PLAN");
118
+ scan.slices.add(`${milestoneId}/${slice.id}`);
119
+ const planPath = resolveSliceFile(basePath, milestoneId, slice.id, "PLAN");
59
120
  if (!planPath || !existsSync(planPath)) continue;
60
121
  const plan = parsePlan(readFileSync(planPath, "utf-8"));
61
- counts.tasks += plan.tasks.length;
122
+ scan.counts.tasks += plan.tasks.length;
123
+ for (const task of plan.tasks) {
124
+ scan.tasks.add(`${milestoneId}/${slice.id}/${task.id}`);
125
+ }
62
126
  }
63
127
  }
64
128
 
65
- return counts;
129
+ return scan;
66
130
  }
67
131
 
68
- export function countDbHierarchy(): HierarchyCounts {
69
- if (!isDbAvailable()) return zeroCounts();
70
- const counts = zeroCounts();
132
+ export function scanDbHierarchy(): HierarchyScan {
133
+ if (!isDbAvailable()) return emptyScan();
134
+ const scan = emptyScan();
71
135
  const milestones = getAllMilestones();
72
- counts.milestones = milestones.length;
136
+ scan.counts.milestones = milestones.length;
73
137
 
74
138
  for (const milestone of milestones) {
139
+ scan.milestones.add(milestone.id);
75
140
  const slices = getMilestoneSlices(milestone.id);
76
- counts.slices += slices.length;
141
+ scan.counts.slices += slices.length;
77
142
  for (const slice of slices) {
78
- counts.tasks += getSliceTasks(milestone.id, slice.id).length;
143
+ scan.slices.add(`${milestone.id}/${slice.id}`);
144
+ const tasks = getSliceTasks(milestone.id, slice.id);
145
+ scan.counts.tasks += tasks.length;
146
+ for (const task of tasks) {
147
+ scan.tasks.add(`${milestone.id}/${slice.id}/${task.id}`);
148
+ }
79
149
  }
80
150
  }
81
151
 
82
- return counts;
152
+ return scan;
153
+ }
154
+
155
+ export function countMarkdownHierarchy(basePath: string): HierarchyCounts {
156
+ return scanMarkdownHierarchy(basePath).counts;
157
+ }
158
+
159
+ export function countDbHierarchy(): HierarchyCounts {
160
+ return scanDbHierarchy().counts;
83
161
  }
84
162
 
85
163
  export async function checkMarkdownHierarchyAgainstDb(
86
164
  basePath: string,
87
165
  ): Promise<MigrationAutoCheckResult> {
88
- const markdown = countMarkdownHierarchy(basePath);
89
- if (sameCounts(markdown, zeroCounts())) {
90
- return {
91
- action: "none",
92
- reason: "no-markdown",
93
- markdown,
94
- beforeDb: zeroCounts(),
95
- afterDb: zeroCounts(),
96
- };
97
- }
98
-
166
+ const markdownScan = scanMarkdownHierarchy(basePath);
167
+ const markdown = markdownScan.counts;
168
+
169
+ // Always open the DB before deciding. An empty markdown tree does NOT imply
170
+ // an empty project — the DB may hold authoritative rows whose markdown was
171
+ // lost, which is itself recoverable drift. The previous early return here
172
+ // skipped the DB entirely and silently hid a populated-DB/empty-markdown
173
+ // project.
99
174
  const opened = await ensureDbOpen(basePath);
100
175
  if (!opened || !isDbAvailable()) {
101
176
  throw new Error(`failed to open or create the GSD database at ${basePath}`);
@@ -106,12 +181,58 @@ export async function checkMarkdownHierarchyAgainstDb(
106
181
  // warn from a stale long-lived SQLite handle.
107
182
  refreshOpenDatabaseFromDisk();
108
183
 
109
- const beforeDb = countDbHierarchy();
110
- if (sameCounts(markdown, beforeDb)) {
184
+ const dbScan = scanDbHierarchy();
185
+ const beforeDb = dbScan.counts;
186
+
187
+ const markdownEmpty = sameCounts(markdown, zeroCounts());
188
+ const dbEmpty = sameCounts(beforeDb, zeroCounts());
189
+
190
+ // Genuinely empty project: nothing on disk, nothing in the DB.
191
+ if (markdownEmpty && dbEmpty) {
192
+ return { action: "none", reason: "no-markdown", markdown, beforeDb, afterDb: beforeDb };
193
+ }
194
+
195
+ // In sync only when both cardinalities AND identities agree. Identity
196
+ // comparison catches drift the counts miss (e.g. a slice deleted from the DB
197
+ // and a different one added nets to the same count but is real divergence,
198
+ // and a missing PLAN.md vs DB tasks shows up as a task-identity gap).
199
+ if (sameCounts(markdown, beforeDb) && scanIdentitiesMatch(markdownScan, dbScan)) {
111
200
  return { action: "none", reason: "in-sync", markdown, beforeDb, afterDb: beforeDb };
112
201
  }
113
202
 
114
- const reason = sameCounts(beforeDb, zeroCounts()) ? "db-empty" : "count-mismatch";
203
+ // Choose the SAFE repair direction by IDENTITY, not cardinality. Recover
204
+ // imports markdown → DB and DELETES any DB row markdown lacks, so it must
205
+ // never be recommended when the DB holds identities the markdown is missing —
206
+ // including equal-count divergence (DB `S99` vs markdown `S01`), which a
207
+ // count-only check would wrongly route to recover. Whenever the DB holds rows
208
+ // markdown lacks, the correct repair is to re-project from the DB (rebuild).
209
+ const dbHasExtra = scanHasExtraIdentities(dbScan, markdownScan);
210
+
211
+ const countsLine =
212
+ `Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
213
+ `do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). `;
214
+
215
+ // The DB holds rows markdown lacks (richer, identity-diverged, or markdown
216
+ // entirely missing): re-project from the DB. Recover here would destroy data.
217
+ if (dbHasExtra) {
218
+ return {
219
+ action: "recovery-required",
220
+ reason: markdownEmpty ? "markdown-missing" : "count-mismatch",
221
+ markdown,
222
+ beforeDb,
223
+ afterDb: beforeDb,
224
+ recoveryCommand: "/gsd rebuild markdown",
225
+ message:
226
+ countsLine +
227
+ "The DB holds rows the markdown lacks, so the markdown projection is stale. " +
228
+ "Run `/gsd rebuild markdown` to re-project from the authoritative DB. " +
229
+ "Do NOT run `/gsd recover --confirm` here — it would delete the extra DB rows.",
230
+ };
231
+ }
232
+
233
+ // DB is empty (or markdown is strictly richer): markdown is the surviving
234
+ // source to import.
235
+ const reason = dbEmpty ? "db-empty" : "count-mismatch";
115
236
  return {
116
237
  action: "recovery-required",
117
238
  reason,
@@ -120,8 +241,7 @@ export async function checkMarkdownHierarchyAgainstDb(
120
241
  afterDb: beforeDb,
121
242
  recoveryCommand: "/gsd recover --confirm",
122
243
  message:
123
- `Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
124
- `do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). ` +
244
+ countsLine +
125
245
  "Runtime startup will not import markdown automatically; run `/gsd recover --confirm` if markdown should repopulate the database.",
126
246
  };
127
247
  }
@@ -60,11 +60,31 @@ export function parseRoadmap(content: string): Roadmap {
60
60
  return cachedParse(content, 'roadmap', _parseRoadmapImpl);
61
61
  }
62
62
 
63
+ /**
64
+ * ADR-011: the roadmap renderer writes a `[sketch]` badge for sketch slices,
65
+ * but the native parser does not surface it. Re-scan the markdown and set
66
+ * isSketch on the matching slice so the flag survives a markdown → DB re-import
67
+ * (e.g. /gsd recover) instead of being silently dropped.
68
+ */
69
+ function applySketchFlags(roadmap: Roadmap, content: string): void {
70
+ const sketchIds = new Set<string>();
71
+ for (const line of content.split("\n")) {
72
+ if (!/\[sketch\]/i.test(line)) continue;
73
+ const m = line.match(/\*\*([\w.]+):/);
74
+ if (m) sketchIds.add(m[1]!);
75
+ }
76
+ if (sketchIds.size === 0) return;
77
+ for (const slice of roadmap.slices) {
78
+ if (sketchIds.has(slice.id)) slice.isSketch = true;
79
+ }
80
+ }
81
+
63
82
  function _parseRoadmapImpl(content: string): Roadmap {
64
83
  const stopTimer = debugTime("parse-roadmap");
65
84
  // Try native parser first for better performance
66
85
  const nativeResult = nativeParseRoadmap(content);
67
86
  if (nativeResult) {
87
+ applySketchFlags(nativeResult, content);
68
88
  stopTimer({ native: true, slices: nativeResult.slices.length, boundaryEntries: nativeResult.boundaryMap.length });
69
89
  debugCount("parseRoadmapCalls");
70
90
  return nativeResult;