@opengsd/gsd-pi 1.2.0-dev.955e4da0 → 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 (424) 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 +3 -0
  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/stream-adapter.js +30 -4
  25. package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +17 -2
  26. package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
  27. package/dist/resources/extensions/gsd/auto/dispatch-history.js +105 -0
  28. package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
  29. package/dist/resources/extensions/gsd/auto/loop.js +4 -1
  30. package/dist/resources/extensions/gsd/auto/orchestrator.js +61 -44
  31. package/dist/resources/extensions/gsd/auto/phases.js +2 -2
  32. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -32
  33. package/dist/resources/extensions/gsd/auto-dispatch.js +40 -57
  34. package/dist/resources/extensions/gsd/auto-model-selection.js +25 -6
  35. package/dist/resources/extensions/gsd/auto-post-unit.js +23 -8
  36. package/dist/resources/extensions/gsd/auto-prompts.js +81 -19
  37. package/dist/resources/extensions/gsd/auto-start.js +18 -15
  38. package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
  39. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +12 -20
  40. package/dist/resources/extensions/gsd/auto-verification.js +9 -28
  41. package/dist/resources/extensions/gsd/auto-worktree.js +30 -90
  42. package/dist/resources/extensions/gsd/auto.js +4 -13
  43. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -2
  44. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +23 -6
  45. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +19 -0
  46. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +212 -48
  47. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +303 -77
  48. package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
  49. package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
  50. package/dist/resources/extensions/gsd/browser-evidence.js +8 -2
  51. package/dist/resources/extensions/gsd/captures.js +4 -6
  52. package/dist/resources/extensions/gsd/consent-question.js +337 -0
  53. package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
  54. package/dist/resources/extensions/gsd/constants.js +0 -2
  55. package/dist/resources/extensions/gsd/crash-recovery.js +4 -12
  56. package/dist/resources/extensions/gsd/db/queries.js +26 -0
  57. package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
  58. package/dist/resources/extensions/gsd/doctor-environment.js +2 -6
  59. package/dist/resources/extensions/gsd/doctor-format.js +9 -6
  60. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +13 -15
  61. package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
  62. package/dist/resources/extensions/gsd/error-classifier.js +9 -0
  63. package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
  64. package/dist/resources/extensions/gsd/files.js +33 -19
  65. package/dist/resources/extensions/gsd/guidance.js +158 -0
  66. package/dist/resources/extensions/gsd/guided-flow.js +17 -2
  67. package/dist/resources/extensions/gsd/markdown-renderer.js +10 -0
  68. package/dist/resources/extensions/gsd/mcp-filter.js +2 -19
  69. package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
  70. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
  71. package/dist/resources/extensions/gsd/migrate/safety.js +4 -1
  72. package/dist/resources/extensions/gsd/milestone-closeout.js +13 -23
  73. package/dist/resources/extensions/gsd/notification-store.js +11 -4
  74. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +6 -4
  75. package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
  76. package/dist/resources/extensions/gsd/paths.js +27 -0
  77. package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
  78. package/dist/resources/extensions/gsd/preferences-models.js +14 -48
  79. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  80. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  81. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  82. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  83. package/dist/resources/extensions/gsd/prompts/run-uat.md +6 -4
  84. package/dist/resources/extensions/gsd/prompts/system.md +5 -2
  85. package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
  86. package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
  87. package/dist/resources/extensions/gsd/publication.js +87 -0
  88. package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
  89. package/dist/resources/extensions/gsd/recovery-classification.js +37 -94
  90. package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
  91. package/dist/resources/extensions/gsd/state.js +6 -20
  92. package/dist/resources/extensions/gsd/stop-notice.js +57 -0
  93. package/dist/resources/extensions/gsd/tool-presentation-plan.js +4 -4
  94. package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
  95. package/dist/resources/extensions/gsd/tools/complete-slice.js +20 -10
  96. package/dist/resources/extensions/gsd/tools/exec-tool.js +9 -7
  97. package/dist/resources/extensions/gsd/tools/plan-slice.js +12 -6
  98. package/dist/resources/extensions/gsd/uat-policy.js +42 -16
  99. package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
  100. package/dist/resources/extensions/gsd/unit-context-composer.js +74 -1
  101. package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
  102. package/dist/resources/extensions/gsd/unit-registry.js +337 -0
  103. package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
  104. package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
  105. package/dist/resources/extensions/gsd/web-app-uat.js +45 -8
  106. package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
  107. package/dist/resources/extensions/gsd/worktree-git-recovery.js +15 -9
  108. package/dist/resources/extensions/gsd/worktree-lifecycle.js +3 -2
  109. package/dist/resources/extensions/gsd/worktree-root.js +11 -0
  110. package/dist/resources/extensions/gsd/worktree-session-state.js +4 -5
  111. package/dist/resources/extensions/search-the-web/native-search.js +5 -3
  112. package/dist/resources/extensions/shared/browser-contract.js +59 -0
  113. package/dist/resources/extensions/shared/gsd-browser-cli.js +96 -5
  114. package/dist/resources/shared/package.json +3 -0
  115. package/dist/resources/skills/create-skill/references/executable-code.md +1 -1
  116. package/dist/resources/skills/create-skill/workflows/add-reference.md +8 -3
  117. package/dist/resources/skills/create-skill/workflows/add-script.md +4 -2
  118. package/dist/resources/skills/create-skill/workflows/add-template.md +3 -1
  119. package/dist/resources/skills/create-skill/workflows/add-workflow.md +8 -3
  120. package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
  121. package/dist/resources/skills/create-skill/workflows/verify-skill.md +9 -4
  122. package/dist/resources/skills/spike-wrap-up/SKILL.md +9 -9
  123. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  124. package/dist/web/standalone/.next/BUILD_ID +1 -1
  125. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  126. package/dist/web/standalone/.next/build-manifest.json +3 -3
  127. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  128. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  130. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  138. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  145. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/index.html +1 -1
  148. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  149. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  154. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  155. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  156. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  157. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  160. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  161. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  162. package/dist/web/standalone/.next/static/chunks/{796.cf859a427a2cb2ac.js → 796.e0bdc932325d7e03.js} +1 -1
  163. package/dist/web/standalone/.next/static/chunks/{webpack-fbea77b5f9953368.js → webpack-f0285ce91d4ec9ef.js} +1 -1
  164. package/dist/web/standalone/package.json +1 -1
  165. package/dist/worktree-cli.js +3 -6
  166. package/dist/worktree-status-banner.js +7 -15
  167. package/package.json +1 -1
  168. package/packages/cloud-mcp-gateway/package.json +2 -2
  169. package/packages/contracts/dist/rpc.d.ts +1 -0
  170. package/packages/contracts/dist/rpc.d.ts.map +1 -1
  171. package/packages/contracts/dist/rpc.js.map +1 -1
  172. package/packages/contracts/dist/workflow.d.ts +4 -0
  173. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  174. package/packages/contracts/dist/workflow.js.map +1 -1
  175. package/packages/contracts/package.json +1 -1
  176. package/packages/daemon/package.json +4 -4
  177. package/packages/gsd-agent-core/package.json +5 -5
  178. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +5 -0
  179. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  180. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  181. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  182. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  183. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +7 -0
  184. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  185. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  186. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
  187. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  188. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  189. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
  190. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  191. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  192. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
  193. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  194. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  195. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
  196. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  197. package/packages/gsd-agent-modes/package.json +7 -7
  198. package/packages/mcp-server/dist/cli.js +6 -3
  199. package/packages/mcp-server/dist/cli.js.map +1 -1
  200. package/packages/mcp-server/dist/workflow-tools.d.ts +8 -0
  201. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  202. package/packages/mcp-server/dist/workflow-tools.js +17 -1
  203. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  204. package/packages/mcp-server/package.json +3 -3
  205. package/packages/native/package.json +1 -1
  206. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
  207. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  208. package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
  209. package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  210. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  211. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  212. package/packages/pi-agent-core/dist/index.js +3 -0
  213. package/packages/pi-agent-core/dist/index.js.map +1 -1
  214. package/packages/pi-agent-core/package.json +1 -1
  215. package/packages/pi-ai/README.md +1 -0
  216. package/packages/pi-ai/dist/image-models.generated.d.ts +2 -2
  217. package/packages/pi-ai/dist/image-models.generated.js +6 -6
  218. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  219. package/packages/pi-ai/dist/models.generated.d.ts +35 -125
  220. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  221. package/packages/pi-ai/dist/models.generated.js +46 -120
  222. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  223. package/packages/pi-ai/package.json +3 -2
  224. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
  225. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/core/auth-storage.js +19 -13
  227. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
  229. package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
  230. package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
  231. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
  232. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  233. package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
  234. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  235. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  236. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  237. package/packages/pi-coding-agent/dist/index.js +1 -1
  238. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  239. package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
  240. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  241. package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
  242. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  243. package/packages/pi-coding-agent/package.json +7 -7
  244. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  245. package/packages/pi-tui/dist/tui.js +9 -0
  246. package/packages/pi-tui/dist/tui.js.map +1 -1
  247. package/packages/pi-tui/package.json +2 -2
  248. package/packages/rpc-client/package.json +2 -2
  249. package/pkg/package.json +1 -1
  250. package/src/resources/GSD-WORKFLOW.md +5 -4
  251. package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
  252. package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
  253. package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
  254. package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
  255. package/src/resources/extensions/async-jobs/index.ts +79 -0
  256. package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
  257. package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
  258. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
  259. package/src/resources/extensions/bg-shell/overlay.ts +9 -5
  260. package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
  261. package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
  262. package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
  263. package/src/resources/extensions/bg-shell/utilities.ts +3 -0
  264. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +265 -98
  265. package/src/resources/extensions/browser-tools/engine/selection.ts +90 -4
  266. package/src/resources/extensions/browser-tools/index.ts +71 -13
  267. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +83 -13
  268. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +29 -1
  269. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +136 -0
  270. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +34 -4
  271. package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +21 -3
  272. package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
  273. package/src/resources/extensions/gsd/auto/dispatch-history.ts +152 -0
  274. package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
  275. package/src/resources/extensions/gsd/auto/loop.ts +4 -1
  276. package/src/resources/extensions/gsd/auto/orchestrator.ts +70 -46
  277. package/src/resources/extensions/gsd/auto/phases.ts +2 -2
  278. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -32
  279. package/src/resources/extensions/gsd/auto-dispatch.ts +38 -52
  280. package/src/resources/extensions/gsd/auto-model-selection.ts +25 -5
  281. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -8
  282. package/src/resources/extensions/gsd/auto-prompts.ts +118 -35
  283. package/src/resources/extensions/gsd/auto-start.ts +18 -17
  284. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  285. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +14 -21
  286. package/src/resources/extensions/gsd/auto-verification.ts +8 -26
  287. package/src/resources/extensions/gsd/auto-worktree.ts +30 -93
  288. package/src/resources/extensions/gsd/auto.ts +8 -15
  289. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -5
  290. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +23 -6
  291. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -0
  292. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +251 -47
  293. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +352 -84
  294. package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
  295. package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
  296. package/src/resources/extensions/gsd/browser-evidence.ts +18 -2
  297. package/src/resources/extensions/gsd/captures.ts +4 -6
  298. package/src/resources/extensions/gsd/consent-question.ts +416 -0
  299. package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
  300. package/src/resources/extensions/gsd/constants.ts +0 -3
  301. package/src/resources/extensions/gsd/crash-recovery.ts +3 -9
  302. package/src/resources/extensions/gsd/db/queries.ts +37 -0
  303. package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
  304. package/src/resources/extensions/gsd/doctor-environment.ts +2 -7
  305. package/src/resources/extensions/gsd/doctor-format.ts +12 -7
  306. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +13 -15
  307. package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
  308. package/src/resources/extensions/gsd/error-classifier.ts +11 -0
  309. package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
  310. package/src/resources/extensions/gsd/files.ts +33 -12
  311. package/src/resources/extensions/gsd/guidance.ts +217 -0
  312. package/src/resources/extensions/gsd/guided-flow.ts +16 -2
  313. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -0
  314. package/src/resources/extensions/gsd/mcp-filter.ts +2 -23
  315. package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
  316. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
  317. package/src/resources/extensions/gsd/migrate/safety.ts +4 -1
  318. package/src/resources/extensions/gsd/milestone-closeout.ts +13 -23
  319. package/src/resources/extensions/gsd/notification-store.ts +26 -3
  320. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +6 -4
  321. package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
  322. package/src/resources/extensions/gsd/paths.ts +33 -0
  323. package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
  324. package/src/resources/extensions/gsd/preferences-models.ts +12 -47
  325. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  326. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  327. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  328. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  329. package/src/resources/extensions/gsd/prompts/run-uat.md +6 -4
  330. package/src/resources/extensions/gsd/prompts/system.md +5 -2
  331. package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
  332. package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
  333. package/src/resources/extensions/gsd/publication.ts +122 -0
  334. package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
  335. package/src/resources/extensions/gsd/recovery-classification.ts +42 -96
  336. package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
  337. package/src/resources/extensions/gsd/state.ts +9 -21
  338. package/src/resources/extensions/gsd/stop-notice.ts +75 -0
  339. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +22 -0
  340. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +101 -26
  341. package/src/resources/extensions/gsd/tests/browser-automation-contract-fixture.ts +39 -0
  342. package/src/resources/extensions/gsd/tests/browser-contract.test.ts +44 -0
  343. package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
  344. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
  345. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +22 -0
  346. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
  347. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  348. package/src/resources/extensions/gsd/tests/consent-question.test.ts +336 -0
  349. package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
  350. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
  351. package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
  352. package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
  353. package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +2 -1
  354. package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
  355. package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
  356. package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
  357. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
  358. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +35 -1
  359. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
  360. package/src/resources/extensions/gsd/tests/guidance.test.ts +148 -0
  361. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +53 -11
  362. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +73 -58
  363. package/src/resources/extensions/gsd/tests/integration/gsd-integration-fixture.ts +80 -0
  364. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
  365. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
  366. package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
  367. package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
  368. package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
  369. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
  370. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +139 -0
  371. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
  372. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
  373. package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
  374. package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
  375. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +157 -0
  376. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +1 -0
  377. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
  378. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +76 -0
  379. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
  380. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
  381. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +112 -29
  382. package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
  383. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +67 -2
  384. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
  385. package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +44 -1
  386. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  387. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +2 -2
  388. package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
  389. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -1
  390. package/src/resources/extensions/gsd/tool-presentation-plan.ts +4 -4
  391. package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
  392. package/src/resources/extensions/gsd/tools/complete-slice.ts +20 -10
  393. package/src/resources/extensions/gsd/tools/exec-tool.ts +8 -7
  394. package/src/resources/extensions/gsd/tools/plan-slice.ts +12 -6
  395. package/src/resources/extensions/gsd/uat-policy.ts +62 -16
  396. package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
  397. package/src/resources/extensions/gsd/unit-context-composer.ts +111 -1
  398. package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
  399. package/src/resources/extensions/gsd/unit-registry.ts +412 -0
  400. package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
  401. package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
  402. package/src/resources/extensions/gsd/web-app-uat.ts +51 -8
  403. package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
  404. package/src/resources/extensions/gsd/worktree-git-recovery.ts +15 -9
  405. package/src/resources/extensions/gsd/worktree-lifecycle.ts +3 -8
  406. package/src/resources/extensions/gsd/worktree-root.ts +12 -0
  407. package/src/resources/extensions/gsd/worktree-session-state.ts +3 -5
  408. package/src/resources/extensions/search-the-web/native-search.ts +5 -3
  409. package/src/resources/extensions/shared/browser-contract.ts +66 -0
  410. package/src/resources/extensions/shared/gsd-browser-cli.ts +119 -5
  411. package/src/resources/shared/package.json +3 -0
  412. package/src/resources/skills/create-skill/references/executable-code.md +1 -1
  413. package/src/resources/skills/create-skill/workflows/add-reference.md +8 -3
  414. package/src/resources/skills/create-skill/workflows/add-script.md +4 -2
  415. package/src/resources/skills/create-skill/workflows/add-template.md +3 -1
  416. package/src/resources/skills/create-skill/workflows/add-workflow.md +8 -3
  417. package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
  418. package/src/resources/skills/create-skill/workflows/verify-skill.md +9 -4
  419. package/src/resources/skills/spike-wrap-up/SKILL.md +9 -9
  420. package/dist/resources/extensions/gsd/user-input-boundary.js +0 -218
  421. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -173
  422. package/src/resources/extensions/gsd/user-input-boundary.ts +0 -216
  423. /package/dist/web/standalone/.next/static/{C24pqUd-aru-l0Dp0gLZP → jmTLg6xZmAuq_LIqKOxrH}/_buildManifest.js +0 -0
  424. /package/dist/web/standalone/.next/static/{C24pqUd-aru-l0Dp0gLZP → jmTLg6xZmAuq_LIqKOxrH}/_ssgManifest.js +0 -0
