@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
@@ -161,13 +161,13 @@ test("enterMilestone returns ok:true mode:worktree on successful create", (t) =>
161
161
  if (result.ok) {
162
162
  assert.equal(result.mode, "worktree");
163
163
  assert.ok(
164
- result.path.endsWith("/.gsd/worktrees/M001"),
165
- `expected path to end with /.gsd/worktrees/M001, got ${result.path}`,
164
+ result.path.endsWith("/.gsd-worktrees/M001"),
165
+ `expected path to end with /.gsd-worktrees/M001, got ${result.path}`,
166
166
  );
167
167
  }
168
168
  assert.ok(
169
- s.basePath.endsWith("/.gsd/worktrees/M001"),
170
- `expected s.basePath to end with /.gsd/worktrees/M001, got ${s.basePath}`,
169
+ s.basePath.endsWith("/.gsd-worktrees/M001"),
170
+ `expected s.basePath to end with /.gsd-worktrees/M001, got ${s.basePath}`,
171
171
  );
172
172
  // After C3 (#5626) `invalidateAllCaches` is inlined; assertion against
173
173
  // `deps.calls` for cache invalidation is no longer possible.
@@ -240,6 +240,43 @@ test("adoptStrandedMilestone forces branch recovery even when normal preferences
240
240
  assert.equal(currentBranch, "milestone/M001");
241
241
  });
242
242
 
243
+ test("enterMilestone honors stranded branch recovery instead of recreating the worktree", (t) => {
244
+ // Regression: after adoptStrandedMilestone checks out milestone/M001 in
245
+ // the project root, a plain enterMilestone under isolation:worktree used
246
+ // to attempt `git worktree add`, which git refuses ("branch is already in
247
+ // use by another worktree" — the root checkout IS the conflicting
248
+ // worktree), tripping a creation-failed warning and degrading isolation.
249
+ // The recovery override must keep re-entries in branch mode.
250
+ const previousCwd = process.cwd();
251
+ const base = makeGitRepoBase({ isolation: "worktree" });
252
+ t.after(() => cleanupRepoBase(base, previousCwd));
253
+
254
+ const s = makeSession({ basePath: base, originalBasePath: base });
255
+ const deps = makeDeps();
256
+ const ctx = makeCtx();
257
+ const lifecycle = new WorktreeLifecycle(s, deps);
258
+
259
+ const adopted = lifecycle.adoptStrandedMilestone("M001", base, ctx, {
260
+ mode: "branch",
261
+ });
262
+ assert.equal(adopted.ok, true, `expected adopt ok:true, got: ${JSON.stringify(adopted)}`);
263
+
264
+ const result = lifecycle.enterMilestone("M001", ctx);
265
+
266
+ assert.equal(result.ok, true, `expected ok:true, got: ${JSON.stringify(result)}`);
267
+ if (result.ok) {
268
+ assert.equal(result.mode, "branch");
269
+ assert.equal(result.path, base);
270
+ }
271
+ assert.equal(s.basePath, base);
272
+ assert.equal(s.isolationDegraded, false, "intentional branch adoption must not degrade isolation");
273
+ assert.equal(
274
+ ctx.messages.some((m) => m.msg.includes("creation for M001 failed")),
275
+ false,
276
+ "re-entry must not attempt (and fail) canonical worktree creation",
277
+ );
278
+ });
279
+
243
280
  test("enterMilestone returns ok:false reason:isolation-degraded when session degraded", () => {
244
281
  const s = makeSession({ isolationDegraded: true });
245
282
  const deps = makeDeps({ getIsolationMode: () => "branch" });
@@ -142,6 +142,27 @@ describe("createWorktree", () => {
142
142
  run("git rev-parse --git-dir", info.path);
143
143
  assert.ok(!existsSync(join(info.path, "orphan.txt")), "stale file removed by recovery");
144
144
  });
145
+
146
+ test("removes stale canonical directory when legacy orphan is cleaned and canonical target is stale", () => {
147
+ // Scenario: a stale .gsd-worktrees/M020 directory (no .git — aborted prior
148
+ // creation) coexists with an orphaned .gsd/worktrees/M020 dir (.git file not
149
+ // registered with git). worktreePathFor returns the legacy path (canonical has
150
+ // no .git marker), so only the legacy path was previously cleaned — the stale
151
+ // canonical blocked git worktree add. The fix ensures createWorktree also
152
+ // removes the stale canonical before calling git worktree add.
153
+ const canonicalDir = join(base, ".gsd-worktrees", "M020");
154
+ const legacyDir = join(base, ".gsd", "worktrees", "M020");
155
+
156
+ mkdirSync(canonicalDir, { recursive: true }); // stale canonical: exists, no .git
157
+ mkdirSync(legacyDir, { recursive: true });
158
+ writeFileSync(join(legacyDir, ".git"), "gitdir: ../../../../.git/worktrees/M020\n", "utf-8");
159
+
160
+ const info = createWorktree(base, "M020");
161
+ assert.strictEqual(info.name, "M020");
162
+ assert.ok(existsSync(info.path), "worktree path should exist after creation");
163
+ assert.ok(existsSync(join(info.path, ".git")), "new worktree has .git marker");
164
+ run("git rev-parse --git-dir", info.path);
165
+ });
145
166
  });
