@opengsd/gsd-pi 1.2.0-dev.4813ead6 → 1.2.0-dev.5457a158

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 (342) hide show
  1. package/dist/cli-style.d.ts +17 -0
  2. package/dist/cli-style.js +28 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/headless-events.d.ts +4 -2
  5. package/dist/headless-events.js +7 -29
  6. package/dist/models-resolver.d.ts +3 -13
  7. package/dist/models-resolver.js +3 -22
  8. package/dist/resource-loader.js +2 -14
  9. package/dist/resources/.managed-resources-content-hash +1 -1
  10. package/dist/resources/extensions/bg-shell/utilities.js +5 -2
  11. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  12. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +35 -4
  13. package/dist/resources/extensions/gsd/auto/orchestrator.js +33 -4
  14. package/dist/resources/extensions/gsd/auto/phases.js +6 -1
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -8
  16. package/dist/resources/extensions/gsd/auto-prompts.js +3 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +12 -14
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
  19. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +7 -16
  20. package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
  21. package/dist/resources/extensions/gsd/auto-worktree.js +35 -352
  22. package/dist/resources/extensions/gsd/auto.js +8 -20
  23. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -2
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -6
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +86 -6
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +30 -4
  27. package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
  28. package/dist/resources/extensions/gsd/captures.js +5 -15
  29. package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
  30. package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
  31. package/dist/resources/extensions/gsd/crash-recovery.js +4 -12
  32. package/dist/resources/extensions/gsd/db/engine.js +755 -0
  33. package/dist/resources/extensions/gsd/db/queries.js +372 -0
  34. package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
  35. package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
  36. package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
  37. package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
  38. package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
  39. package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
  40. package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
  41. package/dist/resources/extensions/gsd/doctor-format.js +9 -6
  42. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
  43. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
  44. package/dist/resources/extensions/gsd/error-classifier.js +9 -0
  45. package/dist/resources/extensions/gsd/git-service.js +1 -0
  46. package/dist/resources/extensions/gsd/gitignore.js +3 -0
  47. package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
  48. package/dist/resources/extensions/gsd/guidance.js +98 -0
  49. package/dist/resources/extensions/gsd/guided-flow.js +51 -5
  50. package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
  51. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
  52. package/dist/resources/extensions/gsd/migrate/safety.js +20 -9
  53. package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
  54. package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
  55. package/dist/resources/extensions/gsd/model-router.js +3 -0
  56. package/dist/resources/extensions/gsd/notification-store.js +11 -4
  57. package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
  58. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
  59. package/dist/resources/extensions/gsd/paths.js +37 -24
  60. package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
  61. package/dist/resources/extensions/gsd/preferences-models.js +12 -46
  62. package/dist/resources/extensions/gsd/preferences.js +14 -0
  63. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  64. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  66. package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
  67. package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
  68. package/dist/resources/extensions/gsd/publication.js +87 -0
  69. package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
  70. package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
  71. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
  72. package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
  73. package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
  74. package/dist/resources/extensions/gsd/state.js +1 -20
  75. package/dist/resources/extensions/gsd/status-guards.js +56 -8
  76. package/dist/resources/extensions/gsd/stop-notice.js +57 -0
  77. package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
  78. package/dist/resources/extensions/gsd/tools/complete-slice.js +24 -43
  79. package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -8
  80. package/dist/resources/extensions/gsd/tools/plan-slice.js +12 -6
  81. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
  82. package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
  83. package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
  84. package/dist/resources/extensions/gsd/undo.js +8 -7
  85. package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
  86. package/dist/resources/extensions/gsd/unit-context-composer.js +9 -1
  87. package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
  88. package/dist/resources/extensions/gsd/unit-registry.js +350 -0
  89. package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
  90. package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
  91. package/dist/resources/extensions/gsd/worktree-git-recovery.js +293 -0
  92. package/dist/resources/extensions/gsd/worktree-lifecycle.js +9 -1
  93. package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
  94. package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
  95. package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
  96. package/dist/resources/extensions/gsd/worktree-root.js +28 -6
  97. package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
  98. package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
  99. package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
  100. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  101. package/dist/web/standalone/.next/BUILD_ID +1 -1
  102. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  103. package/dist/web/standalone/.next/build-manifest.json +2 -2
  104. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  105. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  106. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  124. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  126. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  128. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  130. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  132. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  134. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  136. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  138. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/index.html +1 -1
  156. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  157. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  158. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  159. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  160. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  161. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  162. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  163. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  164. package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
  165. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  166. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  168. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  169. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  170. package/dist/worktree-cli.js +3 -6
  171. package/dist/worktree-status-banner.js +7 -11
  172. package/package.json +1 -1
  173. package/packages/cloud-mcp-gateway/package.json +2 -2
  174. package/packages/contracts/dist/workflow.d.ts +4 -0
  175. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  176. package/packages/contracts/dist/workflow.js.map +1 -1
  177. package/packages/contracts/package.json +1 -1
  178. package/packages/daemon/package.json +4 -4
  179. package/packages/gsd-agent-core/package.json +5 -5
  180. package/packages/gsd-agent-modes/package.json +7 -7
  181. package/packages/mcp-server/dist/cli.js +6 -3
  182. package/packages/mcp-server/dist/cli.js.map +1 -1
  183. package/packages/mcp-server/dist/workflow-tools.d.ts +8 -0
  184. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  185. package/packages/mcp-server/dist/workflow-tools.js +46 -21
  186. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  187. package/packages/mcp-server/package.json +3 -3
  188. package/packages/native/package.json +1 -1
  189. package/packages/pi-agent-core/package.json +1 -1
  190. package/packages/pi-ai/dist/image-models.generated.d.ts +0 -30
  191. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  192. package/packages/pi-ai/dist/image-models.generated.js +0 -30
  193. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  194. package/packages/pi-ai/dist/models.generated.d.ts +361 -255
  195. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  196. package/packages/pi-ai/dist/models.generated.js +311 -256
  197. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  198. package/packages/pi-ai/package.json +1 -1
  199. package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
  200. package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
  201. package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
  202. package/packages/pi-coding-agent/package.json +7 -7
  203. package/packages/pi-tui/package.json +2 -2
  204. package/packages/rpc-client/package.json +2 -2
  205. package/pkg/package.json +1 -1
  206. package/src/resources/extensions/bg-shell/utilities.ts +5 -2
  207. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  208. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +37 -2
  209. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
  210. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
  211. package/src/resources/extensions/gsd/auto/orchestrator.ts +39 -5
  212. package/src/resources/extensions/gsd/auto/phases.ts +10 -1
  213. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -7
  214. package/src/resources/extensions/gsd/auto-prompts.ts +3 -0
  215. package/src/resources/extensions/gsd/auto-start.ts +12 -15
  216. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  217. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +10 -17
  218. package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
  219. package/src/resources/extensions/gsd/auto-worktree.ts +41 -364
  220. package/src/resources/extensions/gsd/auto.ts +20 -24
  221. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -5
  222. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -6
  223. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +87 -6
  224. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +29 -3
  225. package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
  226. package/src/resources/extensions/gsd/captures.ts +5 -16
  227. package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
  228. package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
  229. package/src/resources/extensions/gsd/crash-recovery.ts +3 -9
  230. package/src/resources/extensions/gsd/db/engine.ts +809 -0
  231. package/src/resources/extensions/gsd/db/queries.ts +453 -0
  232. package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
  233. package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
  234. package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
  235. package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
  236. package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
  237. package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
  238. package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
  239. package/src/resources/extensions/gsd/doctor-format.ts +12 -7
  240. package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
  241. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
  242. package/src/resources/extensions/gsd/error-classifier.ts +11 -0
  243. package/src/resources/extensions/gsd/git-service.ts +1 -0
  244. package/src/resources/extensions/gsd/gitignore.ts +3 -0
  245. package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
  246. package/src/resources/extensions/gsd/guidance.ts +139 -0
  247. package/src/resources/extensions/gsd/guided-flow.ts +50 -5
  248. package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
  249. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
  250. package/src/resources/extensions/gsd/migrate/safety.ts +18 -7
  251. package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
  252. package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
  253. package/src/resources/extensions/gsd/model-router.ts +3 -0
  254. package/src/resources/extensions/gsd/notification-store.ts +26 -3
  255. package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
  256. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
  257. package/src/resources/extensions/gsd/paths.ts +42 -22
  258. package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
  259. package/src/resources/extensions/gsd/preferences-models.ts +10 -46
  260. package/src/resources/extensions/gsd/preferences.ts +18 -0
  261. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  262. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  263. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  264. package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
  265. package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
  266. package/src/resources/extensions/gsd/publication.ts +122 -0
  267. package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
  268. package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
  269. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
  270. package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
  271. package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
  272. package/src/resources/extensions/gsd/state.ts +4 -21
  273. package/src/resources/extensions/gsd/status-guards.ts +59 -8
  274. package/src/resources/extensions/gsd/stop-notice.ts +75 -0
  275. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
  276. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
  277. package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
  278. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
  279. package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
  280. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
  281. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -0
  282. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
  283. package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
  284. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
  285. package/src/resources/extensions/gsd/tests/guidance.test.ts +125 -0
  286. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +51 -4
  287. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +54 -1
  288. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
  289. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
  290. package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
  291. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
  292. package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
  293. package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
  294. package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
  295. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +248 -1
  296. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +1 -0
  297. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
  298. package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
  299. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
  300. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
  301. package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
  302. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
  303. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
  304. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
  305. package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
  306. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +23 -2
  307. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
  308. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  309. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +2 -2
  310. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
  311. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
  312. package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
  313. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
  314. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
  315. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
  316. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
  317. package/src/resources/extensions/gsd/tests/write-gate.test.ts +42 -0
  318. package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
  319. package/src/resources/extensions/gsd/tools/complete-slice.ts +23 -58
  320. package/src/resources/extensions/gsd/tools/exec-tool.ts +5 -8
  321. package/src/resources/extensions/gsd/tools/plan-slice.ts +12 -6
  322. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
  323. package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
  324. package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
  325. package/src/resources/extensions/gsd/undo.ts +9 -8
  326. package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
  327. package/src/resources/extensions/gsd/unit-context-composer.ts +12 -1
  328. package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
  329. package/src/resources/extensions/gsd/unit-registry.ts +425 -0
  330. package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
  331. package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
  332. package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
  333. package/src/resources/extensions/gsd/worktree-lifecycle.ts +10 -1
  334. package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
  335. package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
  336. package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
  337. package/src/resources/extensions/gsd/worktree-root.ts +29 -6
  338. package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
  339. package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
  340. package/src/resources/skills/gsd-browser/SKILL.md +1 -1
  341. /package/dist/web/standalone/.next/static/{tkLHUSzPA2kMmWz4DmGwI → 2p9Rv9pQflAxCBbGVI2vb}/_buildManifest.js +0 -0
  342. /package/dist/web/standalone/.next/static/{tkLHUSzPA2kMmWz4DmGwI → 2p9Rv9pQflAxCBbGVI2vb}/_ssgManifest.js +0 -0
