gsd-pi 2.77.0-dev.c9c8932c6 → 2.77.0-dev.cfd69e714

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 (708) hide show
  1. package/README.md +1 -1
  2. package/dist/claude-cli-check.js +5 -1
  3. package/dist/cli-web-branch.d.ts +1 -0
  4. package/dist/cli-web-branch.js +3 -0
  5. package/dist/cli.js +24 -2
  6. package/dist/headless.js +49 -4
  7. package/dist/provider-migrations.d.ts +18 -0
  8. package/dist/provider-migrations.js +14 -0
  9. package/dist/resource-loader.d.ts +40 -0
  10. package/dist/resource-loader.js +32 -13
  11. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  12. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  13. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  14. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  15. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  16. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  17. package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
  18. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +495 -27
  19. package/dist/resources/extensions/github-sync/templates.js +103 -0
  20. package/dist/resources/extensions/google-search/index.js +3 -2
  21. package/dist/resources/extensions/gsd/abandon-detect.js +44 -0
  22. package/dist/resources/extensions/gsd/auto/loop.js +124 -2
  23. package/dist/resources/extensions/gsd/auto/phases.js +57 -39
  24. package/dist/resources/extensions/gsd/auto/resolve.js +24 -0
  25. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
  26. package/dist/resources/extensions/gsd/auto/session.js +6 -2
  27. package/dist/resources/extensions/gsd/auto/turn-epoch.js +95 -0
  28. package/dist/resources/extensions/gsd/auto-dispatch.js +166 -29
  29. package/dist/resources/extensions/gsd/auto-loop.js +1 -1
  30. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  31. package/dist/resources/extensions/gsd/auto-post-unit.js +181 -64
  32. package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
  33. package/dist/resources/extensions/gsd/auto-recovery.js +197 -48
  34. package/dist/resources/extensions/gsd/auto-start.js +107 -29
  35. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +11 -5
  36. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  37. package/dist/resources/extensions/gsd/auto-unit-closeout.js +11 -2
  38. package/dist/resources/extensions/gsd/auto-worktree.js +180 -34
  39. package/dist/resources/extensions/gsd/auto.js +80 -21
  40. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +19 -1
  41. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  42. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -6
  43. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  44. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  45. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +31 -4
  46. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  47. package/dist/resources/extensions/gsd/component-types.js +69 -0
  48. package/dist/resources/extensions/gsd/context-store.js +23 -7
  49. package/dist/resources/extensions/gsd/detection.js +49 -1
  50. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -17
  51. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  52. package/dist/resources/extensions/gsd/file-lock.js +49 -9
  53. package/dist/resources/extensions/gsd/forensics.js +106 -0
  54. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  55. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  56. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  57. package/dist/resources/extensions/gsd/git-service.js +127 -2
  58. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  59. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  60. package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -1
  61. package/dist/resources/extensions/gsd/guided-flow.js +39 -13
  62. package/dist/resources/extensions/gsd/journal.js +17 -2
  63. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  64. package/dist/resources/extensions/gsd/milestone-actions.js +15 -0
  65. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  66. package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
  67. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  68. package/dist/resources/extensions/gsd/model-router.js +6 -0
  69. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  70. package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
  71. package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
  72. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  73. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  74. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  75. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  76. package/dist/resources/extensions/gsd/reports.js +5 -4
  77. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  78. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  79. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  80. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  81. package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
  82. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  83. package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
  84. package/dist/resources/extensions/gsd/state.js +69 -58
  85. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  86. package/dist/resources/extensions/gsd/tools/complete-slice.js +21 -0
  87. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -0
  88. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
  89. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  90. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  91. package/dist/resources/extensions/gsd/uok/audit.js +18 -2
  92. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
  93. package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
  94. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  95. package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
  96. package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
  97. package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
  98. package/dist/resources/extensions/gsd/uok/plan-v2.js +10 -4
  99. package/dist/resources/extensions/gsd/uok/writer.js +82 -0
  100. package/dist/resources/extensions/gsd/workflow-logger.js +10 -2
  101. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  102. package/dist/resources/extensions/gsd/worktree-manager.js +86 -8
  103. package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
  104. package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
  105. package/dist/resources/extensions/mcp-client/auth.js +10 -1
  106. package/dist/resources/extensions/mcp-client/index.js +121 -10
  107. package/dist/resources/extensions/ollama/index.js +5 -1
  108. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  109. package/dist/resources/skills/create-skill/SKILL.md +2 -2
  110. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  111. package/dist/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  112. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  113. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  114. package/dist/web/standalone/.next/BUILD_ID +1 -1
  115. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  116. package/dist/web/standalone/.next/build-manifest.json +3 -3
  117. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  118. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  119. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  120. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  128. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  165. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  169. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  171. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  173. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  175. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  177. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  179. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  181. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  183. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  185. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  187. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  189. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  191. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  193. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  195. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  197. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  199. package/dist/web/standalone/.next/server/app/index.html +1 -1
  200. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  201. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  202. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  203. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  204. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  205. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  206. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  207. package/dist/web/standalone/.next/server/chunks/1926.js +1 -0
  208. package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
  209. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  210. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  211. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  212. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  213. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  214. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  215. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
  216. package/dist/web/standalone/.next/static/chunks/{webpack-1832629448831fdc.js → webpack-2e68521d7c82f7c2.js} +1 -1
  217. package/package.json +4 -5
  218. package/packages/daemon/src/logger.ts +4 -3
  219. package/packages/mcp-server/README.md +3 -3
  220. package/packages/mcp-server/dist/env-writer.d.ts +1 -0
  221. package/packages/mcp-server/dist/env-writer.d.ts.map +1 -1
  222. package/packages/mcp-server/dist/env-writer.js +74 -6
  223. package/packages/mcp-server/dist/env-writer.js.map +1 -1
  224. package/packages/mcp-server/dist/server.d.ts +24 -0
  225. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  226. package/packages/mcp-server/dist/server.js +111 -87
  227. package/packages/mcp-server/dist/server.js.map +1 -1
  228. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  229. package/packages/mcp-server/dist/workflow-tools.js +15 -6
  230. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  231. package/packages/mcp-server/src/env-writer.test.ts +79 -1
  232. package/packages/mcp-server/src/env-writer.ts +76 -6
  233. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  234. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  235. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  236. package/packages/mcp-server/src/server.ts +158 -105
  237. package/packages/mcp-server/src/workflow-tools.test.ts +85 -0
  238. package/packages/mcp-server/src/workflow-tools.ts +19 -6
  239. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  240. package/packages/native/package.json +1 -1
  241. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  242. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  243. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  244. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  245. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  246. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  247. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  248. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  249. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  250. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  251. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  252. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  253. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  254. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  255. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  256. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  257. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  258. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  259. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  260. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  261. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  262. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  263. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  264. package/packages/pi-ai/dist/models.test.js +36 -11
  265. package/packages/pi-ai/dist/models.test.js.map +1 -1
  266. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  267. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  268. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  269. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  270. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  271. package/packages/pi-ai/src/models.test.ts +48 -11
  272. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  273. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  274. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  275. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  276. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  277. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  278. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +1 -0
  280. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +21 -16
  282. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts +2 -0
  284. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts.map +1 -0
  285. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js +130 -0
  286. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js.map +1 -0
  287. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  288. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  290. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  291. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  292. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  294. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  296. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  297. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  298. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  299. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  300. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  301. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  302. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  303. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  304. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  305. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  306. package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -0
  307. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  308. package/packages/pi-coding-agent/dist/core/sdk.js +4 -1
  309. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  310. package/packages/pi-coding-agent/dist/core/sdk.test.js +19 -1
  311. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  312. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  313. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  314. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  315. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  316. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  317. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  318. package/packages/pi-coding-agent/dist/index.js +1 -0
  319. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  320. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +15 -6
  321. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -1
  322. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
  323. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  324. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  325. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  326. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  327. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
  328. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  329. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +7 -1
  330. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  331. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +31 -9
  332. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  333. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  334. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
  335. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  336. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  337. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  338. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  339. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  340. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  341. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  342. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  343. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  344. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  345. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  346. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  347. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  348. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  349. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  350. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  351. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  352. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  353. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  354. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  355. package/packages/pi-coding-agent/src/core/compaction-orchestrator.test.ts +154 -0
  356. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +24 -17
  357. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  358. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  359. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  360. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  361. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  362. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  363. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  364. package/packages/pi-coding-agent/src/core/sdk.test.ts +25 -1
  365. package/packages/pi-coding-agent/src/core/sdk.ts +10 -3
  366. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  367. package/packages/pi-coding-agent/src/index.ts +1 -0
  368. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +17 -7
  369. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
  370. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  371. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +14 -5
  372. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +45 -11
  373. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
  374. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  375. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  376. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  377. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  378. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  379. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  380. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  381. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  382. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  383. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  384. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +51 -6
  385. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  386. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  387. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  388. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  389. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  390. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  391. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  392. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  393. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  394. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  395. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  396. package/packages/pi-tui/dist/components/editor.js +19 -0
  397. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  398. package/packages/pi-tui/dist/components/image.test.js +6 -5
  399. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  400. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  401. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  402. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  403. package/packages/pi-tui/dist/stdin-buffer.d.ts +7 -0
  404. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  405. package/packages/pi-tui/dist/stdin-buffer.js +20 -0
  406. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  407. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  408. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  409. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +62 -6
  410. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  411. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  412. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  413. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  414. package/packages/pi-tui/src/components/editor.ts +22 -0
  415. package/packages/pi-tui/src/components/image.test.ts +10 -5
  416. package/packages/pi-tui/src/editor-component.ts +3 -0
  417. package/packages/pi-tui/src/stdin-buffer.ts +26 -0
  418. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  419. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  420. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  421. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  422. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  423. package/scripts/install.js +15 -1
  424. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  425. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  426. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  427. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  428. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  429. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  430. package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
  431. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +532 -29
  432. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +941 -91
  433. package/src/resources/extensions/github-sync/templates.ts +151 -0
  434. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  435. package/src/resources/extensions/github-sync/tests/templates.test.ts +92 -1
  436. package/src/resources/extensions/google-search/index.ts +3 -2
  437. package/src/resources/extensions/gsd/abandon-detect.ts +62 -0
  438. package/src/resources/extensions/gsd/auto/loop.ts +142 -2
  439. package/src/resources/extensions/gsd/auto/phases.ts +62 -38
  440. package/src/resources/extensions/gsd/auto/resolve.ts +29 -0
  441. package/src/resources/extensions/gsd/auto/run-unit.ts +16 -2
  442. package/src/resources/extensions/gsd/auto/session.ts +7 -2
  443. package/src/resources/extensions/gsd/auto/turn-epoch.ts +108 -0
  444. package/src/resources/extensions/gsd/auto-dispatch.ts +176 -29
  445. package/src/resources/extensions/gsd/auto-loop.ts +1 -1
  446. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  447. package/src/resources/extensions/gsd/auto-post-unit.ts +193 -73
  448. package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
  449. package/src/resources/extensions/gsd/auto-recovery.ts +230 -51
  450. package/src/resources/extensions/gsd/auto-start.ts +127 -9
  451. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -5
  452. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  453. package/src/resources/extensions/gsd/auto-unit-closeout.ts +14 -3
  454. package/src/resources/extensions/gsd/auto-worktree.ts +190 -31
  455. package/src/resources/extensions/gsd/auto.ts +95 -23
  456. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +20 -1
  457. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  458. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -6
  459. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  460. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  461. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +27 -8
  462. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  463. package/src/resources/extensions/gsd/component-types.ts +362 -0
  464. package/src/resources/extensions/gsd/context-store.ts +25 -8
  465. package/src/resources/extensions/gsd/detection.ts +58 -1
  466. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -20
  467. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  468. package/src/resources/extensions/gsd/file-lock.ts +84 -11
  469. package/src/resources/extensions/gsd/forensics.ts +118 -1
  470. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  471. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  472. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  473. package/src/resources/extensions/gsd/git-service.ts +150 -2
  474. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  475. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  476. package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -1
  477. package/src/resources/extensions/gsd/guided-flow.ts +57 -14
  478. package/src/resources/extensions/gsd/journal.ts +38 -3
  479. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  480. package/src/resources/extensions/gsd/milestone-actions.ts +18 -0
  481. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  482. package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
  483. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  484. package/src/resources/extensions/gsd/model-router.ts +6 -0
  485. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  486. package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
  487. package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
  488. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  489. package/src/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  490. package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  491. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  492. package/src/resources/extensions/gsd/reports.ts +5 -4
  493. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  494. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  495. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  496. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  497. package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
  498. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  499. package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
  500. package/src/resources/extensions/gsd/state.ts +76 -66
  501. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  502. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  503. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
  504. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  505. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  506. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -285
  507. package/src/resources/extensions/gsd/tests/auto-mode-guards.test.ts +79 -0
  508. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  509. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  510. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  511. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  512. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
  513. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
  514. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
  515. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
  516. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  517. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
  518. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
  519. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
  520. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
  521. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  522. package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
  523. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  524. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  525. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  526. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
  527. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  528. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  529. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  530. package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
  531. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
  532. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  533. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +159 -0
  534. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
  535. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  536. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  537. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  538. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -4
  539. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
  540. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
  541. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +5 -0
  542. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
  543. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
  544. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  545. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
  546. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
  547. package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
  548. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  549. package/src/resources/extensions/gsd/tests/file-lock.test.ts +86 -12
  550. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
  551. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  552. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  553. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +14 -4
  554. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
  555. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  556. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
  557. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  558. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +30 -0
  559. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  560. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
  561. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  562. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  563. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  564. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  565. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
  566. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  567. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +64 -0
  568. package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
  569. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +47 -0
  570. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  571. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
  572. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  573. package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
  574. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  575. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  576. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
  577. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  578. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
  579. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
  580. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  581. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  582. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
  583. package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
  584. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
  585. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +54 -41
  586. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +213 -0
  587. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
  588. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
  589. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +13 -7
  590. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  591. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  592. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  593. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  594. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +170 -0
  595. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  596. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
  597. package/src/resources/extensions/gsd/tests/rewrite-docs-abandon-detect.test.ts +195 -0
  598. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  599. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  600. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  601. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
  602. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  603. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  604. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  605. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  606. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
  607. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
  608. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  609. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
  610. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
  611. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +29 -5
  612. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
  613. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
  614. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  615. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +2 -2
  616. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
  617. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  618. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  619. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
  620. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
  621. package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
  622. package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  623. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  624. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  625. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +50 -2
  626. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +162 -0
  627. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  628. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  629. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
  630. package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
  631. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  632. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  633. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
  634. package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
  635. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +19 -2
  636. package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
  637. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -0
  638. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
  639. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  640. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  641. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  642. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  643. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  644. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  645. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  646. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  647. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
  648. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  649. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  650. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  651. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  652. package/src/resources/extensions/gsd/tools/complete-slice.ts +38 -0
  653. package/src/resources/extensions/gsd/tools/complete-task.ts +49 -0
  654. package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
  655. package/src/resources/extensions/gsd/types.ts +3 -3
  656. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  657. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  658. package/src/resources/extensions/gsd/uok/audit.ts +20 -2
  659. package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
  660. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
  661. package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
  662. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  663. package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
  664. package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
  665. package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
  666. package/src/resources/extensions/gsd/uok/plan-v2.ts +13 -5
  667. package/src/resources/extensions/gsd/uok/writer.ts +113 -0
  668. package/src/resources/extensions/gsd/workflow-logger.ts +22 -3
  669. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  670. package/src/resources/extensions/gsd/worktree-manager.ts +109 -7
  671. package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
  672. package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
  673. package/src/resources/extensions/mcp-client/auth.ts +12 -1
  674. package/src/resources/extensions/mcp-client/index.ts +132 -11
  675. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  676. package/src/resources/extensions/ollama/index.ts +5 -1
  677. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  678. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  679. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  680. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  681. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  682. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  683. package/src/resources/skills/create-skill/SKILL.md +2 -2
  684. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  685. package/src/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  686. package/src/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  687. package/dist/web/standalone/.next/server/chunks/7461.js +0 -1
  688. package/dist/web/standalone/.next/static/chunks/2826.d445fb428ef41fa1.js +0 -9
  689. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  690. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  691. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  692. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  693. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  694. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
  695. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  696. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  697. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  698. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  699. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  700. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  701. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
  702. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  703. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  704. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  705. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
  706. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
  707. /package/dist/web/standalone/.next/static/{H51SzzS_EEgdBsDVhrLBv → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
  708. /package/dist/web/standalone/.next/static/{H51SzzS_EEgdBsDVhrLBv → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
@@ -13,6 +13,10 @@ import {
13
13
  buildPromptFromContext,
14
14
  buildSdkQueryPrompt,
15
15
  buildSdkOptions,
16
+ createClaudeCodeCanUseToolHandler,
17
+ buildBashPermissionPattern,
18
+ buildBashPermissionPatternOptions,
19
+ bashCommandMatchesSavedRules,
16
20
  createClaudeCodeElicitationHandler,
17
21
  extractImageBlocksFromContext,
18
22
  extractToolResultsFromSdkUserMessage,
@@ -20,11 +24,58 @@ import {
20
24
  parseAskUserQuestionsElicitation,
21
25
  parseTextInputElicitation,
22
26
  parseClaudeLookupOutput,
27
+ resolveBundledClaudeCliPath,
28
+ normalizeClaudePathForSdk,
23
29
  roundResultToElicitationContent,
24
30
  } from "../stream-adapter.ts";
25
31
  import type { AssistantMessage, Context, Message } from "@gsd/pi-ai";
26
32
  import type { SDKUserMessage } from "../sdk-types.ts";
27
33
 
34
+ // ---------------------------------------------------------------------------
35
+ // Env helpers — `GSD_WORKFLOW_MCP_*` save/restore
36
+ //
37
+ // The naive pattern `process.env.X = prev.X` breaks when `prev.X` is
38
+ // undefined: Node coerces the assignment to the literal string
39
+ // "undefined", which then pollutes subsequent tests that read the var
40
+ // and assume it's absent. Issue #4808 documents the resulting bleed.
41
+ //
42
+ // `setWorkflowMcpEnv` returns a `restore()` closure that either
43
+ // re-assigns the previous string value OR `delete`s the key when the
44
+ // original was absent. Call in a try/finally; restore in the finally.
45
+ // ---------------------------------------------------------------------------
46
+
47
+ const WORKFLOW_MCP_ENV_KEYS = [
48
+ "GSD_WORKFLOW_MCP_COMMAND",
49
+ "GSD_WORKFLOW_MCP_NAME",
50
+ "GSD_WORKFLOW_MCP_ARGS",
51
+ "GSD_WORKFLOW_MCP_ENV",
52
+ "GSD_WORKFLOW_MCP_CWD",
53
+ ] as const;
54
+
55
+ type WorkflowMcpEnvKey = (typeof WORKFLOW_MCP_ENV_KEYS)[number];
56
+
57
+ function setWorkflowMcpEnv(
58
+ values: Partial<Record<WorkflowMcpEnvKey, string>>,
59
+ ): () => void {
60
+ const prev: Partial<Record<WorkflowMcpEnvKey, string | undefined>> = {};
61
+ for (const key of WORKFLOW_MCP_ENV_KEYS) {
62
+ prev[key] = process.env[key];
63
+ }
64
+ for (const [key, value] of Object.entries(values)) {
65
+ process.env[key] = value;
66
+ }
67
+ return function restore() {
68
+ for (const key of WORKFLOW_MCP_ENV_KEYS) {
69
+ const previous = prev[key];
70
+ if (previous === undefined) {
71
+ delete process.env[key];
72
+ } else {
73
+ process.env[key] = previous;
74
+ }
75
+ }
76
+ };
77
+ }
78
+
28
79
  // ---------------------------------------------------------------------------
29
80
  // Existing tests — exhausted stream fallback (#2575)
30
81
  // ---------------------------------------------------------------------------
@@ -737,19 +788,14 @@ describe("stream-adapter — session persistence (#2859)", () => {
737
788
  });
738
789
 
739
790
  test("buildSdkOptions includes workflow MCP server config when env is set", () => {
740
- const prev = {
741
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
742
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
743
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
744
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
745
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
746
- };
791
+ const restore = setWorkflowMcpEnv({
792
+ GSD_WORKFLOW_MCP_COMMAND: "node",
793
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
794
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
795
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
796
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
797
+ });
747
798
  try {
748
- process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
749
- process.env.GSD_WORKFLOW_MCP_NAME = "gsd-workflow";
750
- process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
751
- process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
752
- process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
753
799
 
754
800
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
755
801
  const mcpServers = options.mcpServers as Record<string, any>;
@@ -761,72 +807,63 @@ describe("stream-adapter — session persistence (#2859)", () => {
761
807
  assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
762
808
  assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
763
809
  assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
764
- assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
810
+ assert.deepEqual(options.disallowedTools, []);
765
811
  assert.deepEqual(options.allowedTools, [
766
812
  "Read",
767
813
  "Write",
768
814
  "Edit",
769
815
  "Glob",
770
816
  "Grep",
771
- "Bash(ls:*)",
772
- "Bash(pwd)",
817
+ "Bash",
818
+ "Agent",
819
+ "WebFetch",
820
+ "WebSearch",
821
+ "AskUserQuestion",
773
822
  "mcp__gsd-workflow__*",
774
823
  ]);
775
824
  } finally {
776
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
777
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
778
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
779
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
780
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
825
+ restore();
781
826
  }
782
827
  });
783
828
 
784
- test("buildSdkOptions disables AskUserQuestion for custom workflow MCP server names", () => {
785
- const prev = {
786
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
787
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
788
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
789
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
790
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
791
- };
829
+ test("buildSdkOptions auto-approves every tool for custom workflow MCP server names", () => {
830
+ const restore = setWorkflowMcpEnv({
831
+ GSD_WORKFLOW_MCP_COMMAND: "node",
832
+ GSD_WORKFLOW_MCP_NAME: "custom-workflow",
833
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
834
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
835
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
836
+ });
792
837
  try {
793
- process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
794
- process.env.GSD_WORKFLOW_MCP_NAME = "custom-workflow";
795
- process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
796
- process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
797
- process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
798
838
 
799
839
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
800
840
  const mcpServers = options.mcpServers as Record<string, any>;
801
841
  assert.ok(mcpServers?.["custom-workflow"], "expected custom workflow server config");
802
- assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
842
+ assert.deepEqual(options.disallowedTools, []);
803
843
  assert.deepEqual(options.allowedTools, [
804
844
  "Read",
805
845
  "Write",
806
846
  "Edit",
807
847
  "Glob",
808
848
  "Grep",
809
- "Bash(ls:*)",
810
- "Bash(pwd)",
849
+ "Bash",
850
+ "Agent",
851
+ "WebFetch",
852
+ "WebSearch",
853
+ "AskUserQuestion",
811
854
  "mcp__custom-workflow__*",
812
855
  ]);
813
856
  } finally {
814
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
815
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
816
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
817
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
818
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
857
+ restore();
819
858
  }
820
859
  });
821
860
 
822
861
  test("buildSdkOptions auto-discovers bundled MCP server even without env hints", () => {
823
- const prev = {
824
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
825
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
826
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
827
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
828
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
829
- };
862
+ // Use setWorkflowMcpEnv with no values to save current state;
863
+ // restore() in finally will put it back correctly (including
864
+ // deleting any keys that started as undefined — the #4808 bug
865
+ // the naive `process.env.X = prev.X` pattern introduced).
866
+ const restore = setWorkflowMcpEnv({});
830
867
  try {
831
868
  delete process.env.GSD_WORKFLOW_MCP_COMMAND;
832
869
  delete process.env.GSD_WORKFLOW_MCP_NAME;
@@ -845,29 +882,21 @@ describe("stream-adapter — session persistence (#2859)", () => {
845
882
  const mcpServers = (options as any).mcpServers;
846
883
  if (mcpServers) {
847
884
  assert.ok(mcpServers["gsd-workflow"], "if present, must be gsd-workflow");
848
- assert.deepEqual((options as any).disallowedTools, ["AskUserQuestion"]);
885
+ assert.deepEqual((options as any).disallowedTools, []);
849
886
  } else {
850
- assert.deepEqual((options as any).disallowedTools, ["AskUserQuestion"]);
887
+ assert.deepEqual((options as any).disallowedTools, []);
851
888
  }
852
889
  rmSync(emptyDir, { recursive: true, force: true });
853
890
  } finally {
854
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
855
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
856
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
857
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
858
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
891
+ restore();
859
892
  }
860
893
  });
861
894
 
862
895
  test("buildSdkOptions auto-detects local workflow MCP dist CLI when present", () => {
863
- const prev = {
864
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
865
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
866
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
867
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
868
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
869
- GSD_CLI_PATH: process.env.GSD_CLI_PATH,
870
- };
896
+ // GSD_CLI_PATH isn't in WORKFLOW_MCP_ENV_KEYS, so save+restore it
897
+ // manually around setWorkflowMcpEnv which handles the MCP keys.
898
+ const prevCliPath = process.env.GSD_CLI_PATH;
899
+ const restore = setWorkflowMcpEnv({});
871
900
  const originalCwd = process.cwd();
872
901
  const repoDir = mkdtempSync(join(tmpdir(), "claude-mcp-detect-"));
873
902
  try {
@@ -894,27 +923,22 @@ describe("stream-adapter — session persistence (#2859)", () => {
894
923
  assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
895
924
  assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
896
925
  assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, resolvedRepoDir);
897
- assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
926
+ assert.deepEqual(options.disallowedTools, []);
898
927
  } finally {
899
928
  process.chdir(originalCwd);
900
929
  rmSync(repoDir, { recursive: true, force: true });
901
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
902
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
903
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
904
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
905
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
906
- process.env.GSD_CLI_PATH = prev.GSD_CLI_PATH;
930
+ restore();
931
+ // GSD_CLI_PATH isn't in setWorkflowMcpEnv's scope — restore it here.
932
+ if (prevCliPath === undefined) {
933
+ delete process.env.GSD_CLI_PATH;
934
+ } else {
935
+ process.env.GSD_CLI_PATH = prevCliPath;
936
+ }
907
937
  }
908
938
  });
909
939
 
910
940
  test("buildSdkOptions preserves runtime callbacks such as onElicitation", () => {
911
- const prev = {
912
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
913
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
914
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
915
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
916
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
917
- };
941
+ const restore = setWorkflowMcpEnv({});
918
942
  const onElicitation = async () => ({ action: "decline" as const });
919
943
  try {
920
944
  delete process.env.GSD_WORKFLOW_MCP_COMMAND;
@@ -925,11 +949,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
925
949
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { onElicitation });
926
950
  assert.equal(options.onElicitation, onElicitation);
927
951
  } finally {
928
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
929
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
930
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
931
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
932
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
952
+ restore();
933
953
  }
934
954
  });
935
955
  });
@@ -1282,14 +1302,14 @@ describe("stream-adapter — permission mode (F10)", () => {
1282
1302
  }
1283
1303
  }
1284
1304
 
1285
- test("buildSdkOptions defaults to acceptEdits (#4383)", () => {
1305
+ test("buildSdkOptions defaults to bypassPermissions (globally unblocks all tools)", () => {
1286
1306
  clearWorkflowMcpEnv();
1287
1307
  const opts = buildSdkOptions("claude-sonnet-4-6", "test");
1288
- assert.equal(opts.permissionMode, "acceptEdits");
1308
+ assert.equal(opts.permissionMode, "bypassPermissions");
1289
1309
  assert.equal(
1290
1310
  opts.allowDangerouslySkipPermissions,
1291
- false,
1292
- "allowDangerouslySkipPermissions must be false when permissionMode is acceptEdits",
1311
+ true,
1312
+ "allowDangerouslySkipPermissions must be true when permissionMode is bypassPermissions",
1293
1313
  );
1294
1314
  });
1295
1315
 
@@ -1304,9 +1324,9 @@ describe("stream-adapter — permission mode (F10)", () => {
1304
1324
  );
1305
1325
  });
1306
1326
 
1307
- test("resolveClaudePermissionMode defaults to acceptEdits when no env var is set (#4383)", async () => {
1327
+ test("resolveClaudePermissionMode defaults to bypassPermissions when no env var is set (globally unblocks all tools)", async () => {
1308
1328
  const mode = await resolveClaudePermissionMode({});
1309
- assert.equal(mode, "acceptEdits");
1329
+ assert.equal(mode, "bypassPermissions");
1310
1330
  });
1311
1331
 
1312
1332
  test("resolveClaudePermissionMode honours the GSD_CLAUDE_CODE_PERMISSION_MODE env override", async () => {
@@ -1357,8 +1377,838 @@ describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
1357
1377
  assert.equal(getClaudeLookupCommand("linux"), "which claude");
1358
1378
  });
1359
1379
 
1360
- test("parseClaudeLookupOutput keeps the first native path from multi-line lookup output", () => {
1361
- const output = "C:\\Users\\Binoy\\.local\\bin\\claude.exe\r\nC:\\Program Files\\Claude\\claude.exe\r\n";
1362
- assert.equal(parseClaudeLookupOutput(output), "C:\\Users\\Binoy\\.local\\bin\\claude.exe");
1380
+ test("parseClaudeLookupOutput prefers .exe on win32 when where output includes shims", () => {
1381
+ const output = [
1382
+ "C:\\Users\\djeff\\AppData\\Roaming\\npm\\claude",
1383
+ "C:\\Users\\djeff\\AppData\\Roaming\\npm\\claude.cmd",
1384
+ "C:\\Program Files\\Claude\\claude.exe",
1385
+ ].join("\r\n");
1386
+ assert.equal(parseClaudeLookupOutput(output, "win32"), "C:\\Program Files\\Claude\\claude.exe");
1387
+ });
1388
+
1389
+ test("parseClaudeLookupOutput keeps first line on non-win32 platforms", () => {
1390
+ const output = "/usr/local/bin/claude\n/opt/homebrew/bin/claude\n";
1391
+ assert.equal(parseClaudeLookupOutput(output, "darwin"), "/usr/local/bin/claude");
1392
+ });
1393
+
1394
+ test("normalizeClaudePathForSdk swaps Windows shim paths to bundled cli.js", () => {
1395
+ const shimPath = "C:\\Users\\djeff\\AppData\\Roaming\\npm\\claude";
1396
+ const bundled = "C:\\repo\\node_modules\\@anthropic-ai\\claude-agent-sdk\\cli.js";
1397
+ assert.equal(normalizeClaudePathForSdk(shimPath, "win32", bundled), bundled);
1398
+ assert.equal(normalizeClaudePathForSdk("C:\\Program Files\\Claude\\claude.exe", "win32", bundled), "C:\\Program Files\\Claude\\claude.exe");
1399
+ });
1400
+
1401
+ test("resolveBundledClaudeCliPath returns a .js path when SDK package is present", () => {
1402
+ const resolved = resolveBundledClaudeCliPath();
1403
+ assert.ok(resolved, "expected sdk cli.js to be resolvable in test workspace");
1404
+ assert.match(resolved!, /[\\/]@anthropic-ai[\\/]claude-agent-sdk[\\/]cli\.js$/);
1405
+ });
1406
+ });
1407
+
1408
+ // ---------------------------------------------------------------------------
1409
+ // canUseTool handler (#4383)
1410
+ // ---------------------------------------------------------------------------
1411
+
1412
+ describe("stream-adapter — canUseTool handler", () => {
1413
+ function makeOptions(overrides: Partial<{ signal: AbortSignal; suggestions: Array<Record<string, unknown>>; title: string; description: string; toolUseID: string }> = {}) {
1414
+ return {
1415
+ signal: overrides.signal ?? new AbortController().signal,
1416
+ toolUseID: overrides.toolUseID ?? "toolu_test123",
1417
+ ...(overrides.title !== undefined ? { title: overrides.title } : {}),
1418
+ ...(overrides.description !== undefined ? { description: overrides.description } : {}),
1419
+ ...(overrides.suggestions !== undefined ? { suggestions: overrides.suggestions } : {}),
1420
+ };
1421
+ }
1422
+
1423
+ // Point process.cwd() at an empty temp dir so the real repo's
1424
+ // .claude/settings.local.json (which may already contain rules like
1425
+ // "Bash(gh pr list:*)") does not short-circuit the permission flow.
1426
+ // Returns a cleanup function that restores cwd and removes the temp dir.
1427
+ // biome-ignore lint/suspicious/noExplicitAny: test-only monkey-patch
1428
+ function withIsolatedCwd(): () => void {
1429
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-canusetool-")));
1430
+ const orig = process.cwd;
1431
+ process.cwd = () => dir;
1432
+ return () => {
1433
+ process.cwd = orig;
1434
+ rmSync(dir, { recursive: true, force: true });
1435
+ };
1436
+ }
1437
+
1438
+ test("returns undefined when no UI context is provided", () => {
1439
+ const handler = createClaudeCodeCanUseToolHandler(undefined);
1440
+ assert.equal(handler, undefined);
1441
+ });
1442
+
1443
+ test("shows select dialog with Allow/Always Allow/Deny and returns allow", async () => {
1444
+ let selectPrompt = "";
1445
+ let selectOptions: string[] = [];
1446
+ const ui = {
1447
+ select: async (prompt: string, options: string[]) => {
1448
+ selectPrompt = prompt;
1449
+ selectOptions = options;
1450
+ return "Allow";
1451
+ },
1452
+ };
1453
+
1454
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1455
+ assert.ok(handler);
1456
+
1457
+ const input = { command: "ls -la" };
1458
+ const result = await handler!("Bash", input, makeOptions({
1459
+ title: "Claude wants to run: ls -la",
1460
+ description: "List directory contents",
1461
+ }));
1462
+
1463
+ assert.equal(result.behavior, "allow");
1464
+ assert.deepEqual((result as any).updatedInput, input);
1465
+ assert.equal((result as any).toolUseID, "toolu_test123");
1466
+ // Allow (one-time) should NOT include updatedPermissions
1467
+ assert.equal((result as any).updatedPermissions, undefined);
1468
+ assert.deepEqual(selectOptions, ["Allow", "Always Allow", "Deny"]);
1469
+ // Prompt includes title and input summary
1470
+ assert.ok(selectPrompt.includes("Claude wants to run: ls -la"));
1471
+ assert.ok(selectPrompt.includes("ls -la"));
1472
+ });
1473
+
1474
+ test("returns deny when user selects Deny", async () => {
1475
+ const ui = {
1476
+ select: async () => "Deny",
1477
+ };
1478
+
1479
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1480
+ const result = await handler!("Bash", { command: "rm -rf /" }, makeOptions());
1481
+
1482
+ assert.equal(result.behavior, "deny");
1483
+ assert.equal((result as any).message, "User denied");
1484
+ assert.equal((result as any).toolUseID, "toolu_test123");
1485
+ });
1486
+
1487
+ test("returns deny when user dismisses dialog (undefined)", async () => {
1488
+ const ui = {
1489
+ select: async () => undefined,
1490
+ };
1491
+
1492
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1493
+ const result = await handler!("Bash", { command: "echo hi" }, makeOptions());
1494
+
1495
+ assert.equal(result.behavior, "deny");
1496
+ assert.equal((result as any).message, "User denied");
1497
+ });
1498
+
1499
+ test("Always Allow for Bash patches SDK suggestions with smart ruleContent", async () => {
1500
+ const notified: string[] = [];
1501
+ const ui = { select: async (_p: string, opts: string[]) => opts.find((o) => o.startsWith("Always Allow"))!, notify: (msg: string) => notified.push(msg) };
1502
+ const suggestions = [{
1503
+ type: "addRules",
1504
+ rules: [{ toolName: "Bash", ruleContent: "ls -la /tmp" }],
1505
+ behavior: "allow",
1506
+ destination: "localSettings",
1507
+ }];
1508
+
1509
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1510
+ const result = await handler!("Bash", { command: "ls -la /tmp" }, makeOptions({ suggestions }));
1511
+
1512
+ assert.equal(result.behavior, "allow");
1513
+ // Should patch ruleContent with our smart pattern, preserving SDK structure
1514
+ assert.deepEqual((result as any).updatedPermissions, [{
1515
+ type: "addRules",
1516
+ rules: [{ toolName: "Bash", ruleContent: "ls:*" }],
1517
+ behavior: "allow",
1518
+ destination: "localSettings",
1519
+ }]);
1520
+ assert.equal(notified.length, 1);
1521
+ assert.ok(notified[0].includes("Saved:") && notified[0].includes("Bash(ls:*)"));
1522
+ });
1523
+
1524
+ test("Always Allow for Bash with subcommand-sensitive CLI captures verb", async () => {
1525
+ const cleanup = withIsolatedCwd();
1526
+ try {
1527
+ const notified: string[] = [];
1528
+ // First select call: pick "Always Allow ..."; second call (level
1529
+ // picker): pick the "git push" granularity explicitly.
1530
+ let selectCall = 0;
1531
+ const ui = {
1532
+ select: async (_p: string, opts: string[]) => {
1533
+ selectCall++;
1534
+ if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
1535
+ return "Bash(git push:*)";
1536
+ },
1537
+ notify: (msg: string) => notified.push(msg),
1538
+ };
1539
+ const suggestions = [{
1540
+ type: "addRules",
1541
+ rules: [{ toolName: "Bash", ruleContent: "git push origin main" }],
1542
+ behavior: "allow",
1543
+ destination: "localSettings",
1544
+ }];
1545
+
1546
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1547
+ const result = await handler!("Bash", { command: "git push origin main" }, makeOptions({ suggestions }));
1548
+
1549
+ assert.equal(result.behavior, "allow");
1550
+ assert.deepEqual((result as any).updatedPermissions, [{
1551
+ type: "addRules",
1552
+ rules: [{ toolName: "Bash", ruleContent: "git push:*" }],
1553
+ behavior: "allow",
1554
+ destination: "localSettings",
1555
+ }]);
1556
+ assert.ok(notified[0].includes("Saved:") && notified[0].includes("Bash(git push:*)"));
1557
+ } finally {
1558
+ cleanup();
1559
+ }
1560
+ });
1561
+
1562
+ test("Always Allow for Bash without suggestions builds proper PermissionUpdate", async () => {
1563
+ const cleanup = withIsolatedCwd();
1564
+ try {
1565
+ const notified: string[] = [];
1566
+ let selectCall = 0;
1567
+ const ui = {
1568
+ select: async (_p: string, opts: string[]) => {
1569
+ selectCall++;
1570
+ if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
1571
+ return "Bash(gh pr list:*)";
1572
+ },
1573
+ notify: (msg: string) => notified.push(msg),
1574
+ };
1575
+
1576
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1577
+ const result = await handler!("Bash", { command: "gh pr list" }, makeOptions());
1578
+
1579
+ assert.equal(result.behavior, "allow");
1580
+ // No SDK suggestions → builds PermissionUpdate from scratch
1581
+ assert.deepEqual((result as any).updatedPermissions, [{
1582
+ type: "addRules",
1583
+ rules: [{ toolName: "Bash", ruleContent: "gh pr list:*" }],
1584
+ behavior: "allow",
1585
+ destination: "localSettings",
1586
+ }]);
1587
+ assert.ok(notified[0].includes("Saved:") && notified[0].includes("Bash(gh pr list:*)"));
1588
+ } finally {
1589
+ cleanup();
1590
+ }
1591
+ });
1592
+
1593
+ test("Always Allow for non-Bash tools passes SDK suggestions through", async () => {
1594
+ const notified: string[] = [];
1595
+ const ui = { select: async (_p: string, opts: string[]) => opts.find((o) => o.startsWith("Always Allow"))!, notify: (msg: string) => notified.push(msg) };
1596
+ const suggestions = [{
1597
+ type: "addRules",
1598
+ rules: [{ toolName: "Write" }],
1599
+ behavior: "allow",
1600
+ destination: "localSettings",
1601
+ }];
1602
+
1603
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1604
+ const result = await handler!("Write", { file_path: "/tmp/test.txt" }, makeOptions({ suggestions }));
1605
+
1606
+ assert.equal(result.behavior, "allow");
1607
+ assert.deepEqual((result as any).updatedPermissions, suggestions);
1608
+ // Non-Bash tools don't emit a post-selection notification (only Bash runs the level picker)
1609
+ assert.equal(notified.length, 0);
1610
+ });
1611
+
1612
+ test("Always Allow for non-Bash without suggestions omits updatedPermissions", async () => {
1613
+ const notified: string[] = [];
1614
+ const ui = { select: async (_p: string, opts: string[]) => opts.find((o) => o.startsWith("Always Allow"))!, notify: (msg: string) => notified.push(msg) };
1615
+
1616
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1617
+ const result = await handler!("Write", { file_path: "/tmp/test.txt" }, makeOptions());
1618
+
1619
+ assert.equal(result.behavior, "allow");
1620
+ assert.equal((result as any).updatedPermissions, undefined);
1621
+ // No suggestions → no notification
1622
+ assert.equal(notified.length, 0);
1623
+ });
1624
+
1625
+ test("prompt includes command text for Bash tools", async () => {
1626
+ let selectPrompt = "";
1627
+ const ui = {
1628
+ select: async (prompt: string) => {
1629
+ selectPrompt = prompt;
1630
+ return "Allow";
1631
+ },
1632
+ };
1633
+
1634
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1635
+ await handler!("Bash", { command: "git status" }, makeOptions());
1636
+ assert.ok(selectPrompt.includes("git status"), `prompt should include command: ${selectPrompt}`);
1637
+ });
1638
+
1639
+ test("prompt includes file_path for file tools", async () => {
1640
+ let selectPrompt = "";
1641
+ const ui = {
1642
+ select: async (prompt: string) => {
1643
+ selectPrompt = prompt;
1644
+ return "Allow";
1645
+ },
1646
+ };
1647
+
1648
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1649
+ await handler!("Write", { file_path: "/tmp/test.txt", content: "hello" }, makeOptions());
1650
+ assert.ok(selectPrompt.includes("/tmp/test.txt"), `prompt should include file path: ${selectPrompt}`);
1651
+ });
1652
+
1653
+ test("uses title from options when available", async () => {
1654
+ let selectPrompt = "";
1655
+ const ui = {
1656
+ select: async (prompt: string) => {
1657
+ selectPrompt = prompt;
1658
+ return "Allow";
1659
+ },
1660
+ };
1661
+
1662
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1663
+ await handler!("WebFetch", {}, makeOptions({ title: "Claude wants to fetch: https://example.com" }));
1664
+ assert.ok(selectPrompt.includes("Claude wants to fetch: https://example.com"));
1665
+ });
1666
+
1667
+ test("falls back to default title when options.title is missing", async () => {
1668
+ let selectPrompt = "";
1669
+ const ui = {
1670
+ select: async (prompt: string) => {
1671
+ selectPrompt = prompt;
1672
+ return "Allow";
1673
+ },
1674
+ };
1675
+
1676
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1677
+ await handler!("WebFetch", { url: "https://example.com" }, makeOptions());
1678
+ assert.ok(selectPrompt.includes("Allow Claude Code to use: WebFetch?"));
1679
+ });
1680
+
1681
+ test("returns deny when signal is already aborted", async () => {
1682
+ const ui = {
1683
+ select: async () => { throw new Error("should not be called"); },
1684
+ };
1685
+
1686
+ const controller = new AbortController();
1687
+ controller.abort();
1688
+
1689
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1690
+ const result = await handler!("Bash", {}, makeOptions({ signal: controller.signal }));
1691
+
1692
+ assert.equal(result.behavior, "deny");
1693
+ assert.equal((result as any).message, "Aborted");
1694
+ });
1695
+
1696
+ test("returns deny when ui.select throws", async () => {
1697
+ const ui = {
1698
+ select: async () => { throw new Error("dialog crashed"); },
1699
+ };
1700
+
1701
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1702
+ const result = await handler!("Bash", {}, makeOptions());
1703
+
1704
+ assert.equal(result.behavior, "deny");
1705
+ assert.equal((result as any).message, "Aborted");
1706
+ });
1707
+
1708
+ test("buildSdkOptions passes canUseTool through extraOptions", () => {
1709
+ const canUseTool = async () => ({ behavior: "allow" as const, updatedInput: {}, toolUseID: "test" });
1710
+ const opts = buildSdkOptions("claude-sonnet-4-6", "test", undefined, { canUseTool });
1711
+ assert.equal(opts.canUseTool, canUseTool);
1712
+ });
1713
+
1714
+ test("Always Allow shows level picker and user broadens to base command", async () => {
1715
+ const cleanup = withIsolatedCwd();
1716
+ try {
1717
+ const prompts: string[] = [];
1718
+ const levelOpts: string[][] = [];
1719
+ let selectCall = 0;
1720
+ const ui = {
1721
+ select: async (prompt: string, opts: string[]) => {
1722
+ prompts.push(prompt);
1723
+ selectCall++;
1724
+ if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
1725
+ levelOpts.push(opts);
1726
+ return "Bash(gh:*)";
1727
+ },
1728
+ notify: () => {},
1729
+ };
1730
+
1731
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1732
+ const result = await handler!("Bash", { command: "gh pr list" }, makeOptions());
1733
+
1734
+ assert.equal(result.behavior, "allow");
1735
+ assert.deepEqual((result as any).updatedPermissions, [{
1736
+ type: "addRules",
1737
+ rules: [{ toolName: "Bash", ruleContent: "gh:*" }],
1738
+ behavior: "allow",
1739
+ destination: "localSettings",
1740
+ }]);
1741
+ // Second dialog offered every granularity level
1742
+ assert.deepEqual(levelOpts[0], [
1743
+ "Bash(gh:*)",
1744
+ "Bash(gh pr:*)",
1745
+ "Bash(gh pr list:*)",
1746
+ ]);
1747
+ assert.ok(prompts[1].includes("Save permission at which level?"));
1748
+ } finally {
1749
+ cleanup();
1750
+ }
1751
+ });
1752
+
1753
+ test("Always Allow narrows to mid-level pattern when user picks Bash(gh pr:*)", async () => {
1754
+ const cleanup = withIsolatedCwd();
1755
+ try {
1756
+ let selectCall = 0;
1757
+ const ui = {
1758
+ select: async (_p: string, opts: string[]) => {
1759
+ selectCall++;
1760
+ if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
1761
+ return "Bash(gh pr:*)";
1762
+ },
1763
+ notify: () => {},
1764
+ };
1765
+
1766
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1767
+ const result = await handler!("Bash", { command: "gh pr list --limit 5" }, makeOptions());
1768
+
1769
+ assert.equal(result.behavior, "allow");
1770
+ assert.deepEqual((result as any).updatedPermissions, [{
1771
+ type: "addRules",
1772
+ rules: [{ toolName: "Bash", ruleContent: "gh pr:*" }],
1773
+ behavior: "allow",
1774
+ destination: "localSettings",
1775
+ }]);
1776
+ } finally {
1777
+ cleanup();
1778
+ }
1779
+ });
1780
+
1781
+ test("Always Allow skips level picker when only one pattern is available", async () => {
1782
+ const cleanup = withIsolatedCwd();
1783
+ try {
1784
+ const prompts: string[] = [];
1785
+ const ui = {
1786
+ select: async (prompt: string, opts: string[]) => {
1787
+ prompts.push(prompt);
1788
+ return opts.find((o) => o.startsWith("Always Allow"))!;
1789
+ },
1790
+ notify: () => {},
1791
+ };
1792
+
1793
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1794
+ const result = await handler!("Bash", { command: "ls -la /tmp" }, makeOptions());
1795
+
1796
+ assert.equal(result.behavior, "allow");
1797
+ // "ls" has no subcommand tokens before the flag → single-option path
1798
+ assert.equal(prompts.length, 1, "should not show a second dialog");
1799
+ assert.deepEqual((result as any).updatedPermissions, [{
1800
+ type: "addRules",
1801
+ rules: [{ toolName: "Bash", ruleContent: "ls:*" }],
1802
+ behavior: "allow",
1803
+ destination: "localSettings",
1804
+ }]);
1805
+ } finally {
1806
+ cleanup();
1807
+ }
1808
+ });
1809
+
1810
+ test("Always Allow denies the tool when level picker is dismissed", async () => {
1811
+ const cleanup = withIsolatedCwd();
1812
+ try {
1813
+ const notified: string[] = [];
1814
+ let selectCall = 0;
1815
+ const ui = {
1816
+ select: async (_p: string, opts: string[]) => {
1817
+ selectCall++;
1818
+ if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
1819
+ return undefined; // user dismissed level picker
1820
+ },
1821
+ notify: (msg: string) => notified.push(msg),
1822
+ };
1823
+
1824
+ const handler = createClaudeCodeCanUseToolHandler(ui as any);
1825
+ const result = await handler!("Bash", { command: "gh pr list" }, makeOptions());
1826
+
1827
+ // Dismissing the level picker cancels the tool use — a one-time allow
1828
+ // would leave the spawned agent running even though the user bailed.
1829
+ assert.equal(result.behavior, "deny");
1830
+ assert.equal((result as any).updatedPermissions, undefined);
1831
+ assert.equal(notified.length, 0, "no 'Saved:' notification when nothing was saved");
1832
+ } finally {
1833
+ cleanup();
1834
+ }
1835
+ });
1836
+ });
1837
+
1838
+ // ---------------------------------------------------------------------------
1839
+ // buildBashPermissionPattern — smart permission granularity
1840
+ // ---------------------------------------------------------------------------
1841
+
1842
+ describe("buildBashPermissionPattern", () => {
1843
+ test("simple command wildcards all args", () => {
1844
+ assert.equal(buildBashPermissionPattern("ping -n 4 localhost"), "Bash(ping:*)");
1845
+ assert.equal(buildBashPermissionPattern("echo hello world"), "Bash(echo:*)");
1846
+ assert.equal(buildBashPermissionPattern("ls -la /tmp"), "Bash(ls:*)");
1847
+ assert.equal(buildBashPermissionPattern("node server.js"), "Bash(node:*)");
1848
+ });
1849
+
1850
+ test("git captures one subcommand", () => {
1851
+ assert.equal(buildBashPermissionPattern("git push origin main"), "Bash(git push:*)");
1852
+ assert.equal(buildBashPermissionPattern("git log --oneline"), "Bash(git log:*)");
1853
+ assert.equal(buildBashPermissionPattern("git status"), "Bash(git status:*)");
1854
+ });
1855
+
1856
+ test("gh captures two subcommands", () => {
1857
+ assert.equal(buildBashPermissionPattern("gh pr list"), "Bash(gh pr list:*)");
1858
+ assert.equal(buildBashPermissionPattern("gh pr create --title foo"), "Bash(gh pr create:*)");
1859
+ assert.equal(buildBashPermissionPattern("gh issue view 123"), "Bash(gh issue view:*)");
1860
+ });
1861
+
1862
+ test("npm captures one subcommand", () => {
1863
+ assert.equal(buildBashPermissionPattern("npm install lodash"), "Bash(npm install:*)");
1864
+ assert.equal(buildBashPermissionPattern("npm publish"), "Bash(npm publish:*)");
1865
+ assert.equal(buildBashPermissionPattern("npm run test"), "Bash(npm run:*)");
1866
+ });
1867
+
1868
+ test("npx captures package name", () => {
1869
+ assert.equal(buildBashPermissionPattern("npx vitest run"), "Bash(npx vitest:*)");
1870
+ assert.equal(buildBashPermissionPattern("npx --version"), "Bash(npx --version:*)");
1871
+ });
1872
+
1873
+ test("docker captures one subcommand", () => {
1874
+ assert.equal(buildBashPermissionPattern("docker ps -a"), "Bash(docker ps:*)");
1875
+ assert.equal(buildBashPermissionPattern("docker rm container1"), "Bash(docker rm:*)");
1876
+ });
1877
+
1878
+ test("aws captures two subcommands", () => {
1879
+ assert.equal(buildBashPermissionPattern("aws s3 cp file.txt s3://bucket/"), "Bash(aws s3 cp:*)");
1880
+ assert.equal(buildBashPermissionPattern("aws ec2 describe-instances"), "Bash(aws ec2 describe-instances:*)");
1881
+ });
1882
+
1883
+ test("skips sudo wrapper", () => {
1884
+ assert.equal(buildBashPermissionPattern("sudo ping localhost"), "Bash(ping:*)");
1885
+ assert.equal(buildBashPermissionPattern("sudo git push"), "Bash(git push:*)");
1886
+ });
1887
+
1888
+ test("skips env wrapper and VAR=val assignments", () => {
1889
+ assert.equal(buildBashPermissionPattern("env NODE_ENV=prod node server.js"), "Bash(node:*)");
1890
+ assert.equal(buildBashPermissionPattern("NODE_ENV=prod node server.js"), "Bash(node:*)");
1891
+ assert.equal(buildBashPermissionPattern("FOO=bar BAZ=qux git push"), "Bash(git push:*)");
1892
+ });
1893
+
1894
+ test("strips path from executable", () => {
1895
+ assert.equal(buildBashPermissionPattern("/usr/bin/git push"), "Bash(git push:*)");
1896
+ assert.equal(buildBashPermissionPattern("C:\\Windows\\ping.exe localhost"), "Bash(ping:*)");
1897
+ });
1898
+
1899
+ test("empty or whitespace-only command", () => {
1900
+ assert.equal(buildBashPermissionPattern(""), "Bash(*)");
1901
+ assert.equal(buildBashPermissionPattern(" "), "Bash(*)");
1902
+ });
1903
+
1904
+ test("chained commands — extracts pattern from the meaningful segment", () => {
1905
+ assert.equal(buildBashPermissionPattern("cd /foo && gh pr list --limit 5"), "Bash(gh pr list:*)");
1906
+ assert.equal(buildBashPermissionPattern("cd C:/Users/djeff/repos/gsd-2 && gh pr list --limit 5"), "Bash(gh pr list:*)");
1907
+ assert.equal(buildBashPermissionPattern("cd /tmp && git push origin main"), "Bash(git push:*)");
1908
+ assert.equal(buildBashPermissionPattern("export FOO=1 && npm install lodash"), "Bash(npm install:*)");
1909
+ assert.equal(buildBashPermissionPattern("mkdir -p out; docker ps -a"), "Bash(docker ps:*)");
1910
+ assert.equal(buildBashPermissionPattern("echo start || ping localhost"), "Bash(ping:*)");
1911
+ });
1912
+
1913
+ test("skips trailing || true / || : error suppressors", () => {
1914
+ assert.equal(
1915
+ buildBashPermissionPattern("cd C:/Users/djeff/repos/gsd-2 && gh pr create --dry-run --title \"test\" --body \"test\" 2>&1 || true"),
1916
+ "Bash(gh pr create:*)",
1917
+ );
1918
+ assert.equal(buildBashPermissionPattern("gh pr list || true"), "Bash(gh pr list:*)");
1919
+ assert.equal(buildBashPermissionPattern("git push || :"), "Bash(git push:*)");
1920
+ assert.equal(buildBashPermissionPattern("cd /tmp && npm install || echo failed"), "Bash(npm install:*)");
1921
+ });
1922
+
1923
+ test("single command is unaffected by chain extraction", () => {
1924
+ assert.equal(buildBashPermissionPattern("gh pr list"), "Bash(gh pr list:*)");
1925
+ assert.equal(buildBashPermissionPattern("git push origin main"), "Bash(git push:*)");
1926
+ });
1927
+ });
1928
+
1929
+ // ---------------------------------------------------------------------------
1930
+ // buildBashPermissionPatternOptions — granularity level menu
1931
+ // ---------------------------------------------------------------------------
1932
+
1933
+ describe("buildBashPermissionPatternOptions", () => {
1934
+ test("offers every prefix from base to full subcommand chain", () => {
1935
+ assert.deepEqual(buildBashPermissionPatternOptions("gh pr list"), [
1936
+ "Bash(gh:*)",
1937
+ "Bash(gh pr:*)",
1938
+ "Bash(gh pr list:*)",
1939
+ ]);
1940
+ assert.deepEqual(buildBashPermissionPatternOptions("git push origin main"), [
1941
+ "Bash(git:*)",
1942
+ "Bash(git push:*)",
1943
+ "Bash(git push origin:*)",
1944
+ "Bash(git push origin main:*)",
1945
+ ]);
1946
+ });
1947
+
1948
+ test("stops at first flag — flags are args, not verbs", () => {
1949
+ assert.deepEqual(buildBashPermissionPatternOptions("gh pr create --title foo"), [
1950
+ "Bash(gh:*)",
1951
+ "Bash(gh pr:*)",
1952
+ "Bash(gh pr create:*)",
1953
+ ]);
1954
+ assert.deepEqual(buildBashPermissionPatternOptions("git log --oneline"), [
1955
+ "Bash(git:*)",
1956
+ "Bash(git log:*)",
1957
+ ]);
1958
+ });
1959
+
1960
+ test("single-option when there is no subcommand to choose from", () => {
1961
+ assert.deepEqual(buildBashPermissionPatternOptions("ls -la /tmp"), ["Bash(ls:*)"]);
1962
+ assert.deepEqual(buildBashPermissionPatternOptions("ping -n 4 localhost"), ["Bash(ping:*)"]);
1963
+ assert.deepEqual(buildBashPermissionPatternOptions("node"), ["Bash(node:*)"]);
1964
+ });
1965
+
1966
+ test("extracts meaningful segment from compound commands", () => {
1967
+ assert.deepEqual(buildBashPermissionPatternOptions("cd /foo && gh pr list"), [
1968
+ "Bash(gh:*)",
1969
+ "Bash(gh pr:*)",
1970
+ "Bash(gh pr list:*)",
1971
+ ]);
1972
+ assert.deepEqual(buildBashPermissionPatternOptions("gh pr create --dry-run || true"), [
1973
+ "Bash(gh:*)",
1974
+ "Bash(gh pr:*)",
1975
+ "Bash(gh pr create:*)",
1976
+ ]);
1977
+ });
1978
+
1979
+ test("caps at three subcommand tokens to keep the menu short", () => {
1980
+ const result = buildBashPermissionPatternOptions("foo bar baz qux quux corge");
1981
+ // base + 3 sub tokens = 4 patterns max
1982
+ assert.equal(result.length, 4);
1983
+ assert.deepEqual(result, [
1984
+ "Bash(foo:*)",
1985
+ "Bash(foo bar:*)",
1986
+ "Bash(foo bar baz:*)",
1987
+ "Bash(foo bar baz qux:*)",
1988
+ ]);
1989
+ });
1990
+
1991
+ test("skips sudo/env wrappers like the single-pattern variant", () => {
1992
+ assert.deepEqual(buildBashPermissionPatternOptions("sudo git push origin"), [
1993
+ "Bash(git:*)",
1994
+ "Bash(git push:*)",
1995
+ "Bash(git push origin:*)",
1996
+ ]);
1997
+ assert.deepEqual(buildBashPermissionPatternOptions("NODE_ENV=prod node server.js"), [
1998
+ "Bash(node:*)",
1999
+ "Bash(node server.js:*)",
2000
+ ]);
2001
+ });
2002
+
2003
+ test("empty command returns the catch-all pattern", () => {
2004
+ assert.deepEqual(buildBashPermissionPatternOptions(""), ["Bash(*)"]);
2005
+ assert.deepEqual(buildBashPermissionPatternOptions(" "), ["Bash(*)"]);
2006
+ });
2007
+ });
2008
+
2009
+ // ---------------------------------------------------------------------------
2010
+ // bashCommandMatchesSavedRules — compound command bypass for saved rules
2011
+ // ---------------------------------------------------------------------------
2012
+
2013
+ describe("bashCommandMatchesSavedRules — compound command bypass", () => {
2014
+ let tempDir: string;
2015
+ let originalCwd: string;
2016
+
2017
+ // Create a temp project directory with .claude/settings.local.json
2018
+ function setupSettings(allow: string[]): void {
2019
+ const claudeDir = join(tempDir, ".claude");
2020
+ mkdirSync(claudeDir, { recursive: true });
2021
+ writeFileSync(
2022
+ join(claudeDir, "settings.local.json"),
2023
+ JSON.stringify({ permissions: { allow } }),
2024
+ );
2025
+ }
2026
+
2027
+ // biome-ignore lint/suspicious/noExplicitAny: test-only monkey-patch
2028
+ let origCwd: any;
2029
+
2030
+ // Monkey-patch process.cwd() to point at our temp dir
2031
+ function setCwd(dir: string): void {
2032
+ origCwd = process.cwd;
2033
+ process.cwd = () => dir;
2034
+ }
2035
+ function restoreCwd(): void {
2036
+ if (origCwd) process.cwd = origCwd;
2037
+ }
2038
+
2039
+ test("matches cd-prefixed compound command against saved prefix rule", () => {
2040
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2041
+ try {
2042
+ setupSettings(["Bash(gh pr list:*)"]);
2043
+ setCwd(tempDir);
2044
+ assert.equal(
2045
+ bashCommandMatchesSavedRules("cd /some/path && gh pr list --limit 5"),
2046
+ true,
2047
+ );
2048
+ } finally {
2049
+ restoreCwd();
2050
+ rmSync(tempDir, { recursive: true, force: true });
2051
+ }
2052
+ });
2053
+
2054
+ test("matches cd-prefixed compound command with exact subcommand", () => {
2055
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2056
+ try {
2057
+ setupSettings(["Bash(gh pr list:*)"]);
2058
+ setCwd(tempDir);
2059
+ assert.equal(
2060
+ bashCommandMatchesSavedRules("cd C:/Users/foo/repos/bar && gh pr list"),
2061
+ true,
2062
+ );
2063
+ } finally {
2064
+ restoreCwd();
2065
+ rmSync(tempDir, { recursive: true, force: true });
2066
+ }
2067
+ });
2068
+
2069
+ test("rejects when leading segment is not cd", () => {
2070
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2071
+ try {
2072
+ setupSettings(["Bash(gh pr list:*)"]);
2073
+ setCwd(tempDir);
2074
+ // "rm -rf /tmp" is not a cd command — should not auto-approve
2075
+ assert.equal(
2076
+ bashCommandMatchesSavedRules("rm -rf /tmp && gh pr list"),
2077
+ false,
2078
+ );
2079
+ } finally {
2080
+ restoreCwd();
2081
+ rmSync(tempDir, { recursive: true, force: true });
2082
+ }
2083
+ });
2084
+
2085
+ test("rejects when meaningful segment does not match any rule", () => {
2086
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2087
+ try {
2088
+ setupSettings(["Bash(gh pr list:*)"]);
2089
+ setCwd(tempDir);
2090
+ assert.equal(
2091
+ bashCommandMatchesSavedRules("cd /path && gh issue create --title foo"),
2092
+ false,
2093
+ );
2094
+ } finally {
2095
+ restoreCwd();
2096
+ rmSync(tempDir, { recursive: true, force: true });
2097
+ }
2098
+ });
2099
+
2100
+ test("matches simple (non-compound) commands against on-disk rules", () => {
2101
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2102
+ try {
2103
+ setupSettings(["Bash(gh pr list:*)"]);
2104
+ setCwd(tempDir);
2105
+ // Simple commands must also be checked — the SDK's in-memory cache
2106
+ // may be stale if the rule was added mid-session via "Always Allow"
2107
+ assert.equal(bashCommandMatchesSavedRules("gh pr list --limit 5"), true);
2108
+ assert.equal(bashCommandMatchesSavedRules("gh pr list"), true);
2109
+ } finally {
2110
+ restoreCwd();
2111
+ rmSync(tempDir, { recursive: true, force: true });
2112
+ }
2113
+ });
2114
+
2115
+ test("returns false for simple commands with no matching rule", () => {
2116
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2117
+ try {
2118
+ setupSettings(["Bash(gh pr list:*)"]);
2119
+ setCwd(tempDir);
2120
+ assert.equal(bashCommandMatchesSavedRules("gh issue list --limit 5"), false);
2121
+ } finally {
2122
+ restoreCwd();
2123
+ rmSync(tempDir, { recursive: true, force: true });
2124
+ }
2125
+ });
2126
+
2127
+ test("returns false when no settings file exists", () => {
2128
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2129
+ try {
2130
+ // No .claude/settings.local.json created
2131
+ setCwd(tempDir);
2132
+ assert.equal(
2133
+ bashCommandMatchesSavedRules("cd /path && gh pr list"),
2134
+ false,
2135
+ );
2136
+ } finally {
2137
+ restoreCwd();
2138
+ rmSync(tempDir, { recursive: true, force: true });
2139
+ }
2140
+ });
2141
+
2142
+ test("matches exact rule (non-prefix)", () => {
2143
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2144
+ try {
2145
+ setupSettings(["Bash(ping -n 4 localhost)"]);
2146
+ setCwd(tempDir);
2147
+ assert.equal(
2148
+ bashCommandMatchesSavedRules("cd /path && ping -n 4 localhost"),
2149
+ true,
2150
+ );
2151
+ } finally {
2152
+ restoreCwd();
2153
+ rmSync(tempDir, { recursive: true, force: true });
2154
+ }
2155
+ });
2156
+
2157
+ test("handles multiple cd segments before the meaningful command", () => {
2158
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2159
+ try {
2160
+ setupSettings(["Bash(npm install:*)"]);
2161
+ setCwd(tempDir);
2162
+ assert.equal(
2163
+ bashCommandMatchesSavedRules("cd /home && cd project && npm install lodash"),
2164
+ true,
2165
+ );
2166
+ } finally {
2167
+ restoreCwd();
2168
+ rmSync(tempDir, { recursive: true, force: true });
2169
+ }
2170
+ });
2171
+
2172
+ test("matches compound command with trailing || true suppressor", () => {
2173
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2174
+ try {
2175
+ setupSettings(["Bash(gh pr create:*)"]);
2176
+ setCwd(tempDir);
2177
+ assert.equal(
2178
+ bashCommandMatchesSavedRules('cd C:/Users/djeff/repos/gsd-2 && gh pr create --dry-run --title "test" --body "test" 2>&1 || true'),
2179
+ true,
2180
+ );
2181
+ assert.equal(
2182
+ bashCommandMatchesSavedRules("gh pr create --dry-run || true"),
2183
+ true,
2184
+ );
2185
+ assert.equal(
2186
+ bashCommandMatchesSavedRules("cd /tmp && git push || :"),
2187
+ false, // rule is for gh pr create, not git push
2188
+ );
2189
+ } finally {
2190
+ restoreCwd();
2191
+ rmSync(tempDir, { recursive: true, force: true });
2192
+ }
2193
+ });
2194
+
2195
+ test("reads rules from settings.json as well as settings.local.json", () => {
2196
+ tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
2197
+ try {
2198
+ const claudeDir = join(tempDir, ".claude");
2199
+ mkdirSync(claudeDir, { recursive: true });
2200
+ writeFileSync(
2201
+ join(claudeDir, "settings.json"),
2202
+ JSON.stringify({ permissions: { allow: ["Bash(git push:*)"] } }),
2203
+ );
2204
+ setCwd(tempDir);
2205
+ assert.equal(
2206
+ bashCommandMatchesSavedRules("cd /repo && git push origin main"),
2207
+ true,
2208
+ );
2209
+ } finally {
2210
+ restoreCwd();
2211
+ rmSync(tempDir, { recursive: true, force: true });
2212
+ }
1363
2213
  });
1364
2214
  });