146
167
 
147
168
  describe("createWorktree — duplicate rejection", () => {
@@ -180,7 +201,7 @@ describe("createWorktree — branch cleanup on add failure", () => {
180
201
 
181
202
  // Make the worktrees parent directory non-writable so `git worktree add`
182
203
  // fails after the branch has already been force-reset.
183
- const parentDir = join(base, ".gsd", "worktrees");
204
+ const parentDir = join(base, ".gsd-worktrees");
184
205
  mkdirSync(parentDir, { recursive: true });
185
206
  run(`chmod 555 "${parentDir}"`, base);
186
207
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Tests for worktreePathFor — the forward seam (project + name → path).
3
+ *
4
+ * Key invariant: a stale canonical directory (no .git marker) must NOT
5
+ * shadow a live legacy worktree (.gsd/worktrees/<name> with .git).
6
+ */
7
+
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { mkdirSync, writeFileSync, rmSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+ import { randomUUID } from "node:crypto";
14
+
15
+ import { worktreePathFor, canonicalWorktreesDir, legacyWorktreesDir } from "../worktree-placement.ts";
16
+
17
+ function makeTmpRoot(): string {
18
+ const root = join(tmpdir(), `gsd-placement-test-${randomUUID()}`);
19
+ mkdirSync(root, { recursive: true });
20
+ return root;
21
+ }
22
+
23
+ function cleanup(root: string): void {
24
+ try { rmSync(root, { recursive: true, force: true }); } catch { /* */ }
25
+ }
26
+
27
+ function makeCanonicalDir(root: string, name: string): string {
28
+ const p = join(canonicalWorktreesDir(root), name);
29
+ mkdirSync(p, { recursive: true });
30
+ return p;
31
+ }
32
+
33
+ function makeLiveCanonical(root: string, name: string): string {
34
+ const p = makeCanonicalDir(root, name);
35
+ writeFileSync(join(p, ".git"), `gitdir: ${join(root, ".git", "worktrees", name)}\n`);
36
+ return p;
37
+ }
38
+
39
+ function makeLiveLegacy(root: string, name: string): string {
40
+ const p = join(legacyWorktreesDir(root), name);
41
+ mkdirSync(p, { recursive: true });
42
+ writeFileSync(join(p, ".git"), `gitdir: ${join(root, ".git", "worktrees", name)}\n`);
43
+ return p;
44
+ }
45
+
46
+ test("returns canonical path when canonical has .git marker", () => {
47
+ const root = makeTmpRoot();
48
+ try {
49
+ const canonical = makeLiveCanonical(root, "M001");
50
+ assert.equal(worktreePathFor(root, "M001"), canonical);
51
+ } finally {
52
+ cleanup(root);
53
+ }
54
+ });
55
+
56
+ test("returns legacy path when only legacy exists with .git marker", () => {
57
+ const root = makeTmpRoot();
58
+ try {
59
+ const legacy = makeLiveLegacy(root, "M001");
60
+ assert.equal(worktreePathFor(root, "M001"), legacy);
61
+ } finally {
62
+ cleanup(root);
63
+ }
64
+ });
65
+
66
+ test("returns legacy path when canonical dir exists but has no .git (stale canonical)", () => {
67
+ const root = makeTmpRoot();
68
+ try {
69
+ makeCanonicalDir(root, "M001"); // stale: dir exists, no .git
70
+ const legacy = makeLiveLegacy(root, "M001");
71
+ assert.equal(
72
+ worktreePathFor(root, "M001"),
73
+ legacy,
74
+ "stale canonical must not shadow live legacy worktree",
75
+ );
76
+ } finally {
77
+ cleanup(root);
78
+ }
79
+ });
80
+
81
+ test("returns canonical path for new-worktree creation when neither path exists", () => {
82
+ const root = makeTmpRoot();
83
+ try {
84
+ const expected = join(canonicalWorktreesDir(root), "M001");
85
+ assert.equal(worktreePathFor(root, "M001"), expected);
86
+ } finally {
87
+ cleanup(root);
88
+ }
89
+ });
90
+
91
+ test("prefers live canonical over live legacy when both exist", () => {
92
+ const root = makeTmpRoot();
93
+ try {
94
+ const canonical = makeLiveCanonical(root, "M001");
95
+ makeLiveLegacy(root, "M001");
96
+ assert.equal(worktreePathFor(root, "M001"), canonical);
97
+ } finally {
98
+ cleanup(root);
99
+ }
100
+ });
101
+
102
+ test("returns legacy when canonical is stale and legacy has no .git (both stale)", () => {
103
+ const root = makeTmpRoot();
104
+ try {
105
+ makeCanonicalDir(root, "M001"); // stale canonical
106
+ const legacy = join(legacyWorktreesDir(root), "M001");
107
+ mkdirSync(legacy, { recursive: true }); // stale legacy (no .git)
108
+ // Falls through to legacy existsSync since canonical has no .git
109
+ assert.equal(worktreePathFor(root, "M001"), legacy);
110
+ } finally {
111
+ cleanup(root);
112
+ }
113
+ });
@@ -65,7 +65,7 @@ describe("reenterActiveWorktreeIfNeeded", () => {
65
65
  const entered = await reenterActiveWorktreeIfNeeded(dir);
66
66
  assert.ok(entered, "re-entry returned a worktree path");
67
67
  assert.strictEqual(realpathSync(process.cwd()), realpathSync(entered!), "cwd moved into the worktree");
68
- assert.strictEqual(entered, join(dir, ".gsd", "worktrees", "M001"));
68
+ assert.strictEqual(entered, join(dir, ".gsd-worktrees", "M001"));
69
69
  });
70
70
 
71
71
  test("no-op when already inside a worktree", async (t) => {
@@ -126,7 +126,9 @@ describe("Worktree Safety module", () => {
126
126
  assert.equal(result.ok, false);
127
127
  assert.equal(result.kind, "invalid-root");
128
128
  assert.equal(result.details?.unitRoot, outsideRoot);
129
- assert.equal(result.details?.expectedRoot, unitRoot);
129
+ // The reported expected root is the canonical container; the legacy
130
+ // .gsd/worktrees/ location is also accepted but not surfaced here.
131
+ assert.equal(result.details?.expectedRoot, join(projectRoot, ".gsd-worktrees", "M001"));
130
132
  });
131
133
 
132
134
  test("accepts project root for source-writing Unit when isolation mode is none", () => {
@@ -8,6 +8,11 @@
8
8
  *
9
9
  * Fix: removeWorktree should query `git worktree list` to find the actual
10
10
  * registered path when the computed path doesn't match.
11
+ *
12
+ * New worktrees are created at the canonical `.gsd-worktrees/` sibling and
13
+ * never cross the symlink, so this scenario only applies to LEGACY worktrees
14
+ * (created under `.gsd/worktrees/` by older versions). The test creates the
15
+ * worktree at the legacy location directly to keep that coverage.
11
16
  */
12
17
  import { mkdtempSync, mkdirSync, rmSync, symlinkSync, unlinkSync, writeFileSync, existsSync, realpathSync } from "node:fs";
13
18
  import { join } from "node:path";
@@ -15,7 +20,6 @@ import { tmpdir } from "node:os";
15
20
  import { execSync } from "node:child_process";
16
21
 
17
22
  import {
18
- createWorktree,
19
23
  removeWorktree,
20
24
  listWorktrees,
21
25
  worktreePath,
@@ -64,13 +68,15 @@ test('worktree-symlink-removal removes the git-registered symlink target safely'
64
68
  run("git add .", base);
65
69
  run('git commit -m "init"', base);
66
70
 
67
- // Create a worktree git will resolve the symlink and register
68
- // the worktree at the external path
69
- const info = createWorktree(base, "M002", { branch: "milestone/M002" });
70
- assert.ok(info.exists, "worktree created");
71
+ // Create a LEGACY worktree through the symlinked .gsd path git resolves
72
+ // the symlink and registers the worktree at the external path. (Current
73
+ // createWorktree() uses the canonical .gsd-worktrees/ sibling instead.)
74
+ const legacyPath = join(base, ".gsd", "worktrees", "M002");
75
+ run(`git worktree add -b milestone/M002 "${legacyPath}"`, base);
76
+ assert.ok(existsSync(legacyPath), "worktree created");
71
77
 
72
78
  // Verify worktree was created at the resolved (external) path
73
- const realWtPath = realpathSync(info.path);
79
+ const realWtPath = realpathSync(legacyPath);
74
80
  assert.ok(
75
81
  realWtPath.startsWith(externalState),
76
82
  `worktree real path (${realWtPath}) is under external state dir`,
@@ -116,8 +116,8 @@ describe("worktree-teardown-safety", () => {
116
116
 
117
117
  const wtPathResult = worktreePath(tempDir, "anything");
118
118
  assertTrue(
119
- wtPathResult.startsWith(join(tempDir, ".gsd", "worktrees")),
120
- "worktreePath returns path under .gsd/worktrees/",
119
+ wtPathResult.startsWith(join(tempDir, ".gsd-worktrees")),
120
+ "worktreePath returns path under the canonical .gsd-worktrees/ container",
121
121
  );
122
122
  });
123
123
 
@@ -803,3 +803,45 @@ test('write-gate: resetWriteGateState persists through dangling .gsd symlink', (
803
803
  } catch { /* swallow */ }
804
804
  }
805
805
  });
806
+
807
+ // ─── Scenario 31: hydrate in-memory gate state from persisted snapshot (MCP subprocess) ──
808
+
809
+ test('write-gate: getPendingGate hydrates from disk when workflow MCP verified gate in child process', () => {
810
+ const base = join(tmpdir(), `gsd-write-gate-mcp-hydrate-${randomUUID()}`);
811
+ const stateFilePath = join(base, '.gsd', 'runtime', 'write-gate-state.json');
812
+ const originalEnv = process.env.GSD_PERSIST_WRITE_GATE_STATE;
813
+ const gateId = 'depth_verification_M005_confirm';
814
+
815
+ try {
816
+ process.env.GSD_PERSIST_WRITE_GATE_STATE = '1';
817
+ mkdirSync(join(base, '.gsd', 'runtime'), { recursive: true });
818
+ clearDiscussionFlowState(base);
819
+ setPendingGate(gateId, base);
820
+
821
+ writeFileSync(stateFilePath, JSON.stringify({
822
+ verifiedDepthMilestones: ['M005'],
823
+ verifiedApprovalGates: [gateId],
824
+ activeQueuePhase: false,
825
+ pendingGateId: null,
826
+ }, null, 2), 'utf-8');
827
+
828
+ assert.strictEqual(getPendingGate(base), null, 'stale in-memory pending must refresh from disk');
829
+ assert.strictEqual(isMilestoneDepthVerified('M005', base), true, 'verified milestone must hydrate from disk');
830
+ assert.deepEqual(loadWriteGateSnapshot(base), {
831
+ verifiedDepthMilestones: ['M005'],
832
+ verifiedApprovalGates: [gateId],
833
+ activeQueuePhase: false,
834
+ pendingGateId: null,
835
+ });
836
+ } finally {
837
+ if (originalEnv === undefined) {
838
+ delete process.env.GSD_PERSIST_WRITE_GATE_STATE;
839
+ } else {
840
+ process.env.GSD_PERSIST_WRITE_GATE_STATE = originalEnv;
841
+ }
842
+ clearDiscussionFlowState(base);
843
+ try {
844
+ rmSync(base, { recursive: true, force: true });
845
+ } catch { /* swallow */ }
846
+ }
847
+ });
@@ -0,0 +1,76 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Tool Contract module's runtime face — verify the live SDK tool surface covers a Unit's required workflow tools.
3
+
4
+ import { mcpToolMatchesBaseName } from "./mcp-tool-name.js";
5
+ import { getRequiredWorkflowToolsForUnit } from "./unit-tool-contracts.js";
6
+ import { isWorkflowToolSurfaceName } from "./workflow-tool-surface.js";
7
+
8
+ /**
9
+ * Stable phrase recognized as transient by auto-tool-tracking's
10
+ * isToolUnavailableError and error-classifier's transient buckets,
11
+ * which build their matchers from this constant.
12
+ */
13
+ export const TOOL_SURFACE_NOT_READY = "workflow tool surface not ready";
14
+
15
+ /** MCP server statuses that will not self-heal within the session. */
16
+ const TERMINAL_MCP_SERVER_STATUSES = new Set(["failed", "needs-auth", "disabled"]);
17
+
18
+ export interface LiveToolSurfaceObservation {
19
+ /** Tool names the session reported at init (MCP tools appear as mcp__<server>__<tool>). */
20
+ tools: readonly string[];
21
+ /** MCP server connection statuses the session reported at init. */
22
+ mcpServers: readonly { name: string; status: string }[];
23
+ }
24
+
25
+ /**
26
+ * Verify the live tool surface observed at SDK session init covers the Unit's
27
+ * required workflow tools. Complements the static pre-dispatch gate
28
+ * (getWorkflowTransportSupportError), which only proves the MCP launch config
29
+ * is discoverable — the workflow server connects asynchronously after session
30
+ * start, so the static gate cannot see whether the tools actually registered.
31
+ *
32
+ * Returns a transient, recovery-classifiable error (kind tool-unavailable →
33
+ * retry) when the workflow server failed or has not yet registered a required
34
+ * tool, so dispatch aborts before the first model turn instead of letting the
35
+ * Unit improvise around "No such tool available". Returns null when no
36
+ * workflow server is part of this session (native tool path), when the Unit
37
+ * requires no workflow tools, or when the surface is ready.
38
+ */
39
+ export function getToolSurfaceReadinessError(input: {
40
+ unitType: string | undefined;
41
+ workflowServerName: string | undefined;
42
+ observation: LiveToolSurfaceObservation;
43
+ }): string | null {
44
+ const { unitType, workflowServerName, observation } = input;
45
+ if (!unitType || !workflowServerName) return null;
46
+
47
+ const required = getRequiredWorkflowToolsForUnit(unitType).filter(isWorkflowToolSurfaceName);
48
+ if (required.length === 0) return null;
49
+
50
+ const server = observation.mcpServers.find((entry) => entry.name === workflowServerName);
51
+ if (!server) {
52
+ return `${TOOL_SURFACE_NOT_READY} for ${unitType}: MCP server "${workflowServerName}" is absent from the init surface (not yet connected): ${required.join(", ")}`;
53
+ }
54
+
55
+ // The SDK does not wait for MCP servers before init — a still-connecting
56
+ // server reports "pending" there routinely, then registers within seconds,
57
+ // usually well before the Unit's first workflow tool call. Aborting on
58
+ // "pending" would fail the common healthy session, so it passes through;
59
+ // a genuine miss after pass-through still surfaces in-session as
60
+ // "No such tool available" and classifies tool-unavailable → bounded retry.
61
+ // Only statuses that cannot self-heal abort here.
62
+ if (server.status !== "connected" && !TERMINAL_MCP_SERVER_STATUSES.has(server.status)) {
63
+ return null;
64
+ }
65
+
66
+ const missing = required.filter(
67
+ (tool) => !observation.tools.some((name) => name === tool || mcpToolMatchesBaseName(name, tool)),
68
+ );
69
+ if (missing.length === 0) return null;
70
+
71
+ const serverDetail =
72
+ server.status === "connected"
73
+ ? `MCP server "${workflowServerName}" is connected but has not registered`
74
+ : `MCP server "${workflowServerName}" status is "${server.status}" and it has not registered`;
75
+ return `${TOOL_SURFACE_NOT_READY} for ${unitType}: ${serverDetail}: ${missing.join(", ")}`;
76
+ }
@@ -15,20 +15,11 @@ import { existsSync, readFileSync } from "node:fs";
15
15
  import { join } from "node:path";
16
16
 
17
17
  import type { CompleteSliceParams } from "../types.js";
18
- import { isClosedStatus } from "../status-guards.js";
19
18
  import {
20
- transaction,
21
- insertMilestone,
22
- insertSlice,
23
- getSlice,
24
- getSliceTasks,
25
- getMilestone,
26
- updateSliceStatus,
19
+ completeSliceCascade,
27
20
  setSliceSummaryMd,
28
21
  saveGateResult,
29
22
  getPendingGatesForTurn,
30
- getMilestoneSlices,
31
- updateMilestoneStatus,
32
23
  } from "../gsd-db.js";
33
24
  import { getGatesForTurn } from "../gate-registry.js";
34
25
  import { gsdProjectionRoot, clearPathCache, resolveMilestoneFile } from "../paths.js";
@@ -371,60 +362,34 @@ export async function handleCompleteSlice(
371
362
  };
372
363
  }
373
364
 
374
- // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
365
+ // ── Atomic completion cascade (guards + writes in one transaction) ───────
375
366
  const completedAt = new Date().toISOString();
376
367
  let guardError: string | null = null;
377
368
  let existingSummaryMd = "";
378
369
  let duplicateComplete = false;
379
370
 
380
- transaction(() => {
381
- // State machine preconditions (inside txn for atomicity).
382
- // Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
383
- // Only block if they exist and are closed.
384
- const milestone = getMilestone(params.milestoneId);
385
- if (milestone && isClosedStatus(milestone.status)) {
386
- guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
387
- return;
388
- }
389
-
390
- const slice = getSlice(params.milestoneId, params.sliceId);
391
- existingSummaryMd = slice?.full_summary_md?.trim() ?? "";
392
- if (slice && isClosedStatus(slice.status)) {
393
- duplicateComplete = true;
394
- return;
395
- }
396
-
397
- // Verify all tasks are complete
398
- const tasks = getSliceTasks(params.milestoneId, params.sliceId);
399
- if (tasks.length === 0) {
400
- guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
401
- return;
402
- }
403
-
404
- const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
405
- if (incompleteTasks.length > 0) {
406
- const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
407
- guardError = `incomplete tasks: ${incompleteIds}`;
408
- return;
409
- }
410
-
411
- // All guards passed — perform writes. Preserve existing planning metadata:
412
- // completion should not overwrite title/risk/depends/demo/sequence.
413
- insertMilestone({ id: params.milestoneId, title: params.milestoneId });
414
- if (!slice) {
415
- insertSlice({ id: params.sliceId, milestoneId: params.milestoneId, title: params.sliceTitle || params.sliceId });
416
- }
417
- updateSliceStatus(params.milestoneId, params.sliceId, "complete", completedAt);
418
-
419
- const updatedSlices = getMilestoneSlices(params.milestoneId);
420
- if (
421
- milestone?.status === "planned" &&
422
- updatedSlices.length > 0 &&
423
- updatedSlices.every((s) => isClosedStatus(s.status))
424
- ) {
425
- updateMilestoneStatus(params.milestoneId, "active");
426
- }
371
+ const outcome = completeSliceCascade(params.milestoneId, params.sliceId, {
372
+ sliceTitle: params.sliceTitle,
373
+ completedAt,
427
374
  });
375
+ if (outcome.ok) {
376
+ existingSummaryMd = outcome.existingSummaryMd;
377
+ duplicateComplete = outcome.duplicate;
378
+ } else {
379
+ switch (outcome.reason) {
380
+ case "milestone-closed":
381
+ guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${outcome.status})`;
382
+ break;
383
+ case "no-tasks":
384
+ guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
385
+ break;
386
+ case "incomplete-tasks": {
387
+ const incompleteIds = outcome.incomplete.map((t) => `${t.id} (status: ${t.status})`).join(", ");
388
+ guardError = `incomplete tasks: ${incompleteIds}`;
389
+ break;
390
+ }
391
+ }
392
+ }
428
393
 
429
394
  if (duplicateComplete) {
430
395
  const staleSummaryPath = sliceSummaryPath(
@@ -11,6 +11,7 @@ import {
11
11
  import { realpathSync } from "node:fs";
12
12
  import path from "node:path";
13
13
  import { isContextModeEnabled, type ContextModeConfig } from "../preferences-types.js";
14
+ import { projectRootFromWorktreePath } from "../worktree-root.js";
14
15
  import { contextModeDisabledResult, type ToolExecutionResult } from "./context-mode-tool-result.js";
15
16
 
16
17
  export interface ExecToolParams {
@@ -201,13 +202,9 @@ function normalizeScanPath(value: string): string {
201
202
 
202
203
  function parseWorktreeBase(baseDir: string): { originalRoot: string; worktreeRoot: string } | null {
203
204
  const normalizedBase = normalizeScanPath(baseDir);
204
- const marker = "/.gsd/worktrees/";
205
- const markerIndex = normalizedBase.indexOf(marker);
206
- if (markerIndex <= 0) return null;
207
- return {
208
- originalRoot: normalizedBase.slice(0, markerIndex),
209
- worktreeRoot: normalizedBase,
210
- };
205
+ const originalRoot = projectRootFromWorktreePath(normalizedBase);
206
+ if (!originalRoot) return null;
207
+ return { originalRoot, worktreeRoot: normalizedBase };
211
208
  }
212
209
 
213
210
  function pathInside(parent: string, target: string): boolean {
@@ -297,7 +294,7 @@ function scriptReferencesOriginalRootFromWorktree(script: string, baseDir: strin
297
294
  const normalizedScript = script.replace(/\\/g, "/");
298
295
  return comparablePathVariants(parsed.originalRoot).some((originalRoot) => {
299
296
  const originalRootPattern = new RegExp(
300
- `${escapeRegExp(originalRoot)}(?=$|[\\s'"\\\`;)&|<>]|/(?!\\.gsd/worktrees(?:/|$)))`,
297
+ `${escapeRegExp(originalRoot)}(?=$|[\\s'"\\\`;)&|<>]|/(?!\\.gsd(?:-worktrees|/worktrees)(?:/|$)))`,
301
298
  );
302
299
  return originalRootPattern.test(normalizedScript);
303
300
  });
@@ -26,8 +26,9 @@ import { writeManifest } from "../workflow-manifest.js";
26
26
  import { appendEvent } from "../workflow-events.js";
27
27
  import { logWarning } from "../workflow-logger.js";
28
28
  import { validatePathOnlyPlanningFields, validatePlanningPathScope } from "../planning-path-scope.js";
29
- import { checkFilePathConsistency, checkTaskOrdering } from "../pre-execution-checks.js";
29
+ import { runTaskPathChecks } from "../pre-execution-checks.js";
30
30
  import type { TaskRow } from "../db-task-slice-rows.js";
31
+ import { resolveWorktreeProjectRoot } from "../worktree-root.js";
31
32
  import { buildTaskFileName, gsdProjectionRoot } from "../paths.js";
32
33
  import { loadEffectiveGSDPreferences } from "../preferences.js";
33
34
  import { createRepositoryRegistryFromPreferences, defaultRepositoryTargets, type RepositoryRegistry } from "../repository-registry.js";
@@ -268,11 +269,16 @@ function validateTaskPathsBeforePersist(
268
269
  const additionalRoots = allowedRoots
269
270
  .map((root) => resolve(root))
270
271
  .filter((root) => root !== baseRoot);
271
- const context = additionalRoots.length > 0 ? { additionalRoots } : undefined;
272
- const checks = [
273
- ...checkFilePathConsistency(taskRows, basePath, context),
274
- ...checkTaskOrdering(taskRows, basePath, context),
275
- ];
272
+ const resolvedCanonicalRoot = resolve(resolveWorktreeProjectRoot(basePath));
273
+ const canonicalProjectRoot = resolvedCanonicalRoot !== baseRoot ? resolvedCanonicalRoot : undefined;
274
+ const hasContext = additionalRoots.length > 0 || canonicalProjectRoot !== undefined;
275
+ const context = hasContext
276
+ ? {
277
+ ...(additionalRoots.length > 0 ? { additionalRoots } : {}),
278
+ ...(canonicalProjectRoot !== undefined ? { canonicalProjectRoot } : {}),
279
+ }
280
+ : undefined;
281
+ const checks = runTaskPathChecks(taskRows, basePath, context);
276
282
  const blocking = checks.filter((check) => !check.passed && check.blocking);
277
283
 
278
284
  if (blocking.length === 0) return null;
@@ -10,16 +10,11 @@
10
10
  */
11
11
 
12
12
  import {
13
- getMilestone,
14
13
  getMilestoneSlices,
15
14
  getSliceTasks,
16
- reopenMilestoneStatus,
17
- updateSliceStatus,
18
- updateTaskStatus,
19
- transaction,
15
+ reopenMilestoneCascade,
20
16
  } from "../gsd-db.js";
21
17
  import { invalidateStateCache } from "../state.js";
22
- import { isClosedStatus } from "../status-guards.js";
23
18
  import { renderAllProjections } from "../workflow-projections.js";
24
19
  import { writeManifest } from "../workflow-manifest.js";
25
20
  import { appendEvent } from "../workflow-events.js";
@@ -53,40 +48,18 @@ export async function handleReopenMilestone(
53
48
  return { error: "milestoneId is required and must be a non-empty string" };
54
49
  }
55
50
 
56
- // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
57
- let guardError: string | null = null;
58
- let slicesResetCount = 0;
59
- let tasksResetCount = 0;
60
-
61
- transaction(() => {
62
- const milestone = getMilestone(params.milestoneId);
63
- if (!milestone) {
64
- guardError = `milestone not found: ${params.milestoneId}`;
65
- return;
66
- }
67
- if (!isClosedStatus(milestone.status)) {
68
- guardError = `milestone ${params.milestoneId} is not closed (status: ${milestone.status}) — nothing to reopen`;
69
- return;
70
- }
71
-
72
- reopenMilestoneStatus(params.milestoneId);
73
-
74
- const slices = getMilestoneSlices(params.milestoneId);
75
- slicesResetCount = slices.length;
76
-
77
- for (const slice of slices) {
78
- updateSliceStatus(params.milestoneId, slice.id, "in_progress");
79
- const tasks = getSliceTasks(params.milestoneId, slice.id);
80
- tasksResetCount += tasks.length;
81
- for (const task of tasks) {
82
- updateTaskStatus(params.milestoneId, slice.id, task.id, "pending");
83
- }
51
+ // ── Atomic reopen cascade (guards + writes in one transaction) ───────────
52
+ const outcome = reopenMilestoneCascade(params.milestoneId);
53
+ if (!outcome.ok) {
54
+ switch (outcome.reason) {
55
+ case "milestone-not-found":
56
+ return { error: `milestone not found: ${params.milestoneId}` };
57
+ case "milestone-not-closed":
58
+ return { error: `milestone ${params.milestoneId} is not closed (status: ${outcome.status}) — nothing to reopen` };
84
59
  }
85
- });
86
-
87
- if (guardError) {
88
- return { error: guardError };
89
60
  }
61
+ const slicesResetCount = outcome.slicesReset;
62
+ const tasksResetCount = outcome.tasksReset;
90
63
 
91
64
  // ── Invalidate caches ────────────────────────────────────────────────────
92
65
  invalidateStateCache();