@opengsd/gsd-pi 1.2.0-dev.b1abb545 → 1.2.0-dev.d6c5343c

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 (551) 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 +14 -34
  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/GSD-WORKFLOW.md +5 -4
  11. package/dist/resources/extensions/async-jobs/async-bash-tool.js +30 -64
  12. package/dist/resources/extensions/async-jobs/await-tool.js +80 -12
  13. package/dist/resources/extensions/async-jobs/index.js +65 -0
  14. package/dist/resources/extensions/async-jobs/job-manager.js +12 -1
  15. package/dist/resources/extensions/bg-shell/bg-shell-command.js +6 -6
  16. package/dist/resources/extensions/bg-shell/bg-shell-tool.js +10 -7
  17. package/dist/resources/extensions/bg-shell/overlay.js +9 -6
  18. package/dist/resources/extensions/bg-shell/process-manager.js +54 -25
  19. package/dist/resources/extensions/bg-shell/readiness-detector.js +11 -0
  20. package/dist/resources/extensions/bg-shell/utilities.js +5 -2
  21. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +209 -88
  22. package/dist/resources/extensions/browser-tools/engine/selection.js +73 -5
  23. package/dist/resources/extensions/browser-tools/index.js +69 -12
  24. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  25. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +38 -6
  26. package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +17 -2
  27. package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
  28. package/dist/resources/extensions/gsd/auto/dispatch-history.js +105 -0
  29. package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
  30. package/dist/resources/extensions/gsd/auto/loop.js +4 -1
  31. package/dist/resources/extensions/gsd/auto/orchestrator.js +94 -48
  32. package/dist/resources/extensions/gsd/auto/phases.js +8 -3
  33. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -32
  34. package/dist/resources/extensions/gsd/auto-dispatch.js +40 -57
  35. package/dist/resources/extensions/gsd/auto-model-selection.js +25 -6
  36. package/dist/resources/extensions/gsd/auto-post-unit.js +31 -14
  37. package/dist/resources/extensions/gsd/auto-prompts.js +81 -19
  38. package/dist/resources/extensions/gsd/auto-start.js +24 -26
  39. package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
  40. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +12 -20
  41. package/dist/resources/extensions/gsd/auto-verification.js +9 -28
  42. package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
  43. package/dist/resources/extensions/gsd/auto-worktree.js +35 -352
  44. package/dist/resources/extensions/gsd/auto.js +8 -20
  45. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -2
  46. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +32 -12
  47. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +19 -0
  48. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +229 -36
  49. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +319 -71
  50. package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
  51. package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
  52. package/dist/resources/extensions/gsd/browser-evidence.js +8 -2
  53. package/dist/resources/extensions/gsd/captures.js +5 -15
  54. package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
  55. package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
  56. package/dist/resources/extensions/gsd/consent-question.js +337 -0
  57. package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
  58. package/dist/resources/extensions/gsd/constants.js +0 -2
  59. package/dist/resources/extensions/gsd/crash-recovery.js +4 -12
  60. package/dist/resources/extensions/gsd/db/engine.js +755 -0
  61. package/dist/resources/extensions/gsd/db/queries.js +398 -0
  62. package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
  63. package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
  64. package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
  65. package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
  66. package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
  67. package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
  68. package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
  69. package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
  70. package/dist/resources/extensions/gsd/doctor-format.js +9 -6
  71. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
  72. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
  73. package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
  74. package/dist/resources/extensions/gsd/error-classifier.js +9 -0
  75. package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
  76. package/dist/resources/extensions/gsd/files.js +33 -19
  77. package/dist/resources/extensions/gsd/git-service.js +1 -0
  78. package/dist/resources/extensions/gsd/gitignore.js +3 -0
  79. package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
  80. package/dist/resources/extensions/gsd/guidance.js +158 -0
  81. package/dist/resources/extensions/gsd/guided-flow.js +51 -5
  82. package/dist/resources/extensions/gsd/markdown-renderer.js +10 -0
  83. package/dist/resources/extensions/gsd/mcp-filter.js +2 -19
  84. package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
  85. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
  86. package/dist/resources/extensions/gsd/migrate/safety.js +20 -9
  87. package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
  88. package/dist/resources/extensions/gsd/milestone-closeout.js +13 -23
  89. package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
  90. package/dist/resources/extensions/gsd/model-router.js +3 -0
  91. package/dist/resources/extensions/gsd/notification-store.js +11 -4
  92. package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
  93. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
  94. package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
  95. package/dist/resources/extensions/gsd/paths.js +37 -24
  96. package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
  97. package/dist/resources/extensions/gsd/preferences-models.js +14 -48
  98. package/dist/resources/extensions/gsd/preferences.js +14 -0
  99. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  100. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  101. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  102. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  103. package/dist/resources/extensions/gsd/prompts/run-uat.md +6 -4
  104. package/dist/resources/extensions/gsd/prompts/system.md +5 -2
  105. package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
  106. package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
  107. package/dist/resources/extensions/gsd/publication.js +87 -0
  108. package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
  109. package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
  110. package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
  111. package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
  112. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
  113. package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
  114. package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
  115. package/dist/resources/extensions/gsd/state.js +6 -20
  116. package/dist/resources/extensions/gsd/status-guards.js +56 -8
  117. package/dist/resources/extensions/gsd/stop-notice.js +57 -0
  118. package/dist/resources/extensions/gsd/tool-presentation-plan.js +4 -4
  119. package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
  120. package/dist/resources/extensions/gsd/tools/complete-slice.js +44 -53
  121. package/dist/resources/extensions/gsd/tools/exec-tool.js +10 -8
  122. package/dist/resources/extensions/gsd/tools/plan-slice.js +12 -6
  123. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
  124. package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
  125. package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
  126. package/dist/resources/extensions/gsd/uat-policy.js +42 -16
  127. package/dist/resources/extensions/gsd/undo.js +8 -7
  128. package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
  129. package/dist/resources/extensions/gsd/unit-context-composer.js +74 -1
  130. package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
  131. package/dist/resources/extensions/gsd/unit-registry.js +337 -0
  132. package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
  133. package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
  134. package/dist/resources/extensions/gsd/web-app-uat.js +45 -8
  135. package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
  136. package/dist/resources/extensions/gsd/worktree-git-recovery.js +293 -0
  137. package/dist/resources/extensions/gsd/worktree-lifecycle.js +12 -3
  138. package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
  139. package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
  140. package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
  141. package/dist/resources/extensions/gsd/worktree-root.js +28 -6
  142. package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
  143. package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
  144. package/dist/resources/extensions/search-the-web/native-search.js +5 -3
  145. package/dist/resources/extensions/shared/browser-contract.js +59 -0
  146. package/dist/resources/extensions/shared/gsd-browser-cli.js +96 -5
  147. package/dist/resources/shared/package.json +3 -0
  148. package/dist/resources/skills/create-skill/references/executable-code.md +1 -1
  149. package/dist/resources/skills/create-skill/workflows/add-reference.md +8 -3
  150. package/dist/resources/skills/create-skill/workflows/add-script.md +4 -2
  151. package/dist/resources/skills/create-skill/workflows/add-template.md +3 -1
  152. package/dist/resources/skills/create-skill/workflows/add-workflow.md +8 -3
  153. package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
  154. package/dist/resources/skills/create-skill/workflows/verify-skill.md +9 -4
  155. package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
  156. package/dist/resources/skills/spike-wrap-up/SKILL.md +9 -9
  157. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  158. package/dist/web/standalone/.next/BUILD_ID +1 -1
  159. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  160. package/dist/web/standalone/.next/build-manifest.json +3 -3
  161. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  162. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  163. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  164. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  165. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  166. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  167. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  168. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  169. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  170. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  171. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  172. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  173. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  174. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  175. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  176. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  177. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  178. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  179. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  180. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  181. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  182. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  183. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  184. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  185. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  186. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  187. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  188. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  189. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  190. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  191. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  192. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  193. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  194. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  195. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  196. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  197. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  198. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  199. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  200. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  201. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  202. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  203. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  204. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  205. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  206. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  207. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  208. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  209. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  210. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  211. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  212. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  213. package/dist/web/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  214. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  215. package/dist/web/standalone/.next/server/app/index.html +1 -1
  216. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  217. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  218. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  219. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  220. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  221. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  222. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  223. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  224. package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
  225. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  226. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  227. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  228. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  229. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  230. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  231. package/dist/web/standalone/.next/static/chunks/{796.cf859a427a2cb2ac.js → 796.e0bdc932325d7e03.js} +1 -1
  232. package/dist/web/standalone/.next/static/chunks/{webpack-fbea77b5f9953368.js → webpack-f0285ce91d4ec9ef.js} +1 -1
  233. package/dist/web/standalone/package.json +1 -1
  234. package/dist/worktree-cli.js +3 -6
  235. package/dist/worktree-status-banner.js +7 -11
  236. package/package.json +1 -1
  237. package/packages/cloud-mcp-gateway/package.json +2 -2
  238. package/packages/contracts/dist/rpc.d.ts +1 -0
  239. package/packages/contracts/dist/rpc.d.ts.map +1 -1
  240. package/packages/contracts/dist/rpc.js.map +1 -1
  241. package/packages/contracts/dist/workflow.d.ts +4 -0
  242. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  243. package/packages/contracts/dist/workflow.js.map +1 -1
  244. package/packages/contracts/package.json +1 -1
  245. package/packages/daemon/package.json +4 -4
  246. package/packages/gsd-agent-core/package.json +5 -5
  247. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +5 -0
  248. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  249. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +8 -0
  250. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  251. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  252. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +7 -0
  253. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  254. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  255. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
  256. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  257. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  258. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
  259. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  260. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  261. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
  262. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  263. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  264. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
  265. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  266. package/packages/gsd-agent-modes/package.json +7 -7
  267. package/packages/mcp-server/dist/cli.js +6 -3
  268. package/packages/mcp-server/dist/cli.js.map +1 -1
  269. package/packages/mcp-server/dist/workflow-tools.d.ts +8 -0
  270. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  271. package/packages/mcp-server/dist/workflow-tools.js +46 -21
  272. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  273. package/packages/mcp-server/package.json +3 -3
  274. package/packages/native/package.json +1 -1
  275. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
  276. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  277. package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
  278. package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  279. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  280. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  281. package/packages/pi-agent-core/dist/index.js +3 -0
  282. package/packages/pi-agent-core/dist/index.js.map +1 -1
  283. package/packages/pi-agent-core/package.json +1 -1
  284. package/packages/pi-ai/README.md +1 -0
  285. package/packages/pi-ai/dist/models.generated.d.ts +192 -0
  286. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  287. package/packages/pi-ai/dist/models.generated.js +166 -0
  288. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  289. package/packages/pi-ai/package.json +3 -2
  290. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
  291. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  292. package/packages/pi-coding-agent/dist/core/auth-storage.js +19 -13
  293. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  294. package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
  296. package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
  297. package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
  298. package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
  299. package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
  300. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
  301. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  302. package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
  303. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  304. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  305. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  306. package/packages/pi-coding-agent/dist/index.js +1 -1
  307. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  308. package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
  309. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  310. package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
  311. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  312. package/packages/pi-coding-agent/package.json +7 -7
  313. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  314. package/packages/pi-tui/dist/tui.js +9 -0
  315. package/packages/pi-tui/dist/tui.js.map +1 -1
  316. package/packages/pi-tui/package.json +2 -2
  317. package/packages/rpc-client/package.json +2 -2
  318. package/pkg/package.json +1 -1
  319. package/src/resources/GSD-WORKFLOW.md +5 -4
  320. package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
  321. package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
  322. package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
  323. package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
  324. package/src/resources/extensions/async-jobs/index.ts +79 -0
  325. package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
  326. package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
  327. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
  328. package/src/resources/extensions/bg-shell/overlay.ts +9 -5
  329. package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
  330. package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
  331. package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
  332. package/src/resources/extensions/bg-shell/utilities.ts +5 -2
  333. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +265 -98
  334. package/src/resources/extensions/browser-tools/engine/selection.ts +90 -4
  335. package/src/resources/extensions/browser-tools/index.ts +71 -13
  336. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +83 -13
  337. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +29 -1
  338. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +136 -0
  339. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  340. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +40 -4
  341. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
  342. package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +21 -3
  343. package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
  344. package/src/resources/extensions/gsd/auto/dispatch-history.ts +152 -0
  345. package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
  346. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
  347. package/src/resources/extensions/gsd/auto/loop.ts +4 -1
  348. package/src/resources/extensions/gsd/auto/orchestrator.ts +109 -51
  349. package/src/resources/extensions/gsd/auto/phases.ts +12 -3
  350. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -32
  351. package/src/resources/extensions/gsd/auto-dispatch.ts +38 -52
  352. package/src/resources/extensions/gsd/auto-model-selection.ts +25 -5
  353. package/src/resources/extensions/gsd/auto-post-unit.ts +37 -13
  354. package/src/resources/extensions/gsd/auto-prompts.ts +118 -35
  355. package/src/resources/extensions/gsd/auto-start.ts +24 -29
  356. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  357. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +14 -21
  358. package/src/resources/extensions/gsd/auto-verification.ts +8 -26
  359. package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
  360. package/src/resources/extensions/gsd/auto-worktree.ts +41 -364
  361. package/src/resources/extensions/gsd/auto.ts +20 -24
  362. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -5
  363. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +33 -12
  364. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -0
  365. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +270 -37
  366. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +368 -78
  367. package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
  368. package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
  369. package/src/resources/extensions/gsd/browser-evidence.ts +18 -2
  370. package/src/resources/extensions/gsd/captures.ts +5 -16
  371. package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
  372. package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
  373. package/src/resources/extensions/gsd/consent-question.ts +416 -0
  374. package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
  375. package/src/resources/extensions/gsd/constants.ts +0 -3
  376. package/src/resources/extensions/gsd/crash-recovery.ts +3 -9
  377. package/src/resources/extensions/gsd/db/engine.ts +809 -0
  378. package/src/resources/extensions/gsd/db/queries.ts +490 -0
  379. package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
  380. package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
  381. package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
  382. package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
  383. package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
  384. package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
  385. package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
  386. package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
  387. package/src/resources/extensions/gsd/doctor-format.ts +12 -7
  388. package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
  389. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
  390. package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
  391. package/src/resources/extensions/gsd/error-classifier.ts +11 -0
  392. package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
  393. package/src/resources/extensions/gsd/files.ts +33 -12
  394. package/src/resources/extensions/gsd/git-service.ts +1 -0
  395. package/src/resources/extensions/gsd/gitignore.ts +3 -0
  396. package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
  397. package/src/resources/extensions/gsd/guidance.ts +217 -0
  398. package/src/resources/extensions/gsd/guided-flow.ts +50 -5
  399. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -0
  400. package/src/resources/extensions/gsd/mcp-filter.ts +2 -23
  401. package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
  402. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
  403. package/src/resources/extensions/gsd/migrate/safety.ts +18 -7
  404. package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
  405. package/src/resources/extensions/gsd/milestone-closeout.ts +13 -23
  406. package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
  407. package/src/resources/extensions/gsd/model-router.ts +3 -0
  408. package/src/resources/extensions/gsd/notification-store.ts +26 -3
  409. package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
  410. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
  411. package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
  412. package/src/resources/extensions/gsd/paths.ts +42 -22
  413. package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
  414. package/src/resources/extensions/gsd/preferences-models.ts +12 -47
  415. package/src/resources/extensions/gsd/preferences.ts +18 -0
  416. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  417. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  418. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  419. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  420. package/src/resources/extensions/gsd/prompts/run-uat.md +6 -4
  421. package/src/resources/extensions/gsd/prompts/system.md +5 -2
  422. package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
  423. package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
  424. package/src/resources/extensions/gsd/publication.ts +122 -0
  425. package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
  426. package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
  427. package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
  428. package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
  429. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
  430. package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
  431. package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
  432. package/src/resources/extensions/gsd/state.ts +9 -21
  433. package/src/resources/extensions/gsd/status-guards.ts +59 -8
  434. package/src/resources/extensions/gsd/stop-notice.ts +75 -0
  435. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
  436. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +22 -0
  437. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +101 -26
  438. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
  439. package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
  440. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
  441. package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
  442. package/src/resources/extensions/gsd/tests/browser-automation-contract-fixture.ts +39 -0
  443. package/src/resources/extensions/gsd/tests/browser-contract.test.ts +44 -0
  444. package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
  445. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
  446. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -0
  447. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
  448. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  449. package/src/resources/extensions/gsd/tests/consent-question.test.ts +336 -0
  450. package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
  451. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
  452. package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
  453. package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
  454. package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +2 -1
  455. package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
  456. package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
  457. package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
  458. package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
  459. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
  460. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +35 -1
  461. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
  462. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
  463. package/src/resources/extensions/gsd/tests/guidance.test.ts +148 -0
  464. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +58 -15
  465. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +74 -59
  466. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
  467. package/src/resources/extensions/gsd/tests/integration/gsd-integration-fixture.ts +80 -0
  468. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
  469. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
  470. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
  471. package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
  472. package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
  473. package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
  474. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
  475. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +139 -0
  476. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
  477. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
  478. package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
  479. package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
  480. package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
  481. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +248 -1
  482. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +1 -0
  483. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
  484. package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
  485. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
  486. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
  487. package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
  488. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
  489. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +76 -0
  490. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
  491. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
  492. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +112 -29
  493. package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
  494. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +67 -2
  495. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
  496. package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +44 -1
  497. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  498. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +2 -2
  499. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
  500. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
  501. package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
  502. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
  503. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
  504. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
  505. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
  506. package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
  507. package/src/resources/extensions/gsd/tests/write-gate.test.ts +109 -1
  508. package/src/resources/extensions/gsd/tool-presentation-plan.ts +4 -4
  509. package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
  510. package/src/resources/extensions/gsd/tools/complete-slice.ts +43 -68
  511. package/src/resources/extensions/gsd/tools/exec-tool.ts +9 -8
  512. package/src/resources/extensions/gsd/tools/plan-slice.ts +12 -6
  513. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
  514. package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
  515. package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
  516. package/src/resources/extensions/gsd/uat-policy.ts +62 -16
  517. package/src/resources/extensions/gsd/undo.ts +9 -8
  518. package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
  519. package/src/resources/extensions/gsd/unit-context-composer.ts +111 -1
  520. package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
  521. package/src/resources/extensions/gsd/unit-registry.ts +412 -0
  522. package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
  523. package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
  524. package/src/resources/extensions/gsd/web-app-uat.ts +51 -8
  525. package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
  526. package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
  527. package/src/resources/extensions/gsd/worktree-lifecycle.ts +13 -9
  528. package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
  529. package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
  530. package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
  531. package/src/resources/extensions/gsd/worktree-root.ts +29 -6
  532. package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
  533. package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
  534. package/src/resources/extensions/search-the-web/native-search.ts +5 -3
  535. package/src/resources/extensions/shared/browser-contract.ts +66 -0
  536. package/src/resources/extensions/shared/gsd-browser-cli.ts +119 -5
  537. package/src/resources/shared/package.json +3 -0
  538. package/src/resources/skills/create-skill/references/executable-code.md +1 -1
  539. package/src/resources/skills/create-skill/workflows/add-reference.md +8 -3
  540. package/src/resources/skills/create-skill/workflows/add-script.md +4 -2
  541. package/src/resources/skills/create-skill/workflows/add-template.md +3 -1
  542. package/src/resources/skills/create-skill/workflows/add-workflow.md +8 -3
  543. package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
  544. package/src/resources/skills/create-skill/workflows/verify-skill.md +9 -4
  545. package/src/resources/skills/gsd-browser/SKILL.md +1 -1
  546. package/src/resources/skills/spike-wrap-up/SKILL.md +9 -9
  547. package/dist/resources/extensions/gsd/user-input-boundary.js +0 -218
  548. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -173
  549. package/src/resources/extensions/gsd/user-input-boundary.ts +0 -216
  550. /package/dist/web/standalone/.next/static/{3PtrU9qGPEXwNLWkIyiqk → jmTLg6xZmAuq_LIqKOxrH}/_buildManifest.js +0 -0
  551. /package/dist/web/standalone/.next/static/{3PtrU9qGPEXwNLWkIyiqk → jmTLg6xZmAuq_LIqKOxrH}/_ssgManifest.js +0 -0
