gsd-pi 2.77.0-dev.eaa4973bc → 2.78.0-dev.aeeb2ca00

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 (545) hide show
  1. package/README.md +53 -17
  2. package/dist/claude-cli-check.js +46 -10
  3. package/dist/headless.js +49 -4
  4. package/dist/resource-loader.d.ts +40 -0
  5. package/dist/resource-loader.js +32 -13
  6. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  7. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  8. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  9. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  10. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  11. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +72 -16
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
  14. package/dist/resources/extensions/github-sync/templates.js +103 -0
  15. package/dist/resources/extensions/google-search/index.js +3 -2
  16. package/dist/resources/extensions/gsd/auto/loop.js +124 -2
  17. package/dist/resources/extensions/gsd/auto/phases.js +57 -39
  18. package/dist/resources/extensions/gsd/auto/session.js +6 -2
  19. package/dist/resources/extensions/gsd/auto-dispatch.js +142 -29
  20. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  21. package/dist/resources/extensions/gsd/auto-post-unit.js +150 -64
  22. package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
  23. package/dist/resources/extensions/gsd/auto-recovery.js +197 -48
  24. package/dist/resources/extensions/gsd/auto-start.js +107 -29
  25. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  26. package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
  27. package/dist/resources/extensions/gsd/auto.js +76 -21
  28. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +19 -1
  29. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  30. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
  31. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  32. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  33. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  34. package/dist/resources/extensions/gsd/component-types.js +69 -0
  35. package/dist/resources/extensions/gsd/context-store.js +23 -7
  36. package/dist/resources/extensions/gsd/detection.js +49 -1
  37. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -17
  38. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  39. package/dist/resources/extensions/gsd/forensics.js +106 -0
  40. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  41. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  42. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  43. package/dist/resources/extensions/gsd/git-service.js +126 -2
  44. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  45. package/dist/resources/extensions/gsd/guided-flow.js +39 -13
  46. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  47. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  48. package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
  49. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  50. package/dist/resources/extensions/gsd/model-router.js +6 -0
  51. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  52. package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
  53. package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
  54. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  55. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  56. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  57. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  58. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  59. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  60. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  61. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  62. package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
  63. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  64. package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
  65. package/dist/resources/extensions/gsd/state.js +69 -58
  66. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  67. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
  68. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  69. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  70. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
  71. package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
  72. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  73. package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
  74. package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
  75. package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
  76. package/dist/resources/extensions/gsd/uok/plan-v2.js +10 -4
  77. package/dist/resources/extensions/gsd/uok/writer.js +82 -0
  78. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  79. package/dist/resources/extensions/gsd/worktree-manager.js +85 -8
  80. package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
  81. package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
  82. package/dist/resources/extensions/mcp-client/index.js +3 -1
  83. package/dist/resources/extensions/ollama/index.js +5 -1
  84. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  85. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  86. package/dist/web/standalone/.next/BUILD_ID +1 -1
  87. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  88. package/dist/web/standalone/.next/build-manifest.json +2 -2
  89. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  90. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/index.html +1 -1
  108. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  115. package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
  116. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  117. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  119. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  120. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  121. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  122. package/package.json +2 -3
  123. package/packages/daemon/package.json +2 -2
  124. package/packages/daemon/src/logger.ts +4 -3
  125. package/packages/mcp-server/dist/server.d.ts +24 -0
  126. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  127. package/packages/mcp-server/dist/server.js +88 -87
  128. package/packages/mcp-server/dist/server.js.map +1 -1
  129. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  130. package/packages/mcp-server/dist/workflow-tools.js +15 -6
  131. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  132. package/packages/mcp-server/package.json +2 -2
  133. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  134. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  135. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  136. package/packages/mcp-server/src/server.ts +131 -105
  137. package/packages/mcp-server/src/workflow-tools.test.ts +85 -0
  138. package/packages/mcp-server/src/workflow-tools.ts +19 -6
  139. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  140. package/packages/native/package.json +2 -2
  141. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  142. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  143. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  144. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  145. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  146. package/packages/pi-agent-core/package.json +1 -1
  147. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  148. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  149. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  150. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  151. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  152. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  153. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  154. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  155. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  156. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  158. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  159. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  160. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  161. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  162. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  163. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  164. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  165. package/packages/pi-ai/dist/models.test.js +36 -11
  166. package/packages/pi-ai/dist/models.test.js.map +1 -1
  167. package/packages/pi-ai/package.json +1 -1
  168. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  169. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  170. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  171. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  172. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  173. package/packages/pi-ai/src/models.test.ts +48 -11
  174. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  175. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  176. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  178. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  180. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  181. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  182. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  184. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  186. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  188. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  190. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  193. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  195. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  197. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  198. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  199. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  201. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  202. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  203. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  204. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  205. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  206. package/packages/pi-coding-agent/dist/index.js +1 -0
  207. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  211. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  212. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
  214. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  216. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  217. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  218. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  219. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  221. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  224. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  226. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  227. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  228. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  229. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  230. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  231. package/packages/pi-coding-agent/package.json +1 -1
  232. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  233. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  234. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  235. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  236. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  237. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  238. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  239. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  240. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  241. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  242. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  243. package/packages/pi-coding-agent/src/index.ts +1 -0
  244. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
  245. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  246. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
  247. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  248. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  249. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  250. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  251. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  252. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  253. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  254. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  255. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  256. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  257. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +37 -11
  258. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  259. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  260. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  261. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  262. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  263. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  264. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  265. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  266. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  267. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  268. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  269. package/packages/pi-tui/dist/components/editor.js +19 -0
  270. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  271. package/packages/pi-tui/dist/components/image.test.js +6 -5
  272. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  273. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  274. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  275. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  276. package/packages/pi-tui/package.json +1 -1
  277. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  278. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  279. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +42 -11
  280. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  281. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  282. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  283. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  284. package/packages/pi-tui/src/components/editor.ts +22 -0
  285. package/packages/pi-tui/src/components/image.test.ts +10 -5
  286. package/packages/pi-tui/src/editor-component.ts +3 -0
  287. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  288. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  289. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  290. package/packages/rpc-client/package.json +1 -1
  291. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  292. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  293. package/pkg/package.json +1 -1
  294. package/scripts/install.js +15 -1
  295. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  296. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  297. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  298. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  299. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  300. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  301. package/src/resources/extensions/claude-code-cli/readiness.ts +75 -16
  302. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
  303. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
  304. package/src/resources/extensions/github-sync/templates.ts +151 -0
  305. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  306. package/src/resources/extensions/github-sync/tests/templates.test.ts +92 -1
  307. package/src/resources/extensions/google-search/index.ts +3 -2
  308. package/src/resources/extensions/gsd/auto/loop.ts +142 -2
  309. package/src/resources/extensions/gsd/auto/phases.ts +62 -38
  310. package/src/resources/extensions/gsd/auto/session.ts +7 -2
  311. package/src/resources/extensions/gsd/auto-dispatch.ts +156 -29
  312. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  313. package/src/resources/extensions/gsd/auto-post-unit.ts +163 -73
  314. package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
  315. package/src/resources/extensions/gsd/auto-recovery.ts +230 -51
  316. package/src/resources/extensions/gsd/auto-start.ts +127 -9
  317. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  318. package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
  319. package/src/resources/extensions/gsd/auto.ts +90 -23
  320. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +20 -1
  321. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  322. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
  323. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  324. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  325. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  326. package/src/resources/extensions/gsd/component-types.ts +362 -0
  327. package/src/resources/extensions/gsd/context-store.ts +25 -8
  328. package/src/resources/extensions/gsd/detection.ts +58 -1
  329. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -20
  330. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  331. package/src/resources/extensions/gsd/forensics.ts +118 -1
  332. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  333. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  334. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  335. package/src/resources/extensions/gsd/git-service.ts +149 -2
  336. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  337. package/src/resources/extensions/gsd/guided-flow.ts +57 -14
  338. package/src/resources/extensions/gsd/journal.ts +11 -1
  339. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  340. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  341. package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
  342. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  343. package/src/resources/extensions/gsd/model-router.ts +6 -0
  344. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  345. package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
  346. package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
  347. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  348. package/src/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  349. package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  350. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  351. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  352. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  353. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  354. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  355. package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
  356. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  357. package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
  358. package/src/resources/extensions/gsd/state.ts +76 -66
  359. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  360. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  361. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
  362. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  363. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  364. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +133 -292
  365. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  366. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  367. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  368. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  369. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
  370. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
  371. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
  372. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
  373. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  374. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
  375. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
  376. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
  377. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
  378. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  379. package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
  380. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  381. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  382. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  383. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
  384. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  385. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  386. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  387. package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
  388. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
  389. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  390. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +159 -0
  391. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
  392. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  393. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  394. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  395. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -4
  396. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
  397. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
  398. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +5 -0
  399. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
  400. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
  401. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  402. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
  403. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
  404. package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
  405. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  406. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
  407. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  408. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  409. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +14 -4
  410. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
  411. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  412. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
  413. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  414. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  415. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  416. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  417. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  418. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  419. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
  420. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  421. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +64 -0
  422. package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
  423. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  424. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  425. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
  426. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  427. package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
  428. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  429. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  430. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
  431. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  432. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
  433. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
  434. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  435. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  436. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
  437. package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
  438. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
  439. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
  440. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
  441. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
  442. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
  443. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  444. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  445. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  446. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  447. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  448. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
  449. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  450. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  451. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  452. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
  453. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  454. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  455. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  456. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  457. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
  458. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
  459. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  460. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
  461. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
  462. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +29 -5
  463. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
  464. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
  465. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  466. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
  467. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  468. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  469. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
  470. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
  471. package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
  472. package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  473. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  474. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  475. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  476. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  477. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
  478. package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
  479. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  480. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  481. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
  482. package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
  483. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +19 -2
  484. package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
  485. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -0
  486. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
  487. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  488. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  489. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  490. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  491. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  492. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  493. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  494. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  495. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
  496. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  497. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  498. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  499. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  500. package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
  501. package/src/resources/extensions/gsd/types.ts +3 -3
  502. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  503. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  504. package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
  505. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
  506. package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
  507. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  508. package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
  509. package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
  510. package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
  511. package/src/resources/extensions/gsd/uok/plan-v2.ts +13 -5
  512. package/src/resources/extensions/gsd/uok/writer.ts +113 -0
  513. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  514. package/src/resources/extensions/gsd/worktree-manager.ts +108 -7
  515. package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
  516. package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
  517. package/src/resources/extensions/mcp-client/index.ts +3 -1
  518. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  519. package/src/resources/extensions/ollama/index.ts +5 -1
  520. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  521. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  522. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  523. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  524. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  525. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  526. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  527. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  528. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  529. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  530. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  531. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
  532. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  533. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  534. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  535. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  536. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  537. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  538. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
  539. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  540. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  541. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  542. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
  543. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
  544. /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_buildManifest.js +0 -0
  545. /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_ssgManifest.js +0 -0