@@ -0,0 +1,500 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Worktree DB reconciliation writers for the single-writer layer.
3
+ // Owns copyWorktreeDb + reconcileWorktreeDb: the ATTACH-and-merge of an
4
+ // auto-worktree's gsd.db back into the project-root DB, with conflict
5
+ // detection. Reads the shared engine handle via getDbOrNull(); opens the
6
+ // project-root DB via the engine's openDatabase().
7
+ import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { createHash } from "node:crypto";
10
+ import { logError, logWarning } from "../../workflow-logger.js";
11
+ import { getDbOrNull, openDatabase } from "../engine.js";
12
+ import { TERMINAL_STATUS_SQL } from "../sql-constants.js";
13
+
14
+ export function copyWorktreeDb(srcDbPath: string, destDbPath: string): boolean {
15
+ try {
16
+ if (!existsSync(srcDbPath)) return false;
17
+ const destDir = dirname(destDbPath);
18
+ mkdirSync(destDir, { recursive: true });
19
+ copyFileSync(srcDbPath, destDbPath);
20
+ return true;
21
+ } catch (err) {
22
+ logError("db", "failed to copy DB to worktree", { error: (err as Error).message });
23
+ return false;
24
+ }
25
+ }
26
+
27
+ export interface ReconcileResult {
28
+ decisions: number;
29
+ requirements: number;
30
+ artifacts: number;
31
+ milestones: number;
32
+ slices: number;
33
+ tasks: number;
34
+ memories: number;
35
+ replan_history: number;
36
+ assessments: number;
37
+ quality_gates: number;
38
+ slice_dependencies: number;
39
+ verification_evidence: number;
40
+ gate_runs: number;
41
+ milestone_commit_attributions: number;
42
+ conflicts: string[];
43
+ }
44
+
45
+ export function reconcileWorktreeDb(
46
+ mainDbPath: string,
47
+ worktreeDbPath: string,
48
+ ): ReconcileResult {
49
+ const zero: ReconcileResult = {
50
+ decisions: 0,
51
+ requirements: 0,
52
+ artifacts: 0,
53
+ milestones: 0,
54
+ slices: 0,
55
+ tasks: 0,
56
+ memories: 0,
57
+ replan_history: 0,
58
+ assessments: 0,
59
+ quality_gates: 0,
60
+ slice_dependencies: 0,
61
+ verification_evidence: 0,
62
+ gate_runs: 0,
63
+ milestone_commit_attributions: 0,
64
+ conflicts: [],
65
+ };
66
+ if (!existsSync(worktreeDbPath)) return zero;
67
+ // Guard: bail when both paths resolve to the same physical file.
68
+ // ATTACHing a WAL-mode DB to itself corrupts the WAL (#2823).
69
+ try {
70
+ if (realpathSync(mainDbPath) === realpathSync(worktreeDbPath)) return zero;
71
+ } catch (e) { logWarning("db", `realpathSync failed: ${(e as Error).message}`); }
72
+ // Sanitize path: reject any characters that could break ATTACH syntax.
73
+ // ATTACH DATABASE doesn't support parameterized paths in all providers,
74
+ // so we use strict allowlist validation instead.
75
+ if (/['";\x00]/.test(worktreeDbPath)) {
76
+ logError("db", "worktree DB reconciliation failed: path contains unsafe characters");
77
+ return zero;
78
+ }
79
+ if (!getDbOrNull()!) {
80
+ const opened = openDatabase(mainDbPath);
81
+ if (!opened) {
82
+ logError("db", "worktree DB reconciliation failed: cannot open main DB");
83
+ return zero;
84
+ }
85
+ }
86
+ const adapter = getDbOrNull()!!;
87
+ const conflicts: string[] = [];
88
+ try {
89
+ adapter.exec(`ATTACH DATABASE '${worktreeDbPath}' AS wt`);
90
+ try {
91
+ function countChanges(result: unknown): number {
92
+ return typeof result === "object" && result !== null ? ((result as { changes?: number }).changes ?? 0) : 0;
93
+ }
94
+
95
+ function wtTableInfo(tableName: string): Array<Record<string, unknown>> {
96
+ return adapter.prepare(`PRAGMA wt.table_info('${tableName}')`).all() as Array<Record<string, unknown>>;
97
+ }
98
+
99
+ const wtInfo = wtTableInfo("decisions");
100
+ const hasWtDecisions = wtInfo.length > 0;
101
+ const hasMadeBy = wtInfo.some((col) => col["name"] === "made_by");
102
+ // ADR-011: worktree may predate schema v16/v17. For missing columns we
103
+ // fall through to the main DB's existing value (not a literal default)
104
+ // so reconcile never silently clears state the main tree has recorded.
105
+ const hasDecisionSource = wtInfo.some((col) => col["name"] === "source");
106
+ const wtRequirementInfo = wtTableInfo("requirements");
107
+ const hasWtRequirements = wtRequirementInfo.length > 0;
108
+ const wtMilestoneInfo = wtTableInfo("milestones");
109
+ const hasWtMilestones = wtMilestoneInfo.length > 0;
110
+ const hasMilestoneSequence = wtMilestoneInfo.some((col) => col["name"] === "sequence");
111
+ const wtSliceInfo = wtTableInfo("slices");
112
+ const hasWtSlices = wtSliceInfo.length > 0;
113
+ const hasIsSketch = wtSliceInfo.some((col) => col["name"] === "is_sketch");
114
+ const hasSketchScope = wtSliceInfo.some((col) => col["name"] === "sketch_scope");
115
+ const hasSliceTargetRepositories = wtSliceInfo.some((col) => col["name"] === "target_repositories");
116
+ const wtTaskInfo = wtTableInfo("tasks");
117
+ const hasWtTasks = wtTaskInfo.length > 0;
118
+ const hasTaskTargetRepositories = wtTaskInfo.some((col) => col["name"] === "target_repositories");
119
+ const hasBlockerSource = wtTaskInfo.some((col) => col["name"] === "blocker_source");
120
+ const hasEscalationPending = wtTaskInfo.some((col) => col["name"] === "escalation_pending");
121
+ const hasEscalationAwaiting = wtTaskInfo.some((col) => col["name"] === "escalation_awaiting_review");
122
+ const hasEscalationArtifact = wtTaskInfo.some((col) => col["name"] === "escalation_artifact_path");
123
+ const hasEscalationOverride = wtTaskInfo.some((col) => col["name"] === "escalation_override_applied_at");
124
+ const wtArtifactInfo = wtTableInfo("artifacts");
125
+ const hasWtArtifacts = wtArtifactInfo.length > 0;
126
+ const wtMemoryInfo = wtTableInfo("memories");
127
+ const hasWtMemories = wtMemoryInfo.length > 0;
128
+ const hasMemoryScope = wtMemoryInfo.some((col) => col["name"] === "scope");
129
+ const hasMemoryTags = wtMemoryInfo.some((col) => col["name"] === "tags");
130
+ const hasMemoryStructuredFields = wtMemoryInfo.some((col) => col["name"] === "structured_fields");
131
+ const hasMemoryLastHitAt = wtMemoryInfo.some((col) => col["name"] === "last_hit_at");
132
+ const hasWtReplanHistory = wtTableInfo("replan_history").length > 0;
133
+ const hasWtAssessments = wtTableInfo("assessments").length > 0;
134
+ const hasWtQualityGates = wtTableInfo("quality_gates").length > 0;
135
+ const hasWtSliceDependencies = wtTableInfo("slice_dependencies").length > 0;
136
+ const hasWtVerificationEvidence = wtTableInfo("verification_evidence").length > 0;
137
+ const hasWtGateRuns = wtTableInfo("gate_runs").length > 0;
138
+ const hasWtMilestoneCommitAttributions = wtTableInfo("milestone_commit_attributions").length > 0;
139
+
140
+ if (hasWtDecisions) {
141
+ const decConf = adapter.prepare(
142
+ `SELECT m.id FROM decisions m INNER JOIN wt.decisions w ON m.id = w.id WHERE m.decision != w.decision OR m.choice != w.choice OR m.rationale != w.rationale OR ${
143
+ hasMadeBy ? "m.made_by != w.made_by" : "'agent' != 'agent'"
144
+ } OR m.superseded_by IS NOT w.superseded_by`,
145
+ ).all();
146
+ for (const row of decConf) conflicts.push(`decision ${(row as Record<string, unknown>)["id"]}: modified in both`);
147
+ }
148
+
149
+ if (hasWtRequirements) {
150
+ const reqConf = adapter.prepare(
151
+ `SELECT m.id FROM requirements m INNER JOIN wt.requirements w ON m.id = w.id WHERE m.description != w.description OR m.status != w.status OR m.notes != w.notes OR m.superseded_by IS NOT w.superseded_by`,
152
+ ).all();
153
+ for (const row of reqConf) conflicts.push(`requirement ${(row as Record<string, unknown>)["id"]}: modified in both`);
154
+ }
155
+
156
+ const merged: Omit<ReconcileResult, "conflicts"> = {
157
+ decisions: 0,
158
+ requirements: 0,
159
+ artifacts: 0,
160
+ milestones: 0,
161
+ slices: 0,
162
+ tasks: 0,
163
+ memories: 0,
164
+ replan_history: 0,
165
+ assessments: 0,
166
+ quality_gates: 0,
167
+ slice_dependencies: 0,
168
+ verification_evidence: 0,
169
+ gate_runs: 0,
170
+ milestone_commit_attributions: 0,
171
+ };
172
+ const sliceTargetRepositoriesSql = hasSliceTargetRepositories
173
+ ? `CASE
174
+ WHEN w.target_repositories = '[]' AND COALESCE(m.target_repositories, '[]') <> '[]'
175
+ THEN m.target_repositories
176
+ ELSE COALESCE(w.target_repositories, m.target_repositories, '[]')
177
+ END`
178
+ : "COALESCE(m.target_repositories, '[]')";
179
+ const taskTargetRepositoriesSql = hasTaskTargetRepositories
180
+ ? `CASE
181
+ WHEN w.target_repositories = '[]' AND COALESCE(m.target_repositories, '[]') <> '[]'
182
+ THEN m.target_repositories
183
+ ELSE COALESCE(w.target_repositories, m.target_repositories, '[]')
184
+ END`
185
+ : "COALESCE(m.target_repositories, '[]')";
186
+
187
+ adapter.exec("BEGIN");
188
+ try {
189
+ // Join the target decisions so we can prefer an existing main.source
190
+ // when the worktree predates v16 — otherwise a write-through reconcile
191
+ // would clobber 'escalation'-sourced decisions with the literal default.
192
+ if (hasWtDecisions) {
193
+ merged.decisions = countChanges(adapter.prepare(`
194
+ INSERT INTO decisions (
195
+ id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by
196
+ )
197
+ SELECT w.id, w.when_context, w.scope, w.decision, w.choice, w.rationale, w.revisable, ${
198
+ hasMadeBy ? "w.made_by" : "COALESCE(m.made_by, 'agent')"
199
+ }, ${
200
+ hasDecisionSource ? "w.source" : "COALESCE(m.source, 'discussion')"
201
+ }, w.superseded_by
202
+ FROM wt.decisions w
203
+ LEFT JOIN decisions m ON m.id = w.id
204
+ WHERE true
205
+ ON CONFLICT(id) DO UPDATE SET
206
+ when_context = excluded.when_context,
207
+ scope = excluded.scope,
208
+ decision = excluded.decision,
209
+ choice = excluded.choice,
210
+ rationale = excluded.rationale,
211
+ revisable = excluded.revisable,
212
+ made_by = excluded.made_by,
213
+ source = excluded.source,
214
+ superseded_by = excluded.superseded_by
215
+ `).run());
216
+ }
217
+
218
+ if (hasWtRequirements) {
219
+ merged.requirements = countChanges(adapter.prepare(`
220
+ INSERT OR REPLACE INTO requirements (
221
+ id, class, status, description, why, source, primary_owner,
222
+ supporting_slices, validation, notes, full_content, superseded_by
223
+ )
224
+ SELECT id, class, status, description, why, source, primary_owner,
225
+ supporting_slices, validation, notes, full_content, superseded_by
226
+ FROM wt.requirements
227
+ `).run());
228
+ }
229
+
230
+ // Always recompute artifact hashes from the content being merged. Older
231
+ // worktree DBs may not have content_hash at all, and migrated old DBs can
232
+ // carry stale default/null hashes after their content changed.
233
+ if (hasWtArtifacts) {
234
+ const artifactRows = adapter.prepare(`
235
+ SELECT path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
236
+ FROM wt.artifacts
237
+ `).all() as Array<Record<string, unknown>>;
238
+ const artifactStmt = adapter.prepare(`
239
+ INSERT OR REPLACE INTO artifacts (
240
+ path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
241
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
242
+ `);
243
+ for (const row of artifactRows) {
244
+ const fullContent = String(row["full_content"] ?? "");
245
+ merged.artifacts += countChanges(artifactStmt.run(
246
+ row["path"],
247
+ row["artifact_type"],
248
+ row["milestone_id"] ?? null,
249
+ row["slice_id"] ?? null,
250
+ row["task_id"] ?? null,
251
+ fullContent,
252
+ row["imported_at"],
253
+ createHash("sha256").update(fullContent).digest("hex"),
254
+ ));
255
+ }
256
+ }
257
+
258
+ // Merge milestones — worktree may have updated status/planning fields.
259
+ // Never downgrade status: complete > active > pre-planning (#4372).
260
+ // A stale worktree may carry an older 'active' status for a milestone
261
+ // that the main DB has already marked 'complete'; preserve the higher status.
262
+ if (hasWtMilestones) {
263
+ merged.milestones = countChanges(adapter.prepare(`
264
+ INSERT OR REPLACE INTO milestones (
265
+ id, title, status, depends_on, created_at, completed_at,
266
+ vision, success_criteria, key_risks, proof_strategy,
267
+ verification_contract, verification_integration, verification_operational, verification_uat,
268
+ definition_of_done, requirement_coverage, boundary_map_markdown, sequence
269
+ )
270
+ SELECT w.id, w.title,
271
+ CASE
272
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
273
+ THEN m.status ELSE w.status
274
+ END,
275
+ w.depends_on,
276
+ CASE
277
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
278
+ THEN m.created_at ELSE w.created_at
279
+ END,
280
+ CASE
281
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
282
+ THEN m.completed_at ELSE w.completed_at
283
+ END,
284
+ w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
285
+ w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
286
+ w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown,
287
+ ${hasMilestoneSequence ? "COALESCE(w.sequence, 0)" : "COALESCE(m.sequence, 0)"}
288
+ FROM wt.milestones w
289
+ LEFT JOIN milestones m ON m.id = w.id
290
+ `).run());
291
+ }
292
+
293
+ // Merge slices — preserve worktree progress but never downgrade completed status (#2558).
294
+ // ADR-011 Phase 1: carry is_sketch + sketch_scope so reconcile doesn't
295
+ // silently clear sketch metadata. When the worktree predates v16,
296
+ // fall back to the main DB's existing value rather than a literal 0/''.
297
+ if (hasWtSlices) {
298
+ merged.slices = countChanges(adapter.prepare(`
299
+ INSERT OR REPLACE INTO slices (
300
+ milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
301
+ full_summary_md, full_uat_md, goal, success_criteria, proof_level,
302
+ integration_closure, observability_impact, target_repositories, sequence, replan_triggered_at,
303
+ is_sketch, sketch_scope
304
+ )
305
+ SELECT w.milestone_id, w.id, w.title,
306
+ CASE
307
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
308
+ THEN m.status ELSE w.status
309
+ END,
310
+ w.risk, w.depends, w.demo, w.created_at,
311
+ CASE
312
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
313
+ THEN m.completed_at ELSE w.completed_at
314
+ END,
315
+ w.full_summary_md, w.full_uat_md, w.goal, w.success_criteria, w.proof_level,
316
+ w.integration_closure, w.observability_impact,
317
+ ${sliceTargetRepositoriesSql},
318
+ w.sequence, w.replan_triggered_at,
319
+ ${hasIsSketch ? "w.is_sketch" : "COALESCE(m.is_sketch, 0)"},
320
+ ${hasSketchScope ? "w.sketch_scope" : "COALESCE(m.sketch_scope, '')"}
321
+ FROM wt.slices w
322
+ LEFT JOIN slices m ON m.milestone_id = w.milestone_id AND m.id = w.id
323
+ `).run());
324
+ }
325
+
326
+ // Merge tasks — preserve execution results, never downgrade completed status (#2558).
327
+ // ADR-011 P2: carry blocker_source + escalation_* columns so worktree reconcile
328
+ // doesn't silently clear escalation state back to defaults.
329
+ if (hasWtTasks) {
330
+ merged.tasks = countChanges(adapter.prepare(`
331
+ INSERT OR REPLACE INTO tasks (
332
+ milestone_id, slice_id, id, title, status, one_liner, narrative,
333
+ verification_result, duration, completed_at, blocker_discovered,
334
+ deviations, known_issues, key_files, key_decisions, full_summary_md,
335
+ description, estimate, files, verify, inputs, expected_output,
336
+ observability_impact, full_plan_md, target_repositories, sequence,
337
+ blocker_source, escalation_pending, escalation_awaiting_review,
338
+ escalation_artifact_path, escalation_override_applied_at
339
+ )
340
+ SELECT w.milestone_id, w.slice_id, w.id, w.title,
341
+ CASE
342
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
343
+ THEN m.status ELSE w.status
344
+ END,
345
+ w.one_liner, w.narrative,
346
+ w.verification_result, w.duration,
347
+ CASE
348
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
349
+ THEN m.completed_at ELSE w.completed_at
350
+ END,
351
+ w.blocker_discovered,
352
+ w.deviations, w.known_issues, w.key_files, w.key_decisions, w.full_summary_md,
353
+ w.description, w.estimate, w.files, w.verify, w.inputs, w.expected_output,
354
+ w.observability_impact, w.full_plan_md,
355
+ ${taskTargetRepositoriesSql},
356
+ w.sequence,
357
+ ${hasBlockerSource ? "w.blocker_source" : "COALESCE(m.blocker_source, '')"},
358
+ ${hasEscalationPending ? "w.escalation_pending" : "COALESCE(m.escalation_pending, 0)"},
359
+ ${hasEscalationAwaiting ? "w.escalation_awaiting_review" : "COALESCE(m.escalation_awaiting_review, 0)"},
360
+ ${hasEscalationArtifact ? "w.escalation_artifact_path" : "m.escalation_artifact_path"},
361
+ ${hasEscalationOverride ? "w.escalation_override_applied_at" : "m.escalation_override_applied_at"}
362
+ FROM wt.tasks w
363
+ LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
364
+ `).run());
365
+ }
366
+
367
+ // Merge memories — keep worktree-learned insights.
368
+ // V18 (scope, tags), V21 (structured_fields), V28 (last_hit_at): for each
369
+ // column the wt may not yet have (older worktree DB), fall back to the
370
+ // main DB's existing value via LEFT JOIN so reconcile never silently
371
+ // resets these fields to defaults on rows that already had them.
372
+ if (hasWtMemories) {
373
+ merged.memories = countChanges(adapter.prepare(`
374
+ INSERT OR REPLACE INTO memories (
375
+ seq, id, category, content, confidence, source_unit_type, source_unit_id,
376
+ created_at, updated_at, superseded_by, hit_count,
377
+ scope, tags, structured_fields, last_hit_at
378
+ )
379
+ SELECT w.seq, w.id, w.category, w.content, w.confidence, w.source_unit_type, w.source_unit_id,
380
+ w.created_at, w.updated_at, w.superseded_by, w.hit_count,
381
+ ${hasMemoryScope ? "w.scope" : "COALESCE(m.scope, 'project')"},
382
+ ${hasMemoryTags ? "w.tags" : "COALESCE(m.tags, '[]')"},
383
+ ${hasMemoryStructuredFields ? "w.structured_fields" : "m.structured_fields"},
384
+ ${hasMemoryLastHitAt ? "w.last_hit_at" : "m.last_hit_at"}
385
+ FROM wt.memories w
386
+ LEFT JOIN memories m ON m.id = w.id
387
+ `).run());
388
+ }
389
+
390
+ if (hasWtReplanHistory) {
391
+ merged.replan_history = countChanges(adapter.prepare(`
392
+ INSERT INTO replan_history (
393
+ milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at
394
+ )
395
+ SELECT w.milestone_id, w.slice_id, w.task_id, w.summary, w.previous_artifact_path, w.replacement_artifact_path, w.created_at
396
+ FROM wt.replan_history w
397
+ WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
398
+ AND NOT EXISTS (
399
+ SELECT 1 FROM replan_history m
400
+ WHERE m.milestone_id = w.milestone_id
401
+ AND m.slice_id IS w.slice_id
402
+ AND m.task_id IS w.task_id
403
+ AND m.summary = w.summary
404
+ AND m.previous_artifact_path IS w.previous_artifact_path
405
+ AND m.replacement_artifact_path IS w.replacement_artifact_path
406
+ )
407
+ `).run());
408
+ }
409
+
410
+ if (hasWtAssessments) {
411
+ merged.assessments = countChanges(adapter.prepare(`
412
+ INSERT OR REPLACE INTO assessments (
413
+ path, milestone_id, slice_id, task_id, status, scope, full_content, created_at
414
+ )
415
+ SELECT w.path, w.milestone_id, w.slice_id, w.task_id, w.status, w.scope, w.full_content, w.created_at
416
+ FROM wt.assessments w
417
+ WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
418
+ `).run());
419
+ }
420
+
421
+ if (hasWtQualityGates) {
422
+ merged.quality_gates = countChanges(adapter.prepare(`
423
+ INSERT OR REPLACE INTO quality_gates (
424
+ milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at
425
+ )
426
+ SELECT w.milestone_id, w.slice_id, w.gate_id, w.scope, COALESCE(w.task_id, ''), w.status, w.verdict, w.rationale, w.findings, w.evaluated_at
427
+ FROM wt.quality_gates w
428
+ WHERE EXISTS (SELECT 1 FROM slices s WHERE s.milestone_id = w.milestone_id AND s.id = w.slice_id)
429
+ `).run());
430
+ }
431
+
432
+ if (hasWtSliceDependencies) {
433
+ merged.slice_dependencies = countChanges(adapter.prepare(`
434
+ INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id)
435
+ SELECT w.milestone_id, w.slice_id, w.depends_on_slice_id
436
+ FROM wt.slice_dependencies w
437
+ WHERE EXISTS (SELECT 1 FROM slices s WHERE s.milestone_id = w.milestone_id AND s.id = w.slice_id)
438
+ AND EXISTS (SELECT 1 FROM slices d WHERE d.milestone_id = w.milestone_id AND d.id = w.depends_on_slice_id)
439
+ `).run());
440
+ }
441
+
442
+ // Merge verification evidence — append-only, use INSERT OR IGNORE to avoid duplicates
443
+ if (hasWtVerificationEvidence) {
444
+ merged.verification_evidence = countChanges(adapter.prepare(`
445
+ INSERT OR IGNORE INTO verification_evidence (
446
+ task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
447
+ )
448
+ SELECT task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
449
+ FROM wt.verification_evidence
450
+ `).run());
451
+ }
452
+
453
+ if (hasWtGateRuns) {
454
+ merged.gate_runs = countChanges(adapter.prepare(`
455
+ INSERT INTO gate_runs (
456
+ trace_id, turn_id, gate_id, gate_type, unit_type, unit_id, milestone_id, slice_id, task_id,
457
+ outcome, failure_class, rationale, findings, attempt, max_attempts, retryable, evaluated_at
458
+ )
459
+ SELECT w.trace_id, w.turn_id, w.gate_id, w.gate_type, w.unit_type, w.unit_id, w.milestone_id, w.slice_id, w.task_id,
460
+ w.outcome, w.failure_class, w.rationale, w.findings, w.attempt, w.max_attempts, w.retryable, w.evaluated_at
461
+ FROM wt.gate_runs w
462
+ WHERE NOT EXISTS (
463
+ SELECT 1 FROM gate_runs m
464
+ WHERE m.trace_id = w.trace_id
465
+ AND m.turn_id = w.turn_id
466
+ AND m.gate_id = w.gate_id
467
+ AND m.attempt = w.attempt
468
+ AND m.evaluated_at = w.evaluated_at
469
+ )
470
+ `).run());
471
+ }
472
+
473
+ if (hasWtMilestoneCommitAttributions) {
474
+ merged.milestone_commit_attributions = countChanges(adapter.prepare(`
475
+ INSERT OR REPLACE INTO milestone_commit_attributions (
476
+ commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
477
+ )
478
+ SELECT w.commit_sha, w.milestone_id, w.slice_id, w.task_id, w.source, w.confidence, w.files_json, w.created_at
479
+ FROM wt.milestone_commit_attributions w
480
+ WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
481
+ `).run());
482
+ }
483
+
484
+ adapter.exec("COMMIT");
485
+ } catch (txErr) {
486
+ try { adapter.exec("ROLLBACK"); } catch (e) { logWarning("db", `rollback failed: ${(e as Error).message}`); }
487
+ throw txErr;
488
+ }
489
+ return { ...merged, conflicts };
490
+ } finally {
491
+ try { adapter.exec("DETACH DATABASE wt"); } catch (e) { logWarning("db", `detach worktree DB failed: ${(e as Error).message}`); }
492
+ }
493
+ } catch (err) {
494
+ logError("db", "worktree DB reconciliation failed", { error: (err as Error).message });
495
+ return { ...zero, conflicts };
496
+ }
497
+ }
498
+
499
+ // ─── Replan & Assessment Helpers ──────────────────────────────────────────
500
+
@@ -0,0 +1,88 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Status Transition Core (ADR-030) — the single row-level
3
+ // chokepoint every generic status write funnels through. Owns the milestone
4
+ // closed→open guard and is the one place future row-level status policy lands.
5
+ // The update*Status faces in gsd-db.ts delegate here.
6
+ //
7
+ // Behavior this pass is intentionally identical to the prior per-face writes.
8
+ // Two ADR-030 responsibilities are deferred for safety and documented inline:
9
+ // - Write-normalization via toStatus(): workflow-reconcile replays journal
10
+ // events that write raw "done"/"in-progress" and tests assert those exact
11
+ // stored values, so converging on write is a separate, behavior-sensitive
12
+ // change (migrate replay/import to canonical first).
13
+ // - Generalizing the closed→open guard to task/slice: four legitimate reopen
14
+ // callers (undo, tools/reopen-task, auto-post-unit, tools/plan-slice) move
15
+ // entities to open statuses through the generic faces. Generalizing safely
16
+ // needs sanctioned reopenTaskStatus/reopenSliceStatus faces first, mirroring
17
+ // the existing milestone updateMilestoneStatus/reopenMilestoneStatus split.
18
+ import { getDbOrNull } from "../engine.js";
19
+ import { GSDError, GSD_STALE_STATE } from "../../errors.js";
20
+ import { isClosedStatus } from "../../status-guards.js";
21
+
22
+ /** A single row-level status write, discriminated by entity (the faces' arity). */
23
+ export type StatusTransition =
24
+ | { entity: "task"; milestoneId: string; sliceId: string; taskId: string; status: string; completedAt?: string | null }
25
+ | { entity: "slice"; milestoneId: string; sliceId: string; status: string; completedAt?: string | null }
26
+ | { entity: "milestone"; milestoneId: string; status: string; completedAt?: string | null };
27
+
28
+ function requireDb() {
29
+ const db = getDbOrNull();
30
+ if (!db) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
31
+ return db;
32
+ }
33
+
34
+ /**
35
+ * Apply a row-level status transition. The single chokepoint for generic status
36
+ * writes — the update*Status faces delegate here so the guard and (future)
37
+ * normalization/journal/cache policy live in one place rather than per face.
38
+ *
39
+ * Milestone closed→open guard: generic updates may close, park/unpark, or
40
+ * advance a milestone, but may not reopen a closed one; callers must use
41
+ * reopenMilestoneStatus() (gsd_milestone_reopen). Tasks and slices are not yet
42
+ * guarded — see the file header.
43
+ */
44
+ export function applyStatusTransition(t: StatusTransition): void {
45
+ const db = requireDb();
46
+ const completedAt = t.completedAt ?? null;
47
+
48
+ switch (t.entity) {
49
+ case "task":
50
+ db.prepare(
51
+ `UPDATE tasks SET status = :status, completed_at = :completed_at
52
+ WHERE milestone_id = :milestone_id AND slice_id = :slice_id AND id = :id`,
53
+ ).run({
54
+ ":status": t.status,
55
+ ":completed_at": completedAt,
56
+ ":milestone_id": t.milestoneId,
57
+ ":slice_id": t.sliceId,
58
+ ":id": t.taskId,
59
+ });
60
+ return;
61
+
62
+ case "slice":
63
+ db.prepare(
64
+ `UPDATE slices SET status = :status, completed_at = :completed_at
65
+ WHERE milestone_id = :milestone_id AND id = :id`,
66
+ ).run({
67
+ ":status": t.status,
68
+ ":completed_at": completedAt,
69
+ ":milestone_id": t.milestoneId,
70
+ ":id": t.sliceId,
71
+ });
72
+ return;
73
+
74
+ case "milestone": {
75
+ const row = db.prepare("SELECT status FROM milestones WHERE id = :id").get({ ":id": t.milestoneId });
76
+ const currentStatus = typeof row?.["status"] === "string" ? (row["status"] as string) : null;
77
+ if (currentStatus && isClosedStatus(currentStatus) && !isClosedStatus(t.status)) {
78
+ throw new Error(
79
+ `Cannot update closed milestone ${t.milestoneId} from ${currentStatus} to ${t.status}; use gsd_milestone_reopen for an explicit reopen.`,
80
+ );
81
+ }
82
+ db.prepare(
83
+ `UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`,
84
+ ).run({ ":status": t.status, ":completed_at": completedAt, ":id": t.milestoneId });
85
+ return;
86
+ }
87
+ }
88
+ }
@@ -15,6 +15,7 @@ import { join } from "node:path";
15
15
 
16
16
  import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
17
17
  import { detectPythonExecutable } from "./python-resolver.js";
18
+ import { projectRootFromWorktreePath } from "./worktree-root.js";
18
19
 
19
20
  // ── Types ──────────────────────────────────────────────────────────────────
20
21
 
@@ -38,27 +39,18 @@ const CMD_TIMEOUT = 5_000;
38
39
 
39
40
  // ── Helpers ────────────────────────────────────────────────────────────────
40
41
 
41
- /** Worktree sentinel — path segment that marks an auto-worktree directory. */
42
- const WORKTREE_PATH_SEGMENT = `${join(".gsd", "worktrees")}/`;
43
-
44
42
  /**
45
- * Resolve the project root when running inside a `.gsd/worktrees/<name>/`
46
- * auto-worktree. Returns `null` if not in a worktree.
43
+ * Resolve the project root when running inside a GSD auto-worktree.
44
+ * Returns `null` if not in a worktree.
47
45
  *
48
46
  * Detection order:
49
47
  * 1. `GSD_WORKTREE` env var (set by the worktree launcher)
50
- * 2. `.gsd/worktrees/` segment in basePath
48
+ * 2. worktree segment in basePath (via worktree-root's layout matching)
51
49
  */
52
50
  function resolveWorktreeProjectRoot(basePath: string): string | null {
53
51
  const envRoot = process.env.GSD_WORKTREE;
54
52
  if (envRoot) return envRoot;
55
-
56
- const normalised = basePath.replace(/\\/g, "/");
57
- const idx = normalised.indexOf(WORKTREE_PATH_SEGMENT.replace(/\\/g, "/"));
58
- if (idx === -1) return null;
59
-
60
- // Everything before `.gsd/worktrees/` is the project root
61
- return basePath.slice(0, idx);
53
+ return projectRootFromWorktreePath(basePath);
62
54
  }
63
55
 
64
56
  function tryExec(cmd: string, cwd: string): string | null {
@@ -1,4 +1,9 @@
1
- import type { DoctorIssue, DoctorIssueCode, DoctorReport, DoctorSummary } from "./doctor-types.js";
1
+ import type { DoctorIssue, DoctorIssueCode, DoctorReport, DoctorSummary, DoctorSeverity } from "./doctor-types.js";
2
+ import { doctorFixHint } from "./guidance.js";
3
+
4
+ function severityTag(severity: DoctorSeverity): string {
5
+ return severity === "error" ? "ERROR" : severity === "warning" ? "WARN" : "INFO";
6
+ }
2
7
 
3
8
  function matchesScope(unitId: string, scope?: string): boolean {
4
9
  if (!scope) return true;
@@ -53,8 +58,9 @@ export function formatDoctorReport(
53
58
  if (scopedIssues.length > 0) {
54
59
  lines.push("Priority issues:");
55
60
  for (const issue of scopedIssues.slice(0, maxIssues)) {
56
- const prefix = issue.severity === "error" ? "ERROR" : issue.severity === "warning" ? "WARN" : "INFO";
57
- lines.push(`- [${prefix}] ${issue.unitId}: ${issue.message}${issue.file ? ` (${issue.file})` : ""}`);
61
+ lines.push(`- [${severityTag(issue.severity)}] ${issue.unitId}: ${issue.message}${issue.file ? ` (${issue.file})` : ""}`);
62
+ const hint = doctorFixHint(issue.code);
63
+ if (hint && issue.severity !== "info") lines.push(` Fix: ${hint}`);
58
64
  }
59
65
  if (scopedIssues.length > maxIssues) {
60
66
  lines.push(`- ...and ${scopedIssues.length - maxIssues} more in scope`);
@@ -72,10 +78,9 @@ export function formatDoctorReport(
72
78
 
73
79
  export function formatDoctorIssuesForPrompt(issues: DoctorIssue[]): string {
74
80
  if (issues.length === 0) return "- No remaining issues in scope.";
75
- return issues.map(issue => {
76
- const prefix = issue.severity === "error" ? "ERROR" : issue.severity === "warning" ? "WARN" : "INFO";
77
- return `- [${prefix}] ${issue.unitId} | ${issue.code} | ${issue.message}${issue.file ? ` | file: ${issue.file}` : ""} | fixable: ${issue.fixable ? "yes" : "no"}`;
78
- }).join("\n");
81
+ return issues.map(issue =>
82
+ `- [${severityTag(issue.severity)}] ${issue.unitId} | ${issue.code} | ${issue.message}${issue.file ? ` | file: ${issue.file}` : ""} | fixable: ${issue.fixable ? "yes" : "no"}`
83
+ ).join("\n");
79
84
  }
80
85
 
81
86
  /**