@@ -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
 
@@ -0,0 +1,358 @@
1
+ // gsd-pi - Write-gate two-process seam tests.
2
+ /**
3
+ * Deterministic interleaving tests for the host/child write-gate adapters
4
+ * (write-gate.ts). The "child" (workflow MCP server) runs in a separate
5
+ * process in production; these tests simulate its writes by stamping the
6
+ * snapshot file directly, exactly as childWriteGateAdapter persists it.
7
+ *
8
+ * Covered interleavings:
9
+ * (a) child verifies on disk while the host holds stale memory — the host
10
+ * re-arm must NOT clobber the verification, on BOTH windows
11
+ * (tool_execution_start re-arm and the tool_call defer path);
12
+ * (b) concurrent writes: every persist is an unconditional read-merge-write
13
+ * (read disk → union-merge → mutate → atomic rename), so a write the
14
+ * other process landed in between is folded in, never overwritten;
15
+ * (c) two basePaths defer approval gates in the same process — both stay
16
+ * deferred and both activate (regression for the old single global slot);
17
+ * (d) old snapshot files (including ones carrying the retired epoch field)
18
+ * keep loading; stale fields are dropped on the next write.
19
+ */
20
+
21
+ import test from "node:test";
22
+ import assert from "node:assert/strict";
23
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
24
+ import { join } from "node:path";
25
+ import { tmpdir } from "node:os";
26
+
27
+ import { registerHooks } from "../bootstrap/register-hooks.ts";
28
+ import {
29
+ childWriteGateAdapter,
30
+ clearDiscussionFlowState,
31
+ getPendingGate,
32
+ hostWriteGateAdapter,
33
+ loadWriteGateSnapshot,
34
+ markDepthVerified,
35
+ refreshWriteGateStateFromDisk,
36
+ setPendingGate,
37
+ type WriteGateSnapshot,
38
+ } from "../bootstrap/write-gate.ts";
39
+
40
+ function makeTempDir(prefix: string): string {
41
+ const dir = join(
42
+ tmpdir(),
43
+ `gsd-write-gate-seam-${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
44
+ );
45
+ mkdirSync(dir, { recursive: true });
46
+ return dir;
47
+ }
48
+
49
+ function snapshotPath(basePath: string): string {
50
+ return join(basePath, ".gsd", "runtime", "write-gate-state.json");
51
+ }
52
+
53
+ /** Simulate a write from the OTHER process by stamping the file directly. */
54
+ function foreignProcessWrites(basePath: string, snapshot: Partial<WriteGateSnapshot>): void {
55
+ mkdirSync(join(basePath, ".gsd", "runtime"), { recursive: true });
56
+ writeFileSync(snapshotPath(basePath), JSON.stringify({
57
+ verifiedDepthMilestones: [],
58
+ verifiedApprovalGates: [],
59
+ activeQueuePhase: false,
60
+ pendingGateId: null,
61
+ ...snapshot,
62
+ }, null, 2), "utf-8");
63
+ }
64
+
65
+ function readDiskRaw(basePath: string): Record<string, unknown> {
66
+ return JSON.parse(readFileSync(snapshotPath(basePath), "utf-8"));
67
+ }
68
+
69
+ function makeHookHarness(): {
70
+ handlers: Map<string, Array<(event: any, ctx?: any) => Promise<any> | any>>;
71
+ pi: any;
72
+ } {
73
+ const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<any> | any>>();
74
+ const pi = {
75
+ on(event: string, handler: (event: any, ctx?: any) => Promise<any> | any) {
76
+ const existing = handlers.get(event) ?? [];
77
+ existing.push(handler);
78
+ handlers.set(event, existing);
79
+ },
80
+ } as any;
81
+ return { handlers, pi };
82
+ }
83
+
84
+ function cleanup(dir: string): void {
85
+ clearDiscussionFlowState(dir);
86
+ rmSync(dir, { recursive: true, force: true });
87
+ }
88
+
89
+ const GATE = "depth_verification_M007_confirm";
90
+
91
+ // ── (a) verified-on-disk wins over a host re-arm ────────────────────────────
92
+
93
+ test("seam: host setPending does not clobber a child verification on disk", (t) => {
94
+ const dir = makeTempDir("no-clobber-adapter");
95
+ t.after(() => cleanup(dir));
96
+
97
+ // Host has stale memory: it armed the gate earlier (persisted to disk).
98
+ assert.equal(setPendingGate(GATE, dir), true, "fresh gate must arm");
99
+ assert.equal(getPendingGate(dir), GATE);
100
+
101
+ // Child verifies the gate in its own process (newer write on disk).
102
+ foreignProcessWrites(dir, {
103
+ verifiedDepthMilestones: ["M007"],
104
+ verifiedApprovalGates: [GATE],
105
+ writer: "child",
106
+ });
107
+
108
+ // Host attempts a re-arm — adapter policy: verified on disk wins.
109
+ assert.equal(hostWriteGateAdapter.setPending(GATE, dir), false, "re-arm must be suppressed");
110
+ const snapshot = loadWriteGateSnapshot(dir);
111
+ assert.ok(snapshot.verifiedDepthMilestones.includes("M007"), "verification must survive");
112
+ assert.ok((snapshot.verifiedApprovalGates ?? []).includes(GATE));
113
+ assert.equal(getPendingGate(dir), null, "no pending gate after suppressed re-arm");
114
+ });
115
+
116
+ test("seam: tool_call defer path does not block tools for a gate the child verified", async (t) => {
117
+ const dir = makeTempDir("no-clobber-defer");
118
+ t.after(() => cleanup(dir));
119
+
120
+ // Child verified the gate before the host ever saw the tool block.
121
+ foreignProcessWrites(dir, {
122
+ verifiedDepthMilestones: ["M007"],
123
+ verifiedApprovalGates: [GATE],
124
+ writer: "child",
125
+ });
126
+
127
+ const { handlers, pi } = makeHookHarness();
128
+ registerHooks(pi, []);
129
+ const ctx = { cwd: dir, ui: { notify: () => undefined } } as any;
130
+
131
+ // tool_call defer window: ask_user_questions arrives post-hoc with the gate id.
132
+ for (const handler of handlers.get("tool_call") ?? []) {
133
+ await handler({
134
+ toolCallId: "t-gate",
135
+ toolName: "ask_user_questions",
136
+ input: { questions: [{ id: GATE }] },
137
+ }, ctx);
138
+ }
139
+
140
+ // A subsequent tool in the same turn must NOT hit the deferred-gate block.
141
+ let blocked: any;
142
+ for (const handler of handlers.get("tool_call") ?? []) {
143
+ const result = await handler({
144
+ toolCallId: "t-next",
145
+ toolName: "glob",
146
+ input: { pattern: "*.md" },
147
+ }, ctx);
148
+ if (result?.block) blocked = result;
149
+ }
150
+ assert.equal(blocked, undefined, "verified gate must not be deferred/blocking");
151
+ assert.equal(getPendingGate(dir), null);
152
+ const snapshot = loadWriteGateSnapshot(dir);
153
+ assert.ok(snapshot.verifiedDepthMilestones.includes("M007"), "verification must survive the defer window");
154
+ });
155
+
156
+ test("seam: tool_execution_start re-arm window keeps the child verification", async (t) => {
157
+ const dir = makeTempDir("no-clobber-exec-start");
158
+ t.after(() => cleanup(dir));
159
+
160
+ foreignProcessWrites(dir, {
161
+ verifiedDepthMilestones: ["M007"],
162
+ verifiedApprovalGates: [GATE],
163
+ writer: "child",
164
+ });
165
+
166
+ const { handlers, pi } = makeHookHarness();
167
+ registerHooks(pi, []);
168
+ const ctx = { cwd: dir, ui: { notify: () => undefined } } as any;
169
+
170
+ for (const handler of handlers.get("tool_execution_start") ?? []) {
171
+ await handler({
172
+ toolCallId: "t-gate",
173
+ toolName: "mcp__gsd-workflow__ask_user_questions",
174
+ args: { questions: [{ id: GATE }] },
175
+ }, ctx);
176
+ }
177
+
178
+ assert.equal(getPendingGate(dir), null, "post-hoc replay must not re-arm a verified gate");
179
+ assert.ok(loadWriteGateSnapshot(dir).verifiedDepthMilestones.includes("M007"));
180
+ });
181
+
182
+ // ── (b) concurrent writes re-merge instead of overwriting ───────────────────
183
+
184
+ test("seam: host persist re-merges a concurrent child write (unconditional read-merge-write)", (t) => {
185
+ const dir = makeTempDir("concurrent-write");
186
+ t.after(() => cleanup(dir));
187
+
188
+ // Host persists its own verification.
189
+ markDepthVerified("M001", dir);
190
+
191
+ // Child lands a different verification on disk while the host is idle.
192
+ foreignProcessWrites(dir, {
193
+ verifiedDepthMilestones: ["M002"],
194
+ writer: "child",
195
+ });
196
+
197
+ // Host persists again — every mutation re-reads the disk snapshot and
198
+ // union-merges before writing, so the child's verification survives.
199
+ markDepthVerified("M003", dir);
200
+ const merged = loadWriteGateSnapshot(dir);
201
+ assert.deepEqual(merged.verifiedDepthMilestones, ["M001", "M002", "M003"]);
202
+ assert.equal(readDiskRaw(dir).writer, "host");
203
+ });
204
+
205
+ test("seam: missing snapshot resets stale in-memory pending gate before mutation", (t) => {
206
+ const dir = makeTempDir("missing-snapshot-reset");
207
+ t.after(() => cleanup(dir));
208
+
209
+ assert.equal(setPendingGate(GATE, dir), true);
210
+ rmSync(snapshotPath(dir), { force: true });
211
+
212
+ markDepthVerified("M008", dir);
213
+
214
+ const snapshot = loadWriteGateSnapshot(dir);
215
+ assert.equal(snapshot.pendingGateId, null);
216
+ assert.deepEqual(snapshot.verifiedDepthMilestones, ["M008"]);
217
+ assert.equal(readDiskRaw(dir).pendingGateId, null);
218
+ });
219
+
220
+ test("seam: childWriteGateAdapter is write-through and stamps writer provenance", (t) => {
221
+ const dir = makeTempDir("child-write-through");
222
+ t.after(() => cleanup(dir));
223
+
224
+ foreignProcessWrites(dir, { verifiedDepthMilestones: ["M001"], writer: "host" });
225
+ childWriteGateAdapter.markDepthVerified("M002", dir);
226
+
227
+ const disk = readDiskRaw(dir);
228
+ assert.deepEqual(disk.verifiedDepthMilestones, ["M001", "M002"], "fresh disk read, then mutate");
229
+ assert.equal(disk.writer, "child");
230
+ });
231
+
232
+ // ── (c) per-basePath deferred gates ──────────────────────────────────────────
233
+
234
+ test("seam: two basePaths defer gates in one process and both activate", async (t) => {
235
+ const dirA = makeTempDir("defer-a");
236
+ const dirB = makeTempDir("defer-b");
237
+ t.after(() => {
238
+ cleanup(dirA);
239
+ cleanup(dirB);
240
+ });
241
+
242
+ const { handlers, pi } = makeHookHarness();
243
+ registerHooks(pi, []);
244
+ const gateA = "depth_verification_M010_confirm";
245
+ const gateB = "depth_verification_M020_confirm";
246
+ const ctxA = { cwd: dirA, ui: { notify: () => undefined } } as any;
247
+ const ctxB = { cwd: dirB, ui: { notify: () => undefined } } as any;
248
+
249
+ for (const handler of handlers.get("tool_call") ?? []) {
250
+ await handler({ toolCallId: "a-1", toolName: "ask_user_questions", input: { questions: [{ id: gateA }] } }, ctxA);
251
+ }
252
+ for (const handler of handlers.get("tool_call") ?? []) {
253
+ await handler({ toolCallId: "b-1", toolName: "ask_user_questions", input: { questions: [{ id: gateB }] } }, ctxB);
254
+ }
255
+
256
+ // With the old single global slot, project A's deferral was lost the moment
257
+ // project B deferred. Both must still block follow-up tools.
258
+ for (const [ctx, label] of [[ctxA, "A"], [ctxB, "B"]] as const) {
259
+ let blocked: any;
260
+ for (const handler of handlers.get("tool_call") ?? []) {
261
+ const result = await handler({ toolCallId: `chk-${label}`, toolName: "glob", input: { pattern: "*" } }, ctx);
262
+ if (result?.block) blocked = result;
263
+ }
264
+ assert.equal(blocked?.block, true, `project ${label} deferred gate must still block`);
265
+ assert.match(blocked?.reason ?? "", /Approval question/);
266
+ }
267
+
268
+ // Activation happens via tool_execution_start in each project independently.
269
+ for (const [ctx, gate, dir] of [[ctxA, gateA, dirA], [ctxB, gateB, dirB]] as const) {
270
+ for (const handler of handlers.get("tool_execution_start") ?? []) {
271
+ await handler({ toolCallId: "act", toolName: "ask_user_questions", args: { questions: [{ id: gate }] } }, ctx);
272
+ }
273
+ assert.equal(getPendingGate(dir), gate, `gate must arm durably for ${dir}`);
274
+ }
275
+ });
276
+
277
+ // ── (d) backward compatibility: legacy snapshot fields ──────────────────────
278
+
279
+ test("seam: old snapshot with a retired epoch field loads and sheds it on write", (t) => {
280
+ const dir = makeTempDir("legacy-snapshot");
281
+ t.after(() => cleanup(dir));
282
+
283
+ writeFileSync(
284
+ (mkdirSync(join(dir, ".gsd", "runtime"), { recursive: true }), snapshotPath(dir)),
285
+ JSON.stringify({
286
+ verifiedDepthMilestones: ["M001"],
287
+ verifiedApprovalGates: ["depth_verification_M001_confirm"],
288
+ activeQueuePhase: false,
289
+ pendingGateId: null,
290
+ // Written by an older build that still stamped the write-only epoch.
291
+ epoch: 7,
292
+ }),
293
+ "utf-8",
294
+ );
295
+
296
+ const loaded = loadWriteGateSnapshot(dir);
297
+ assert.deepEqual(loaded.verifiedDepthMilestones, ["M001"]);
298
+ assert.equal("epoch" in loaded, false, "retired epoch field is not surfaced");
299
+
300
+ const refreshed = refreshWriteGateStateFromDisk(dir);
301
+ assert.ok(refreshed.verifiedDepthMilestones.includes("M001"));
302
+
303
+ markDepthVerified("M002", dir);
304
+ const upgraded = readDiskRaw(dir);
305
+ assert.deepEqual(upgraded.verifiedDepthMilestones, ["M001", "M002"]);
306
+ assert.equal("epoch" in upgraded, false, "retired epoch field is dropped on rewrite");
307
+ assert.equal(upgraded.writer, "host");
308
+ });
309
+
310
+ // ── (e) corrupt snapshot is treated as a reset (matches "delete the file") ──
311
+
312
+ test("seam: corrupt snapshot file resets host state instead of persisting stale pendingGateId", (t) => {
313
+ const dir = makeTempDir("corrupt-snapshot");
314
+ t.after(() => cleanup(dir));
315
+
316
+ // Host arms a gate; stale pendingGateId now lives in memory and on disk.
317
+ assert.equal(setPendingGate(GATE, dir), true);
318
+ assert.equal(getPendingGate(dir), GATE);
319
+
320
+ // The snapshot file is corrupted out-of-band (e.g. partial write from a
321
+ // crashed editor, foreign tool, or filesystem fault).
322
+ writeFileSync(snapshotPath(dir), "{ not json", "utf-8");
323
+
324
+ // refreshWriteGateStateFromDisk must treat the unreadable file the same
325
+ // as a missing file: full reset, including dropping the stale pending id.
326
+ const refreshed = refreshWriteGateStateFromDisk(dir);
327
+ assert.equal(refreshed.pendingGateId, null, "stale pendingGateId must not survive a corrupt snapshot");
328
+ assert.deepEqual(refreshed.verifiedDepthMilestones, []);
329
+ assert.deepEqual(refreshed.verifiedApprovalGates, []);
330
+ assert.equal(getPendingGate(dir), null);
331
+
332
+ // A subsequent mutation must not write the stale gate back to disk.
333
+ markDepthVerified("M042", dir);
334
+ const persisted = readDiskRaw(dir);
335
+ assert.equal(persisted.pendingGateId, null, "next persist must not re-stamp the stale pending id");
336
+ assert.deepEqual(persisted.verifiedDepthMilestones, ["M042"]);
337
+ });
338
+
339
+ test("seam: mutateWriteGateState reset path drops stale pendingGateId on a corrupt snapshot", (t) => {
340
+ const dir = makeTempDir("corrupt-snapshot-mutate");
341
+ t.after(() => cleanup(dir));
342
+
343
+ // Host arms a gate first.
344
+ assert.equal(setPendingGate(GATE, dir), true);
345
+
346
+ // Corrupt the snapshot directly (skipping the refresh path so we exercise
347
+ // the reconcile-on-mutate branch in mutateWriteGateState).
348
+ writeFileSync(snapshotPath(dir), "}}}", "utf-8");
349
+
350
+ // markDepthVerified runs through mutateWriteGateState; the reconcile pass
351
+ // must reset the in-memory state when the disk read returns null, even
352
+ // though the file still exists on disk.
353
+ markDepthVerified("M099", dir);
354
+
355
+ const persisted = readDiskRaw(dir);
356
+ assert.equal(persisted.pendingGateId, null, "stale pending id must not be persisted after a corrupt-snapshot reconcile");
357
+ assert.deepEqual(persisted.verifiedDepthMilestones, ["M099"]);
358
+ });
@@ -24,6 +24,7 @@ import {
24
24
  setQueuePhaseActive,
25
25
  } from '../index.ts';
26
26
  import {
27
+ childWriteGateAdapter,
27
28
  markDepthVerified,
28
29
  isMilestoneDepthVerified,
29
30
  markApprovalGateVerified,
@@ -317,7 +318,11 @@ test('write-gate: reopening a gate revokes its previous verified approval', () =
317
318
  'precondition: verified approval unlocks the final project artifact',
318
319
  );
319
320
 
320
- setPendingGate('depth_verification_project_confirm', base);
321
+ // A genuine re-ask originates from the workflow MCP child (where
322
+ // ask_user_questions executes): the child adapter arms unconditionally,
323
+ // revoking the prior approval. Host-side setPendingGate is guarded
324
+ // (verified-on-disk wins) and would deliberately suppress this arm.
325
+ childWriteGateAdapter.setPending('depth_verification_project_confirm', base);
321
326
  clearPendingGate(base);
322
327
 
323
328
  assert.strictEqual(
@@ -458,6 +463,66 @@ test('write-gate: applyAskUserQuestionsGateResult verifies confirmed pending gat
458
463
  }
459
464
  });
460
465
 
466
+ test('write-gate: applyAskUserQuestionsGateResult reports declined pending gate (consent-verdict semantics)', () => {
467
+ const base = join(tmpdir(), `gsd-write-gate-ask-declined-${randomUUID()}`);
468
+ const gateId = 'depth_verification_M001_confirm';
469
+
470
+ try {
471
+ mkdirSync(base, { recursive: true });
472
+ clearDiscussionFlowState(base);
473
+ setPendingGate(gateId, base);
474
+
475
+ const result = applyAskUserQuestionsGateResult({
476
+ basePath: base,
477
+ questions: [{
478
+ id: gateId,
479
+ options: [{ label: 'Confirm depth (Recommended)' }, { label: 'Needs adjustment' }],
480
+ }],
481
+ details: {
482
+ response: { answers: { [gateId]: { selected: 'Needs adjustment' } } },
483
+ },
484
+ });
485
+
486
+ assert.deepEqual(result, { status: 'declined', gateId });
487
+ assert.strictEqual(getPendingGate(base), gateId, 'declined gate must stay pending');
488
+ assert.strictEqual(isMilestoneDepthVerified('M001', base), false, 'declined gate must not verify depth');
489
+ } finally {
490
+ clearDiscussionFlowState(base);
491
+ rmSync(base, { recursive: true, force: true });
492
+ }
493
+ });
494
+
495
+ test('write-gate: applyAskUserQuestionsGateResult keeps empty-selection pending gate waiting (consent-verdict semantics)', () => {
496
+ // An empty selection is never an answer (fail-closed): the round used to
497
+ // report "answered" here, silently treating a missing selection as resolved.
498
+ const base = join(tmpdir(), `gsd-write-gate-ask-empty-${randomUUID()}`);
499
+ const gateId = 'depth_verification_M001_confirm';
500
+
501
+ try {
502
+ mkdirSync(base, { recursive: true });
503
+ clearDiscussionFlowState(base);
504
+ setPendingGate(gateId, base);
505
+
506
+ const result = applyAskUserQuestionsGateResult({
507
+ basePath: base,
508
+ questions: [{
509
+ id: gateId,
510
+ options: [{ label: 'Confirm depth (Recommended)' }, { label: 'Needs adjustment' }],
511
+ }],
512
+ details: {
513
+ response: { answers: { [gateId]: { selected: '' } } },
514
+ },
515
+ });
516
+
517
+ assert.deepEqual(result, { status: 'waiting', pendingGateId: gateId, interrupted: false });
518
+ assert.strictEqual(getPendingGate(base), gateId, 'unanswered gate must stay pending');
519
+ assert.strictEqual(isMilestoneDepthVerified('M001', base), false, 'unanswered gate must not verify depth');
520
+ } finally {
521
+ clearDiscussionFlowState(base);
522
+ rmSync(base, { recursive: true, force: true });
523
+ }
524
+ });
525
+
461
526
  // ─── Scenario 21: shouldBlockPendingGate blocks non-safe tools when gate is pending ──
462
527
 
463
528
  test('write-gate: shouldBlockPendingGate blocks write/edit during pending gate', () => {
@@ -789,6 +854,7 @@ test('write-gate: resetWriteGateState persists through dangling .gsd symlink', (
789
854
  verifiedApprovalGates: [],
790
855
  activeQueuePhase: false,
791
856
  pendingGateId: null,
857
+ writer: 'host',
792
858
  });
793
859
  } finally {
794
860
  if (originalEnv === undefined) {
@@ -803,3 +869,45 @@ test('write-gate: resetWriteGateState persists through dangling .gsd symlink', (
803
869
  } catch { /* swallow */ }
804
870
  }
805
871
  });
872
+
873
+ // ─── Scenario 31: hydrate in-memory gate state from persisted snapshot (MCP subprocess) ──
874
+
875
+ test('write-gate: getPendingGate hydrates from disk when workflow MCP verified gate in child process', () => {
876
+ const base = join(tmpdir(), `gsd-write-gate-mcp-hydrate-${randomUUID()}`);
877
+ const stateFilePath = join(base, '.gsd', 'runtime', 'write-gate-state.json');
878
+ const originalEnv = process.env.GSD_PERSIST_WRITE_GATE_STATE;
879
+ const gateId = 'depth_verification_M005_confirm';
880
+
881
+ try {
882
+ process.env.GSD_PERSIST_WRITE_GATE_STATE = '1';
883
+ mkdirSync(join(base, '.gsd', 'runtime'), { recursive: true });
884
+ clearDiscussionFlowState(base);
885
+ setPendingGate(gateId, base);
886
+
887
+ writeFileSync(stateFilePath, JSON.stringify({
888
+ verifiedDepthMilestones: ['M005'],
889
+ verifiedApprovalGates: [gateId],
890
+ activeQueuePhase: false,
891
+ pendingGateId: null,
892
+ }, null, 2), 'utf-8');
893
+
894
+ assert.strictEqual(getPendingGate(base), null, 'stale in-memory pending must refresh from disk');
895
+ assert.strictEqual(isMilestoneDepthVerified('M005', base), true, 'verified milestone must hydrate from disk');
896
+ assert.deepEqual(loadWriteGateSnapshot(base), {
897
+ verifiedDepthMilestones: ['M005'],
898
+ verifiedApprovalGates: [gateId],
899
+ activeQueuePhase: false,
900
+ pendingGateId: null,
901
+ });
902
+ } finally {
903
+ if (originalEnv === undefined) {
904
+ delete process.env.GSD_PERSIST_WRITE_GATE_STATE;
905
+ } else {
906
+ process.env.GSD_PERSIST_WRITE_GATE_STATE = originalEnv;
907
+ }
908
+ clearDiscussionFlowState(base);
909
+ try {
910
+ rmSync(base, { recursive: true, force: true });
911
+ } catch { /* swallow */ }
912
+ }
913
+ });