@@ -0,0 +1,742 @@
1
+ /**
2
+ * Regression coverage for the model-policy dispatch bugs (#4959, #4681, #4850).
3
+ *
4
+ * The five tests here pin the four fix layers documented in the RCA on #4959:
5
+ *
6
+ * 1. Vacuous-truth guard: with an empty unit-required tool subset and an
7
+ * otherwise-permitted model, dispatch must succeed. Without this test,
8
+ * an over-aggressive Change 1 (e.g. always denying) would still pass any
9
+ * "no longer throws" assertion trivially.
10
+ * 2. Cross-unit poisoning: per-unit narrowing at the bottom of
11
+ * `selectAndApplyModel` must NOT bleed into the next unit's policy
12
+ * evaluation. The baseline-restore path (Change 2) must restore the
13
+ * pre-dispatch active-tool set before policy runs.
14
+ * 3. Genuinely-impossible negative: when the workflow REQUIRES a tool no
15
+ * candidate model can carry, dispatch must throw
16
+ * `ModelPolicyDispatchBlockedError` — proving Change 1 didn't accidentally
17
+ * remove gating, and Change 3 wired the typed error.
18
+ * 4. Restore happened: assert call ordering on a recording fake — the
19
+ * baseline `setActiveTools` call must precede the next `selectAndApplyModel`
20
+ * reading the active set.
21
+ * 5. Error message carries reason: the throw must include the per-model
22
+ * `tool policy denied (...)` reason fragment from `applyModelPolicyFilter`,
23
+ * so users can act on the failure without digging through audit events.
24
+ */
25
+
26
+ import test from "node:test";
27
+ import assert from "node:assert/strict";
28
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
29
+ import { join } from "node:path";
30
+ import { tmpdir } from "node:os";
31
+
32
+ import {
33
+ selectAndApplyModel,
34
+ ModelPolicyDispatchBlockedError,
35
+ clearToolBaseline,
36
+ } from "../auto-model-selection.js";
37
+ import {
38
+ registerToolCompatibility,
39
+ resetToolCompatibilityRegistry,
40
+ } from "@gsd/pi-coding-agent";
41
+
42
+ function makeTempProject(): { dir: string; cleanup: () => void; restoreEnv: () => void } {
43
+ const originalCwd = process.cwd();
44
+ const originalGsdHome = process.env.GSD_HOME;
45
+ const dir = mkdtempSync(join(tmpdir(), "gsd-policy-poison-"));
46
+ const home = mkdtempSync(join(tmpdir(), "gsd-policy-home-"));
47
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
48
+ // Empty PREFERENCES so default uok.model_policy.enabled = true applies.
49
+ writeFileSync(join(dir, ".gsd", "PREFERENCES.md"), "---\n---\n", "utf-8");
50
+ process.env.GSD_HOME = home;
51
+ process.chdir(dir);
52
+ return {
53
+ dir,
54
+ cleanup: () => {
55
+ rmSync(dir, { recursive: true, force: true });
56
+ rmSync(home, { recursive: true, force: true });
57
+ },
58
+ restoreEnv: () => {
59
+ process.chdir(originalCwd);
60
+ if (originalGsdHome === undefined) delete process.env.GSD_HOME;
61
+ else process.env.GSD_HOME = originalGsdHome;
62
+ },
63
+ };
64
+ }
65
+
66
+ interface RecordingPi {
67
+ setModel: (m: { provider: string; id: string }) => Promise<boolean>;
68
+ emitBeforeModelSelect: () => Promise<undefined>;
69
+ getActiveTools: () => string[];
70
+ emitAdjustToolSet: () => Promise<undefined>;
71
+ setActiveTools: (names: string[]) => void;
72
+ setThinkingLevel: () => void;
73
+ __calls: Array<{ kind: string; payload: unknown }>;
74
+ __activeTools: string[];
75
+ }
76
+
77
+ function makeRecordingPi(initialActiveTools: string[]): RecordingPi {
78
+ const calls: Array<{ kind: string; payload: unknown }> = [];
79
+ let active = [...initialActiveTools];
80
+ return {
81
+ __calls: calls,
82
+ get __activeTools() { return active; },
83
+ setModel: async (m) => {
84
+ calls.push({ kind: "setModel", payload: `${m.provider}/${m.id}` });
85
+ return true;
86
+ },
87
+ emitBeforeModelSelect: async () => {
88
+ calls.push({ kind: "emitBeforeModelSelect", payload: null });
89
+ return undefined;
90
+ },
91
+ getActiveTools: () => {
92
+ calls.push({ kind: "getActiveTools", payload: [...active] });
93
+ return [...active];
94
+ },
95
+ emitAdjustToolSet: async () => {
96
+ calls.push({ kind: "emitAdjustToolSet", payload: null });
97
+ return undefined;
98
+ },
99
+ setActiveTools: (names) => {
100
+ active = [...names];
101
+ calls.push({ kind: "setActiveTools", payload: [...names] });
102
+ },
103
+ setThinkingLevel: () => {},
104
+ } as RecordingPi;
105
+ }
106
+
107
+ function makeCtx(availableModels: Array<{ id: string; provider: string; api: string }>) {
108
+ return {
109
+ modelRegistry: {
110
+ getAvailable: () => availableModels,
111
+ getProviderAuthMode: () => "apiKey",
112
+ },
113
+ sessionManager: { getSessionId: () => "test-session" },
114
+ ui: { notify: () => {} },
115
+ model: { provider: availableModels[0]?.provider, id: availableModels[0]?.id, api: availableModels[0]?.api },
116
+ } as any;
117
+ }
118
+
119
+ // ─── 1. Vacuous-truth guard ──────────────────────────────────────────────────
120
+ //
121
+ // Two scenarios pin the empty-requiredTools branch and a permitted-tool branch.
122
+ // Without the empty-list scenario, a regression that mishandles `requiredTools = []`
123
+ // (e.g. by treating an empty array as "deny all" or by null-derefing the helper
124
+ // return) would still pass.
125
+
126
+ test("vacuous-truth (a): unit type with empty workflow-required tools → dispatch succeeds", async () => {
127
+ const env = makeTempProject();
128
+ try {
129
+ // `refine-slice` is not in the getRequiredWorkflowToolsForAutoUnit switch
130
+ // → returns []. Exercises the empty-requiredTools branch in
131
+ // applyModelPolicyFilter (CodeRabbit Minor: existing test used
132
+ // gate-evaluate which has non-empty required tools and never hit this path).
133
+ //
134
+ // PREFERENCES with tier_models is required so resolvePreferredModelConfig
135
+ // returns a non-undefined modelConfig — only then does selectAndApplyModel
136
+ // run the policy filter we want to exercise.
137
+ writeFileSync(
138
+ join(env.dir, ".gsd", "PREFERENCES.md"),
139
+ ["---", "dynamic_routing:", " enabled: true", " tier_models:", " heavy: anthropic/claude-sonnet-4-6", "---"].join("\n"),
140
+ "utf-8",
141
+ );
142
+ const availableModels = [
143
+ { id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
144
+ ];
145
+ const pi = makeRecordingPi([]);
146
+ clearToolBaseline(pi as unknown as object);
147
+
148
+ const result = await selectAndApplyModel(
149
+ makeCtx(availableModels),
150
+ pi as any,
151
+ "refine-slice",
152
+ "x1",
153
+ env.dir,
154
+ undefined,
155
+ false,
156
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
157
+ undefined,
158
+ true,
159
+ );
160
+
161
+ assert.equal(result.appliedModel?.id, "claude-sonnet-4-6", "empty requiredTools must not deny dispatch");
162
+ const setModelCalls = pi.__calls.filter(c => c.kind === "setModel");
163
+ assert.equal(setModelCalls.length, 1, "setModel should have been called exactly once");
164
+ } finally {
165
+ env.restoreEnv();
166
+ env.cleanup();
167
+ }
168
+ });
169
+
170
+ test("vacuous-truth (b): non-empty workflow tool requirement that the model carries → dispatch succeeds", async () => {
171
+ const env = makeTempProject();
172
+ try {
173
+ // gate-evaluate has tool requirement ["gsd_save_gate_result"]; if the
174
+ // model's API can carry it, policy must still allow dispatch. Counter-test
175
+ // to (a): proves the path with a non-empty requirement isn't denying
176
+ // legitimate dispatches.
177
+ const availableModels = [
178
+ { id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
179
+ ];
180
+ const pi = makeRecordingPi(["gsd_save_gate_result"]);
181
+ clearToolBaseline(pi as unknown as object);
182
+
183
+ const result = await selectAndApplyModel(
184
+ makeCtx(availableModels),
185
+ pi as any,
186
+ "gate-evaluate",
187
+ "g1",
188
+ env.dir,
189
+ undefined,
190
+ false,
191
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
192
+ undefined,
193
+ true,
194
+ );
195
+
196
+ assert.equal(result.appliedModel?.id, "claude-sonnet-4-6", "compat-required dispatch must succeed");
197
+ const setModelCalls = pi.__calls.filter(c => c.kind === "setModel");
198
+ assert.equal(setModelCalls.length, 1, "setModel should have been called exactly once");
199
+ } finally {
200
+ env.restoreEnv();
201
+ env.cleanup();
202
+ }
203
+ });
204
+
205
+ // ─── 2. Cross-unit poisoning ─────────────────────────────────────────────────
206
+ test("cross-unit poisoning: prior unit narrowing must not deny next unit's eligible model", async () => {
207
+ const env = makeTempProject();
208
+ try {
209
+ // Unit-N runs against an `openai-completions` provider that strips a tool
210
+ // (e.g. "thinking_partner") via adjustToolSet's hard filter. Without the
211
+ // baseline-restore (Change 2), pi.getActiveTools() afterward is missing
212
+ // that tool, but if we used it as the policy required-set we'd erroneously
213
+ // deny the next unit. With Change 1+2, policy uses the workflow-required
214
+ // subset (NOT the live snapshot), and baseline restoration re-seeds the
215
+ // active set before the next unit.
216
+ const availableModels = [
217
+ { id: "openai-narrow", provider: "openai", api: "openai-completions" },
218
+ { id: "claude-wide", provider: "anthropic", api: "anthropic-messages" },
219
+ ];
220
+ // The baseline contains a synthetic "thinking_partner" that openai-completions
221
+ // does not support.
222
+ const pi = makeRecordingPi(["gsd_save_gate_result", "thinking_partner"]);
223
+ clearToolBaseline(pi as unknown as object);
224
+
225
+ // Unit-N: dispatch on openai/openai-narrow. Soft adjustToolSet will narrow
226
+ // the active set, simulating production poisoning.
227
+ await selectAndApplyModel(
228
+ makeCtx(availableModels),
229
+ pi as any,
230
+ "gate-evaluate",
231
+ "n",
232
+ env.dir,
233
+ undefined,
234
+ false,
235
+ { provider: "openai", id: "openai-narrow" },
236
+ undefined,
237
+ true,
238
+ );
239
+
240
+ const setModelCallsAfterUnitN = pi.__calls.filter(c => c.kind === "setModel").length;
241
+ assert.ok(setModelCallsAfterUnitN >= 1, "unit-N should have dispatched");
242
+
243
+ // Unit-N+1: now dispatch with claude-wide. If active-tool snapshot were
244
+ // still the policy required-set, the previous narrowing wouldn't matter
245
+ // (anthropic-messages can carry both tools), so we instead simulate the
246
+ // 4959 path: a second unit whose workflow requires "gsd_save_gate_result"
247
+ // (small) — must succeed reaching pi.setModel for claude-wide.
248
+ const beforeCount = pi.__calls.filter(c => c.kind === "setModel").length;
249
+ await selectAndApplyModel(
250
+ makeCtx(availableModels),
251
+ pi as any,
252
+ "gate-evaluate",
253
+ "n+1",
254
+ env.dir,
255
+ undefined,
256
+ false,
257
+ { provider: "anthropic", id: "claude-wide" },
258
+ undefined,
259
+ true,
260
+ );
261
+ const afterCount = pi.__calls.filter(c => c.kind === "setModel").length;
262
+ assert.ok(afterCount > beforeCount, "unit-N+1 should reach pi.setModel — cross-unit narrowing must not block dispatch");
263
+ } finally {
264
+ env.restoreEnv();
265
+ env.cleanup();
266
+ }
267
+ });
268
+
269
+ // ─── 3a. Genuinely-impossible: tool-compatibility denial path ────────────────
270
+ //
271
+ // Exercises the real `getRequiredWorkflowToolsForAutoUnit` →
272
+ // `filterToolsForProvider` path that #4959 was about (CodeRabbit Minor:
273
+ // existing 3b test used cross-provider denial which never hit this path).
274
+ // Registers `gsd_plan_slice` as `producesImages: true`, then offers only an
275
+ // `ollama-chat` candidate (which has `imageToolResults: false`) — the
276
+ // workflow-required tool is incompatible with the candidate's API, so the
277
+ // policy filter denies the model with a `tool policy denied (...)` reason.
278
+ test("genuinely-impossible (a): workflow tool incompatible with candidate API → typed error names tool + api", async () => {
279
+ const env = makeTempProject();
280
+ try {
281
+ // Register the workflow tool as image-producing for the duration of this
282
+ // test. afterEach() resets the registry below.
283
+ registerToolCompatibility("gsd_plan_slice", { producesImages: true });
284
+
285
+ // PREFERENCES needs tier_models so resolvePreferredModelConfig returns a
286
+ // non-undefined modelConfig — without that, selectAndApplyModel skips the
287
+ // entire policy block and we never reach the tool-compat denial path.
288
+ writeFileSync(
289
+ join(env.dir, ".gsd", "PREFERENCES.md"),
290
+ ["---", "dynamic_routing:", " enabled: true", " tier_models:", " heavy: ollama/ollama-llama-3", "---"].join("\n"),
291
+ "utf-8",
292
+ );
293
+
294
+ const availableModels = [
295
+ { id: "ollama-llama-3", provider: "ollama", api: "ollama-chat" },
296
+ ];
297
+ const pi = makeRecordingPi(["gsd_plan_slice"]);
298
+ clearToolBaseline(pi as unknown as object);
299
+
300
+ const ctx = makeCtx(availableModels);
301
+ // Same provider as candidate so the cross-provider gate doesn't fire —
302
+ // we want this denial to come from tool-compatibility, not provider mismatch.
303
+ ctx.model = { provider: "ollama", id: "ollama-llama-3", api: "ollama-chat" };
304
+
305
+ let thrown: unknown;
306
+ try {
307
+ await selectAndApplyModel(
308
+ ctx,
309
+ pi as any,
310
+ "plan-slice",
311
+ "s1",
312
+ env.dir,
313
+ undefined,
314
+ false,
315
+ { provider: "ollama", id: "ollama-llama-3" },
316
+ undefined,
317
+ true,
318
+ );
319
+ } catch (e) {
320
+ thrown = e;
321
+ }
322
+
323
+ assert.ok(thrown instanceof ModelPolicyDispatchBlockedError, "should throw ModelPolicyDispatchBlockedError");
324
+ const err = thrown as ModelPolicyDispatchBlockedError;
325
+ assert.equal(err.unitType, "plan-slice");
326
+ assert.match(err.message, /tool policy denied/, "throw must surface the tool-compatibility deny reason");
327
+ assert.match(err.message, /gsd_plan_slice/, "throw must name the incompatible tool");
328
+ assert.match(err.message, /ollama-chat/, "throw must name the api for which the tool was filtered");
329
+ } finally {
330
+ resetToolCompatibilityRegistry();
331
+ env.restoreEnv();
332
+ env.cleanup();
333
+ }
334
+ });
335
+
336
+ // ─── 3b. Genuinely-impossible: cross-provider denial path ────────────────────
337
+ test("genuinely-impossible (b): cross-provider routing disabled + provider mismatch → typed error", async () => {
338
+ const env = makeTempProject();
339
+ try {
340
+ // Use plan-slice (workflow-required: ["gsd_plan_slice"]) but pretend no
341
+ // candidate model can carry it. The simplest way: provide a model whose
342
+ // api is a fictitious "no-tools" string — `filterToolsForProvider` returns
343
+ // every tool as filtered for an unknown api with toolCalling=false, OR we
344
+ // can pick a real api that also denies the tool. We use an api that
345
+ // exists but has known incompatibility — no such case is portable, so we
346
+ // fall back to a model whose api is recognized to deny `gsd_plan_slice`.
347
+ //
348
+ // Pragmatic approach: monkey the policy via `allowCrossProvider=false` +
349
+ // a single candidate model on a *different* provider than current, which
350
+ // makes EVERY candidate denied for cross-provider-routing reasons. This
351
+ // exercises the same throw path with a deterministic deny reason.
352
+ const availableModels = [
353
+ { id: "other-model", provider: "other-provider", api: "anthropic-messages" },
354
+ ];
355
+ const pi = makeRecordingPi([]);
356
+ clearToolBaseline(pi as unknown as object);
357
+
358
+ const ctx = makeCtx(availableModels);
359
+ // currentProvider mismatches → cross-provider denial when disabled.
360
+ ctx.model = { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" };
361
+
362
+ // Set dynamic_routing.cross_provider=false via PREFERENCES so the policy
363
+ // disables cross-provider routing.
364
+ writeFileSync(
365
+ join(env.dir, ".gsd", "PREFERENCES.md"),
366
+ ["---", "dynamic_routing:", " enabled: true", " cross_provider: false", " tier_models:", " heavy: other-provider/other-model", "---"].join("\n"),
367
+ "utf-8",
368
+ );
369
+
370
+ let thrown: unknown;
371
+ try {
372
+ await selectAndApplyModel(
373
+ ctx,
374
+ pi as any,
375
+ "plan-slice",
376
+ "s1",
377
+ env.dir,
378
+ undefined,
379
+ false,
380
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
381
+ undefined,
382
+ true,
383
+ );
384
+ } catch (e) {
385
+ thrown = e;
386
+ }
387
+
388
+ assert.ok(thrown instanceof ModelPolicyDispatchBlockedError, "should throw ModelPolicyDispatchBlockedError");
389
+ const err = thrown as ModelPolicyDispatchBlockedError;
390
+ assert.equal(err.unitType, "plan-slice");
391
+ assert.equal(err.unitId, "s1");
392
+ assert.ok(err.reasons.length > 0, "deny reasons should be captured");
393
+ } finally {
394
+ env.restoreEnv();
395
+ env.cleanup();
396
+ }
397
+ });
398
+
399
+ // ─── 4. Restore happened ─────────────────────────────────────────────────────
400
+ test("restore baseline: setActiveTools(BASELINE) called between units before next dispatch", async () => {
401
+ const env = makeTempProject();
402
+ try {
403
+ const availableModels = [
404
+ { id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
405
+ ];
406
+ const baselineTools = ["gsd_save_gate_result", "tool_a", "tool_b"];
407
+ const pi = makeRecordingPi(baselineTools);
408
+ clearToolBaseline(pi as unknown as object);
409
+
410
+ // First call captures the baseline.
411
+ await selectAndApplyModel(
412
+ makeCtx(availableModels),
413
+ pi as any,
414
+ "gate-evaluate",
415
+ "u1",
416
+ env.dir,
417
+ undefined,
418
+ false,
419
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
420
+ undefined,
421
+ true,
422
+ );
423
+
424
+ // Simulate a downstream caller narrowing the tool set (post-unit poisoning).
425
+ pi.setActiveTools(["gsd_save_gate_result"]);
426
+ const callsBeforeU2 = pi.__calls.length;
427
+
428
+ // Second call should restore the baseline before reading anything.
429
+ await selectAndApplyModel(
430
+ makeCtx(availableModels),
431
+ pi as any,
432
+ "gate-evaluate",
433
+ "u2",
434
+ env.dir,
435
+ undefined,
436
+ false,
437
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
438
+ undefined,
439
+ true,
440
+ );
441
+
442
+ const u2Calls = pi.__calls.slice(callsBeforeU2);
443
+ const restoreCall = u2Calls.find(
444
+ c => c.kind === "setActiveTools"
445
+ && Array.isArray(c.payload)
446
+ && (c.payload as string[]).length === baselineTools.length
447
+ && baselineTools.every(t => (c.payload as string[]).includes(t)),
448
+ );
449
+ assert.ok(restoreCall, "setActiveTools(BASELINE) must be called during u2's selectAndApplyModel before dispatch");
450
+
451
+ const restoreIdx = u2Calls.indexOf(restoreCall!);
452
+ const setModelIdx = u2Calls.findIndex(c => c.kind === "setModel");
453
+ assert.ok(setModelIdx > restoreIdx, "baseline restore must precede setModel dispatch");
454
+ } finally {
455
+ env.restoreEnv();
456
+ env.cleanup();
457
+ }
458
+ });
459
+
460
+ // ─── 5. Error message carries reason ─────────────────────────────────────────
461
+ test("error carries deny reason fragment from applyModelPolicyFilter", async () => {
462
+ const env = makeTempProject();
463
+ try {
464
+ writeFileSync(
465
+ join(env.dir, ".gsd", "PREFERENCES.md"),
466
+ ["---", "dynamic_routing:", " enabled: true", " cross_provider: false", " tier_models:", " heavy: other-provider/other-model", "---"].join("\n"),
467
+ "utf-8",
468
+ );
469
+
470
+ const availableModels = [
471
+ { id: "other-model", provider: "other-provider", api: "anthropic-messages" },
472
+ ];
473
+ const pi = makeRecordingPi([]);
474
+ clearToolBaseline(pi as unknown as object);
475
+
476
+ const ctx = makeCtx(availableModels);
477
+ ctx.model = { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" };
478
+
479
+ let thrown: Error | undefined;
480
+ try {
481
+ await selectAndApplyModel(
482
+ ctx,
483
+ pi as any,
484
+ "plan-slice",
485
+ "s1",
486
+ env.dir,
487
+ undefined,
488
+ false,
489
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
490
+ undefined,
491
+ true,
492
+ );
493
+ } catch (e) {
494
+ thrown = e as Error;
495
+ }
496
+
497
+ assert.ok(thrown, "should throw");
498
+ // The cross-provider denial path produces:
499
+ // "cross-provider routing disabled (other-provider != anthropic)"
500
+ assert.match(
501
+ thrown!.message,
502
+ /cross-provider routing disabled/,
503
+ "thrown error message should include the per-model deny reason",
504
+ );
505
+ assert.match(thrown!.message, /other-provider\/other-model/, "should name the rejected model");
506
+ } finally {
507
+ env.restoreEnv();
508
+ env.cleanup();
509
+ }
510
+ });
511
+
512
+ // ─── 6. Lifecycle: clearToolBaseline forces recapture (CodeRabbit Major) ─────
513
+ //
514
+ // The WeakMap baseline is keyed per `pi` instance, but auto sessions are NOT
515
+ // 1:1 with `pi` instances — a single `pi` can host multiple `/gsd auto` runs
516
+ // separated by stops, manual tool edits, or extension toggles. Without
517
+ // `clearToolBaseline(pi)` at session boundaries, the SECOND auto run on the
518
+ // same `pi` would silently restore the FIRST run's snapshot and undo whatever
519
+ // tool changes the user made between sessions. This test pins the contract
520
+ // that `clearToolBaseline` causes the next dispatch to RECAPTURE from the
521
+ // live active set rather than restoring the prior snapshot.
522
+ test("lifecycle: clearToolBaseline forces recapture; subsequent runs respect intervening tool edits", async () => {
523
+ const env = makeTempProject();
524
+ try {
525
+ const availableModels = [
526
+ { id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
527
+ ];
528
+ const pi = makeRecordingPi(["A", "B", "C"]);
529
+ clearToolBaseline(pi as unknown as object);
530
+
531
+ // ── Run 1: captures baseline [A, B, C] ──
532
+ await selectAndApplyModel(
533
+ makeCtx(availableModels),
534
+ pi as any,
535
+ "gate-evaluate",
536
+ "u1",
537
+ env.dir,
538
+ undefined,
539
+ false,
540
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
541
+ undefined,
542
+ true,
543
+ );
544
+
545
+ // ── Simulate `/gsd auto` stop + intervening user tool edit ──
546
+ // (auto.ts calls clearToolBaseline in stopAuto; the user then mutates
547
+ // tools while auto is paused.)
548
+ clearToolBaseline(pi as unknown as object);
549
+ pi.setActiveTools(["A", "B"]); // user removed C between sessions
550
+
551
+ // ── Run 2: must capture [A, B] as the NEW baseline, not restore [A, B, C] ──
552
+ const callsBeforeU2 = pi.__calls.length;
553
+ await selectAndApplyModel(
554
+ makeCtx(availableModels),
555
+ pi as any,
556
+ "gate-evaluate",
557
+ "u2",
558
+ env.dir,
559
+ undefined,
560
+ false,
561
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
562
+ undefined,
563
+ true,
564
+ );
565
+ const u2Calls = pi.__calls.slice(callsBeforeU2);
566
+ // No setActiveTools(["A", "B", "C"]) call should appear during u2 — that
567
+ // would be the bug (restoring the run-1 snapshot over the user's edit).
568
+ const staleRestore = u2Calls.find(
569
+ c => c.kind === "setActiveTools"
570
+ && Array.isArray(c.payload)
571
+ && (c.payload as string[]).includes("C"),
572
+ );
573
+ assert.equal(
574
+ staleRestore,
575
+ undefined,
576
+ "after clearToolBaseline, run 2 must NOT restore the run-1 snapshot containing tool C",
577
+ );
578
+
579
+ // ── Run 3 (no clear): mutate to [A], expect restore to [A, B] (run-2 baseline) ──
580
+ pi.setActiveTools(["A"]);
581
+ const callsBeforeU3 = pi.__calls.length;
582
+ await selectAndApplyModel(
583
+ makeCtx(availableModels),
584
+ pi as any,
585
+ "gate-evaluate",
586
+ "u3",
587
+ env.dir,
588
+ undefined,
589
+ false,
590
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
591
+ undefined,
592
+ true,
593
+ );
594
+ const u3Calls = pi.__calls.slice(callsBeforeU3);
595
+ const restoreToRun2Baseline = u3Calls.find(
596
+ c => c.kind === "setActiveTools"
597
+ && Array.isArray(c.payload)
598
+ && (c.payload as string[]).length === 2
599
+ && (c.payload as string[]).includes("A")
600
+ && (c.payload as string[]).includes("B")
601
+ && !(c.payload as string[]).includes("C"),
602
+ );
603
+ assert.ok(
604
+ restoreToRun2Baseline,
605
+ "run 3 must restore the run-2 baseline [A, B] — proves the recaptured baseline is in use, not the run-1 snapshot",
606
+ );
607
+ } finally {
608
+ env.restoreEnv();
609
+ env.cleanup();
610
+ }
611
+ });
612
+
613
+ // ─── 7. Cross-mode isolation (#4965) ─────────────────────────────────────────
614
+ //
615
+ // `selectAndApplyModel` is called from two places: auto-mode (`isAutoMode=true`,
616
+ // from auto/phases.ts) and guided-flow (`isAutoMode=false`, from guided-flow.ts).
617
+ // The baseline lifecycle (clearToolBaseline) is owned by startAuto/stopAuto —
618
+ // guided-flow has no equivalent clear hook. If `restoreToolBaseline` ran
619
+ // unconditionally, an interactive guided-flow dispatch on a `pi` that previously
620
+ // hosted an auto session would resurrect the auto-era baseline and silently
621
+ // overwrite any user tool edits made between the auto and guided dispatches.
622
+ // Therefore the restore is gated by `isAutoMode`. Guided-flow has its own
623
+ // narrow/restore discipline via discuss-tool-scoping at guided-flow.ts:587-622.
624
+
625
+ test("cross-mode (#4965): isAutoMode=false does NOT restore baseline even when one is recorded", async () => {
626
+ const env = makeTempProject();
627
+ try {
628
+ const availableModels = [
629
+ { id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
630
+ ];
631
+ const baselineTools = ["gsd_save_gate_result", "tool_a", "tool_b"];
632
+ const pi = makeRecordingPi(baselineTools);
633
+ clearToolBaseline(pi as unknown as object);
634
+
635
+ // ── Step 1: auto-mode call captures baseline [A, B, C] ──
636
+ await selectAndApplyModel(
637
+ makeCtx(availableModels),
638
+ pi as any,
639
+ "gate-evaluate",
640
+ "u-auto",
641
+ env.dir,
642
+ undefined,
643
+ false,
644
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
645
+ undefined,
646
+ /* isAutoMode */ true,
647
+ );
648
+
649
+ // ── Step 2: simulate user tool edit between auto and guided dispatches ──
650
+ pi.setActiveTools(["only_user_kept_tool"]);
651
+ const callsBeforeGuided = pi.__calls.length;
652
+
653
+ // ── Step 3: guided-flow dispatch (isAutoMode=false) ──
654
+ await selectAndApplyModel(
655
+ makeCtx(availableModels),
656
+ pi as any,
657
+ "gate-evaluate",
658
+ "u-guided",
659
+ env.dir,
660
+ undefined,
661
+ false,
662
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
663
+ undefined,
664
+ /* isAutoMode */ false,
665
+ );
666
+
667
+ const guidedCalls = pi.__calls.slice(callsBeforeGuided);
668
+ // The bug we're guarding against: a setActiveTools call during the guided
669
+ // dispatch that contains the auto-era baseline tools (which would mean the
670
+ // auto-captured baseline resurrected and overwrote the user's edit).
671
+ const baselineRestore = guidedCalls.find(
672
+ c => c.kind === "setActiveTools"
673
+ && Array.isArray(c.payload)
674
+ && baselineTools.every(t => (c.payload as string[]).includes(t)),
675
+ );
676
+ assert.equal(
677
+ baselineRestore,
678
+ undefined,
679
+ "guided-flow dispatch (isAutoMode=false) must NOT restore the auto-mode baseline",
680
+ );
681
+ } finally {
682
+ env.restoreEnv();
683
+ env.cleanup();
684
+ }
685
+ });
686
+
687
+ test("cross-mode (#4965): auto → guided → auto preserves the original auto-era baseline for the second auto run", async () => {
688
+ const env = makeTempProject();
689
+ try {
690
+ const availableModels = [
691
+ { id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
692
+ ];
693
+ const baselineTools = ["gsd_save_gate_result", "tool_a", "tool_b"];
694
+ const pi = makeRecordingPi(baselineTools);
695
+ clearToolBaseline(pi as unknown as object);
696
+
697
+ // Auto run 1 — captures baseline.
698
+ await selectAndApplyModel(
699
+ makeCtx(availableModels), pi as any, "gate-evaluate", "u1",
700
+ env.dir, undefined, false,
701
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
702
+ undefined, /* isAutoMode */ true,
703
+ );
704
+
705
+ // Guided dispatch in between — must not corrupt the baseline.
706
+ pi.setActiveTools(["narrow_for_guided"]);
707
+ await selectAndApplyModel(
708
+ makeCtx(availableModels), pi as any, "gate-evaluate", "u-guided",
709
+ env.dir, undefined, false,
710
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
711
+ undefined, /* isAutoMode */ false,
712
+ );
713
+
714
+ // Now narrow further (simulating any post-guided state) and run auto u2.
715
+ pi.setActiveTools(["something_completely_different"]);
716
+ const callsBeforeU2 = pi.__calls.length;
717
+
718
+ // Auto run 2 — must restore the ORIGINAL auto-era baseline, not the
719
+ // intervening narrow-for-guided state.
720
+ await selectAndApplyModel(
721
+ makeCtx(availableModels), pi as any, "gate-evaluate", "u2",
722
+ env.dir, undefined, false,
723
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
724
+ undefined, /* isAutoMode */ true,
725
+ );
726
+
727
+ const u2Calls = pi.__calls.slice(callsBeforeU2);
728
+ const restoreCall = u2Calls.find(
729
+ c => c.kind === "setActiveTools"
730
+ && Array.isArray(c.payload)
731
+ && (c.payload as string[]).length === baselineTools.length
732
+ && baselineTools.every(t => (c.payload as string[]).includes(t)),
733
+ );
734
+ assert.ok(
735
+ restoreCall,
736
+ "auto run 2 must restore the auto-era baseline [A, B, C] — proves guided-flow didn't corrupt it",
737
+ );
738
+ } finally {
739
+ env.restoreEnv();
740
+ env.cleanup();
741
+ }
742
+ });