@@ -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) {
@@ -10,7 +10,7 @@ import {
10
10
  import { parseMcpToolName, toMcpToolName } from "./mcp-tool-name.js";
11
11
  import { createToolSurfaceSnapshot, type ToolSurfaceSnapshot } from "./tool-surface-snapshot.js";
12
12
  import { uatTypeIncludesBrowser } from "./uat-policy.js";
13
- import { canonicalWorkflowSurfaceToolName } from "./workflow-tool-surface.js";
13
+ import { canonicalWorkflowToolName } from "./engine-hook-contract.js";
14
14
 
15
15
  export {
16
16
  RUN_UAT_BROWSER_TOOL_NAMES,
@@ -66,9 +66,9 @@ export const RUN_UAT_CLAUDE_NATIVE_TOOL_NAMES = [
66
66
  "Grep",
67
67
  ] as const;
68
68
 
69
- export function canonicalWorkflowToolName(toolName: string): string {
70
- return canonicalWorkflowSurfaceToolName(toolName);
71
- }
69
+ // Normalizer seam lives in engine-hook-contract.ts; re-exported here for
70
+ // existing presentation importers (uat-run.ts).
71
+ export { canonicalWorkflowToolName };
72
72
 
73
73
  export { parseMcpToolName } from "./mcp-tool-name.js";
74
74
 
@@ -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
+ }
@@ -26,10 +26,9 @@ import { gsdProjectionRoot, clearPathCache, resolveMilestoneFile } from "../path
26
26
  import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
27
27
  import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
28
28
  import { saveFile, clearParseCache } from "../files.js";
29
- import { getDeclaredUatType, shouldEscalateArtifactUatToBrowser } from "../uat-policy.js";
29
+ import { classifyUatContent, escalatesArtifactUatToBrowser } from "../uat-policy.js";
30
30
  import { invalidateStateCache } from "../state.js";
31
- import { renderRoadmapFromDb } from "../markdown-renderer.js";
32
- import { parseRoadmap } from "../parsers-legacy.js";
31
+ import { renderRoadmapFromDb, roadmapRenderMarksSliceDone } from "../markdown-renderer.js";
33
32
  import { isStaleWrite } from "../auto/turn-epoch.js";
34
33
  import { renderAllProjections } from "../workflow-projections.js";
35
34
  import { writeManifest } from "../workflow-manifest.js";
@@ -91,9 +90,11 @@ function hasCompleteSliceArtifactContract(basePath: string, milestoneId: string,
91
90
  join(gsdProjectionRoot(basePath), "milestones", milestoneId, `${milestoneId}-ROADMAP.md`);
92
91
  if (!existsSync(roadmapPath)) return false;
93
92
 
93
+ // Projection-completeness check (ADR-017): the DB has already recorded the
94
+ // duplicate completion; this only verifies the rendered markdown artifacts
95
+ // exist and reflect it, deciding whether re-rendering is needed.
94
96
  try {
95
- const roadmap = parseRoadmap(readFileSync(roadmapPath, "utf-8"));
96
- return roadmap.slices.some((slice) => slice.id === sliceId && slice.done);
97
+ return roadmapRenderMarksSliceDone(readFileSync(roadmapPath, "utf-8"), sliceId);
97
98
  } catch {
98
99
  return false;
99
100
  }
@@ -355,10 +356,18 @@ export async function handleCompleteSlice(
355
356
  // `npx playwright test` via gsd_uat_exec, and live-runtime/mixed/
356
357
  // browser-executable receive browser tools (UAT_MODE_POLICIES).
357
358
  const uatContent = params.uatContent || "";
358
- const declaredUatMode = getDeclaredUatType(uatContent);
359
- if (shouldEscalateArtifactUatToBrowser(uatContent)) {
359
+ const uatPolicy = classifyUatContent(uatContent);
360
+ if (escalatesArtifactUatToBrowser(uatPolicy)) {
361
+ // Distinguish an explicit artifact-driven declaration from a missing or
362
+ // unparseable one that merely *defaulted* to artifact-driven — telling an
363
+ // agent it "declared artifact-driven" when its declaration simply failed
364
+ // to parse sends it into a rewrite loop with the same unparseable format.
365
+ const staticOnlyClause = `which only runs static/file checks and would defer the browser work to a human`;
366
+ const modeClause = uatPolicy.modeDeclared
367
+ ? `declares "UAT mode: artifact-driven", ${staticOnlyClause}`
368
+ : `has no parseable UAT mode declaration in its "## UAT Type" section (the declaration must be a bullet exactly like "- UAT mode: browser-executable"), so it defaults to "artifact-driven", ${staticOnlyClause}`;
360
369
  return {
361
- error: `UAT requires browser verification (opening a page in a browser, navigating to a page or localhost, screenshots) but declares "UAT mode: artifact-driven", which only runs static/file checks and would defer the browser work to a human. Use a mode that actually verifies the UI: "browser-executable" (interactive browser tools), "runtime-executable" (a browser test command such as playwright), or a browser-inclusive "mixed"/"live-runtime". Re-author the UAT Type section and complete the slice again.`,
370
+ error: `UAT requires browser verification (opening a page in a browser, navigating to a page or localhost, screenshots) but ${modeClause}. Use a mode that actually verifies the UI: "browser-executable" (interactive browser tools), "runtime-executable" (a browser test command such as playwright), or a browser-inclusive "mixed"/"live-runtime". Re-author the UAT Type section and complete the slice again.`,
362
371
  };
363
372
  }
364
373
 
@@ -470,8 +479,9 @@ export async function handleCompleteSlice(
470
479
 
471
480
  const roadmap = await renderRoadmapFromDb(artifactBasePath, params.milestoneId);
472
481
  clearParseCache();
473
- const roadmapSlice = parseRoadmap(roadmap.content).slices.find((slice) => slice.id === params.sliceId);
474
- if (!roadmapSlice?.done) {
482
+ // Render verification (ADR-017): confirms the just-written projection
483
+ // reflects the DB completion; the DB row is already committed.
484
+ if (!roadmapRenderMarksSliceDone(roadmap.content, params.sliceId)) {
475
485
  throw new Error(`roadmap render did not mark ${params.milestoneId}/${params.sliceId} complete`);
476
486
  }
477
487
  } catch (renderErr) {
@@ -11,7 +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 { findWorktreeSegment } from "../worktree-root.js";
14
+ import { projectRootFromWorktreePath } from "../worktree-root.js";
15
15
  import { contextModeDisabledResult, type ToolExecutionResult } from "./context-mode-tool-result.js";
16
16
 
17
17
  export interface ExecToolParams {
@@ -202,12 +202,9 @@ function normalizeScanPath(value: string): string {
202
202
 
203
203
  function parseWorktreeBase(baseDir: string): { originalRoot: string; worktreeRoot: string } | null {
204
204
  const normalizedBase = normalizeScanPath(baseDir);
205
- const segment = findWorktreeSegment(normalizedBase);
206
- if (!segment || segment.gsdIdx <= 0) return null;
207
- return {
208
- originalRoot: normalizedBase.slice(0, segment.gsdIdx),
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 {
@@ -427,6 +424,7 @@ function formatResult(result: ExecSandboxResult): ToolExecutionResult {
427
424
  exit_code: result.exit_code,
428
425
  signal: result.signal,
429
426
  timed_out: result.timed_out,
427
+ force_resolved: result.force_resolved,
430
428
  duration_ms: result.duration_ms,
431
429
  stdout_bytes: result.stdout_bytes,
432
430
  stderr_bytes: result.stderr_bytes,
@@ -441,6 +439,9 @@ function formatResult(result: ExecSandboxResult): ToolExecutionResult {
441
439
  }
442
440
 
443
441
  function formatExit(result: ExecSandboxResult): string {
442
+ // force_resolved means a non-closing (D-state) child was force-resolved past its
443
+ // hard deadline rather than observed exiting; distinguish it from a clean timeout.
444
+ if (result.force_resolved) return "timeout(force-killed)";
444
445
  if (result.timed_out) return "timeout";
445
446
  if (result.signal) return `signal:${result.signal}`;
446
447
  if (result.exit_code === null) return "null";