gsd-pi 2.76.0-dev.82e249f7b → 2.76.0-dev.97f5583d9

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 (271) hide show
  1. package/dist/claude-cli-check.js +32 -3
  2. package/dist/mcp-server.d.ts +7 -0
  3. package/dist/mcp-server.js +35 -1
  4. package/dist/resource-loader.d.ts +1 -1
  5. package/dist/resource-loader.js +2 -8
  6. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  8. package/dist/resources/extensions/gsd/auto/phases.js +42 -1
  9. package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
  10. package/dist/resources/extensions/gsd/auto/session.js +12 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
  14. package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
  15. package/dist/resources/extensions/gsd/auto-recovery.js +13 -0
  16. package/dist/resources/extensions/gsd/auto-start.js +27 -18
  17. package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
  18. package/dist/resources/extensions/gsd/auto.js +55 -27
  19. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  21. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +51 -5
  24. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
  25. package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
  26. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  27. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  28. package/dist/resources/extensions/gsd/error-classifier.js +10 -3
  29. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  30. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  31. package/dist/resources/extensions/gsd/gsd-db.js +115 -7
  32. package/dist/resources/extensions/gsd/guided-flow.js +189 -0
  33. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  34. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  35. package/dist/resources/extensions/gsd/key-manager.js +6 -0
  36. package/dist/resources/extensions/gsd/model-router.js +36 -3
  37. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -9
  38. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  39. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  40. package/dist/resources/extensions/gsd/preferences.js +17 -17
  41. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  42. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  43. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  44. package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
  45. package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
  46. package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
  47. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  48. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  49. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  50. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  51. package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
  52. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  53. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  54. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  55. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  56. package/dist/web/standalone/.next/BUILD_ID +1 -1
  57. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  58. package/dist/web/standalone/.next/build-manifest.json +2 -2
  59. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  60. package/dist/web/standalone/.next/required-server-files.json +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.html +1 -1
  78. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  85. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  87. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  88. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  89. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  90. package/dist/web/standalone/server.js +1 -1
  91. package/package.json +1 -1
  92. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  93. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  94. package/packages/mcp-server/dist/remote-questions.js +732 -0
  95. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  96. package/packages/mcp-server/dist/server.d.ts +7 -0
  97. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  98. package/packages/mcp-server/dist/server.js +41 -4
  99. package/packages/mcp-server/dist/server.js.map +1 -1
  100. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  101. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  102. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  103. package/packages/mcp-server/package.json +2 -1
  104. package/packages/mcp-server/src/mcp-server.test.ts +30 -0
  105. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  106. package/packages/mcp-server/src/remote-questions.ts +916 -0
  107. package/packages/mcp-server/src/server.ts +62 -10
  108. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  109. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  110. package/packages/mcp-server/tsconfig.test.json +19 -0
  111. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  112. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
  113. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  114. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  115. package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
  116. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  117. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  118. package/packages/pi-ai/dist/providers/anthropic.js +8 -3
  119. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  120. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
  121. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
  122. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
  123. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
  124. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  125. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  127. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  128. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
  129. package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
  130. package/packages/pi-ai/src/providers/anthropic.ts +9 -3
  131. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
  132. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  133. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  134. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  136. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  137. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  138. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
  140. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  144. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  146. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  147. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  148. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  149. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
  151. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
  153. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  161. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  163. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  165. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  166. package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
  167. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  168. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  169. package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
  170. package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
  171. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  172. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  173. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  174. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  175. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  176. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  177. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  178. package/src/resources/extensions/gsd/auto/loop-deps.ts +13 -0
  179. package/src/resources/extensions/gsd/auto/phases.ts +66 -1
  180. package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
  181. package/src/resources/extensions/gsd/auto/session.ts +22 -0
  182. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
  183. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  184. package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
  185. package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
  186. package/src/resources/extensions/gsd/auto-recovery.ts +15 -0
  187. package/src/resources/extensions/gsd/auto-start.ts +29 -19
  188. package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
  189. package/src/resources/extensions/gsd/auto.ts +58 -27
  190. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
  191. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  192. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  193. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  194. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +53 -5
  195. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
  196. package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
  197. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  198. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  199. package/src/resources/extensions/gsd/error-classifier.ts +10 -3
  200. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  201. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  202. package/src/resources/extensions/gsd/gsd-db.ts +122 -7
  203. package/src/resources/extensions/gsd/guided-flow.ts +221 -0
  204. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  205. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  206. package/src/resources/extensions/gsd/journal.ts +2 -1
  207. package/src/resources/extensions/gsd/key-manager.ts +6 -0
  208. package/src/resources/extensions/gsd/model-router.ts +42 -1
  209. package/src/resources/extensions/gsd/pre-execution-checks.ts +36 -10
  210. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  211. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  212. package/src/resources/extensions/gsd/preferences.ts +17 -17
  213. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  214. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  215. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  216. package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
  217. package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
  218. package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
  219. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +119 -1
  220. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
  221. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  222. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
  223. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  224. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  225. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  226. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  227. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  228. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
  229. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  230. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
  232. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  233. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
  235. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  236. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  237. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  238. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -0
  239. package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
  240. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  241. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  242. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  243. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  244. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
  245. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +234 -0
  246. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  247. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  248. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
  249. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  250. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  251. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
  252. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  253. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  254. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  255. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  256. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  257. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
  258. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  259. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  260. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  261. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  262. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  263. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  264. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  265. package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
  266. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  267. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  268. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  269. package/src/resources/skills/write-docs/SKILL.md +2 -1
  270. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_buildManifest.js +0 -0
  271. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_ssgManifest.js +0 -0
@@ -12,12 +12,41 @@ import { execFileSync } from 'node:child_process';
12
12
  * `src/resources/extensions/gsd/pre-execution-checks.ts`.
13
13
  */
14
14
  export const CLAUDE_COMMAND = process.platform === 'win32' ? 'claude.cmd' : 'claude';
15
+ /**
16
+ * Ordered list of binary names to probe for the Claude Code CLI.
17
+ *
18
+ * Windows installs vary: npm-global installs produce a `claude.cmd` shim,
19
+ * direct binary installs produce `claude.exe`, and Git Bash wrappers may
20
+ * expose a bare `claude` shim. Try all three so no valid install is missed.
21
+ */
22
+ const CLAUDE_COMMAND_CANDIDATES = process.platform === 'win32' ? [CLAUDE_COMMAND, 'claude.exe', 'claude'] : [CLAUDE_COMMAND];
23
+ /**
24
+ * Try to run `args` against each candidate binary.
25
+ * Returns the output buffer on first success, throws the last error if all fail.
26
+ */
27
+ function execClaudeCheck(args) {
28
+ let lastError;
29
+ for (const command of CLAUDE_COMMAND_CANDIDATES) {
30
+ try {
31
+ return execFileSync(command, args, { timeout: 5_000, stdio: 'pipe' });
32
+ }
33
+ catch (error) {
34
+ lastError = error;
35
+ const code = error?.code;
36
+ // EINVAL can surface on Windows Git Bash for .cmd spawn failures.
37
+ if (code === 'ENOENT' || code === 'EINVAL')
38
+ continue;
39
+ throw error;
40
+ }
41
+ }
42
+ throw lastError ?? new Error(`Claude CLI not found (tried: ${CLAUDE_COMMAND_CANDIDATES.join(', ')})`);
43
+ }
15
44
  /**
16
45
  * Check if the `claude` binary is installed (regardless of auth state).
17
46
  */
18
47
  export function isClaudeBinaryInstalled() {
19
48
  try {
20
- execFileSync(CLAUDE_COMMAND, ['--version'], { timeout: 5_000, stdio: 'pipe' });
49
+ execClaudeCheck(['--version']);
21
50
  return true;
22
51
  }
23
52
  catch {
@@ -29,13 +58,13 @@ export function isClaudeBinaryInstalled() {
29
58
  */
30
59
  export function isClaudeCliReady() {
31
60
  try {
32
- execFileSync(CLAUDE_COMMAND, ['--version'], { timeout: 5_000, stdio: 'pipe' });
61
+ execClaudeCheck(['--version']);
33
62
  }
34
63
  catch {
35
64
  return false;
36
65
  }
37
66
  try {
38
- const output = execFileSync(CLAUDE_COMMAND, ['auth', 'status'], { timeout: 5_000, stdio: 'pipe' })
67
+ const output = execClaudeCheck(['auth', 'status'])
39
68
  .toString()
40
69
  .toLowerCase();
41
70
  return !(/not logged in|no credentials|unauthenticated|not authenticated/i.test(output));
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Minimal tool interface matching GSD's AgentTool shape.
3
3
  * Avoids a direct dependency on @gsd/pi-agent-core from this compiled module.
4
+ *
5
+ * `details` and `isError` are optional fields that runtime tool implementations
6
+ * may populate. The MCP transport drops non-standard fields, so the wrapper at
7
+ * the call site mirrors `details` into `structuredContent` and forwards
8
+ * `isError` directly. See #4472.
4
9
  */
5
10
  export interface McpToolDef {
6
11
  name: string;
@@ -13,6 +18,8 @@ export interface McpToolDef {
13
18
  data?: string;
14
19
  mimeType?: string;
15
20
  }>;
21
+ details?: Record<string, unknown>;
22
+ isError?: boolean;
16
23
  }>;
17
24
  }
18
25
  /**
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Strict plain-object guard. True only for object literals and
3
+ * `Object.create(null)` — not for `Date`, `URL`, `Map`, `Set`, class instances,
4
+ * or arrays. Used to gate `structuredContent` forwarding so the MCP transport
5
+ * receives only true JSON objects (the protocol contract). See #4477 review.
6
+ *
7
+ * Mirrored in `packages/mcp-server/src/workflow-tools.ts` for the
8
+ * `adaptExecutorResult` adapter on the workflow path. Keep both copies in
9
+ * sync if the contract definition needs to evolve.
10
+ */
11
+ function isPlainObject(value) {
12
+ if (value === null || typeof value !== 'object')
13
+ return false;
14
+ if (Array.isArray(value))
15
+ return false;
16
+ const proto = Object.getPrototypeOf(value);
17
+ return proto === null || proto === Object.prototype;
18
+ }
1
19
  // MCP SDK subpath imports use wildcard exports (./*) in @modelcontextprotocol/sdk's
2
20
  // package.json export map. The wildcard maps "./foo" → "./dist/cjs/foo" (no .js
3
21
  // suffix), so bare subpath specifiers like `${MCP_PKG}/server/stdio` resolve to
@@ -83,7 +101,23 @@ export async function startMcpServer(options) {
83
101
  // by stringifying into a text block so clients see the payload.
84
102
  return { type: 'text', text: JSON.stringify(block) };
85
103
  });
86
- return { content };
104
+ // Forward a tool's runtime `details` field to MCP's `structuredContent`
105
+ // channel. The protocol drops non-standard fields on the wire, so tools
106
+ // that populate `details` for client-side renderers (e.g. save_gate_result)
107
+ // would otherwise arrive empty on the other side. See #4472.
108
+ //
109
+ // Use a strict plain-object guard (prototype-chain check) rather than just
110
+ // `typeof === 'object' && !Array.isArray()` — Date, URL, Map, Set, and
111
+ // class instances would otherwise pass through and end up as
112
+ // `structuredContent`, violating the protocol's JSON-object contract.
113
+ // The mirror discipline applies in `workflow-tools.ts adaptExecutorResult`.
114
+ const base = { content };
115
+ if (isPlainObject(result.details)) {
116
+ base.structuredContent = result.details;
117
+ }
118
+ if (result.isError === true)
119
+ base.isError = true;
120
+ return base;
87
121
  }
88
122
  catch (err) {
89
123
  // AbortError from a cancelled tool surfaces as a normal error — MCP
@@ -21,6 +21,6 @@ export declare function getNewerManagedResourceVersion(agentDir: string, current
21
21
  *
22
22
  * Inspectable: `ls ~/.gsd/agent/extensions/`
23
23
  */
24
- export declare function initResources(agentDir: string): void;
24
+ export declare function initResources(agentDir: string, skillsDir?: string): void;
25
25
  export declare function hasStaleCompiledExtensionSiblings(extensionsDir: string, sourceDir?: string): boolean;
26
26
  export declare function buildResourceLoader(agentDir: string): DefaultResourceLoader;
@@ -506,7 +506,7 @@ function pruneRemovedBundledExtensions(manifest, agentDir) {
506
506
  *
507
507
  * Inspectable: `ls ~/.gsd/agent/extensions/`
508
508
  */
509
- export function initResources(agentDir) {
509
+ export function initResources(agentDir, skillsDir = join(homedir(), '.agents', 'skills')) {
510
510
  mkdirSync(agentDir, { recursive: true });
511
511
  const currentVersion = getBundledGsdVersion();
512
512
  const manifest = readManagedResourceManifest(agentDir);
@@ -538,13 +538,7 @@ export function initResources(agentDir) {
538
538
  // Sync bundled resources — overwrite so updates land on next launch.
539
539
  syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions'));
540
540
  syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents'));
541
- // Skills are no longer force-synced here. Users install skills via the
542
- // skills.sh CLI (`npx skills add <repo>`) into ~/.agents/skills/ which
543
- // is the industry-standard Agent Skills ecosystem directory.
544
- //
545
- // Migration from the legacy ~/.gsd/agent/skills/ directory is handled
546
- // above the manifest check so it runs on every launch (including retries
547
- // after partial copy failures).
541
+ syncResourceDir(join(resourcesDir, 'skills'), skillsDir);
548
542
  // Sync GSD-WORKFLOW.md to agentDir as a fallback for when GSD_WORKFLOW_PATH
549
543
  // env var is not set (e.g. fork/dev builds, alternative entry points).
550
544
  const workflowSrc = join(resourcesDir, 'GSD-WORKFLOW.md');
@@ -18,10 +18,11 @@ import { execFileSync } from "node:child_process";
18
18
  const CLAUDE_COMMAND = process.platform === "win32" ? "claude.cmd" : "claude";
19
19
  /**
20
20
  * Windows installs vary: some environments expose `claude.cmd` (npm shim),
21
- * others expose a `claude` shim on PATH (for example Git Bash wrappers).
22
- * Try both to avoid false "not installed" results in readiness checks.
21
+ * `claude.exe` (direct binary install), or a bare `claude` shim on PATH
22
+ * (for example Git Bash wrappers). Try all three to avoid false "not
23
+ * installed" results in readiness checks.
23
24
  */
24
- const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude"] : [CLAUDE_COMMAND];
25
+ const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude.exe", "claude"] : [CLAUDE_COMMAND];
25
26
  function execClaude(args) {
26
27
  let lastError;
27
28
  for (const command of CLAUDE_COMMAND_CANDIDATES) {
@@ -484,25 +484,23 @@ export function makeAbortedMessage(model, lastTextContent) {
484
484
  /**
485
485
  * Resolve the Claude Code permission mode for the current run.
486
486
  *
487
- * GSD subagents run underneath a host Claude Code session the user has
488
- * already consented to, and their work (edits, shell inspection, MCP calls)
489
- * spans the full workflow toolset. Defaulting the inner SDK to
490
- * `bypassPermissions` avoids per-tool approval prompts that offer no
491
- * meaningful safety beyond what the host session and the subagent prompts
492
- * already enforce. `GSD_CLAUDE_CODE_PERMISSION_MODE` lets security-conscious
493
- * users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
487
+ * Defaults to `acceptEdits`, which auto-approves file reads/edits but
488
+ * surfaces a permission dialog for dangerous operations (e.g. general Bash,
489
+ * Agent, WebFetch). This prevents tools outside the allowlist from being
490
+ * silently denied the SDK emits an `extension_ui_request` event so the
491
+ * user sees a prompt instead of a silent refusal that Claude Code mistakes
492
+ * for user rejection (#4383).
494
493
  *
495
- * Tradeoff: bypass means a prompt-injection payload read from an untrusted
496
- * file could trigger tool calls without a second gate. Accepted for GSD
497
- * because the workflow is explicit user intent and the alternative
498
- * (#4099) is continuous approval fatigue that blocks real work.
494
+ * Set `GSD_CLAUDE_CODE_PERMISSION_MODE` to `bypassPermissions` to restore
495
+ * the old always-approve behaviour, or to `default` / `plan` for stricter
496
+ * modes.
499
497
  */
500
498
  export async function resolveClaudePermissionMode(env = process.env) {
501
499
  const override = env.GSD_CLAUDE_CODE_PERMISSION_MODE?.trim();
502
500
  if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
503
501
  return override;
504
502
  }
505
- return "bypassPermissions";
503
+ return "acceptEdits";
506
504
  }
507
505
  // NOTE: These helpers intentionally mirror @gsd/pi-ai anthropic-shared
508
506
  // behavior so this extension remains typecheck-stable even when the published
@@ -554,7 +552,7 @@ function mapThinkingLevelToAnthropicEffort(level, modelId) {
554
552
  export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
555
553
  const { reasoning, ...sdkExtraOptions } = extraOptions;
556
554
  const mcpServers = buildWorkflowMcpServers();
557
- const permissionMode = overrides?.permissionMode ?? "bypassPermissions";
555
+ const permissionMode = overrides?.permissionMode ?? "acceptEdits";
558
556
  const disallowedTools = ["AskUserQuestion"];
559
557
  // Pre-authorize the safe built-ins and every registered workflow MCP
560
558
  // server's tools. `acceptEdits` mode (the interactive default) only
@@ -637,6 +635,68 @@ function normalizeToolResultContent(content) {
637
635
  }
638
636
  return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
639
637
  }
638
+ /**
639
+ * Extract a `details` payload from an MCP tool-result block.
640
+ *
641
+ * MCP's `CallToolResult` carries structured data in `structuredContent` — the
642
+ * protocol's supported channel for non-text payloads. Claude Code's synthetic
643
+ * user message may surface that field in one of two shapes depending on SDK
644
+ * version: as a sibling on the `mcp_tool_result` block itself, or as a
645
+ * dedicated content sub-block with `type: "structuredContent"`. Snake-case
646
+ * (`structured_content`) is accepted defensively in case a transport hop
647
+ * rewrites casing. All other shapes fall back to an empty object so callers
648
+ * can rely on `details` being present.
649
+ */
650
+ function extractStructuredDetailsFromBlock(block) {
651
+ const sibling = block.structuredContent ?? block.structured_content;
652
+ if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
653
+ return sibling;
654
+ }
655
+ if (Array.isArray(block.content)) {
656
+ for (const item of block.content) {
657
+ if (!item || typeof item !== "object")
658
+ continue;
659
+ const sub = item;
660
+ if (sub.type !== "structuredContent" && sub.type !== "structured_content")
661
+ continue;
662
+ const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
663
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
664
+ return payload;
665
+ }
666
+ }
667
+ }
668
+ // Return undefined (not {}) when no structured payload is present, matching
669
+ // the pre-#4477 contract where `details` was nullable. An empty-object
670
+ // sentinel is truthy and breaks downstream consumers that gate on
671
+ // `if (details)`. `undefined` matches the type of the field these results
672
+ // flow into (`Record<string, unknown> | undefined`).
673
+ return undefined;
674
+ }
675
+ /**
676
+ * True for items that are MCP `structuredContent` pseudo-blocks living inside
677
+ * a tool-result `content[]` array. These blocks carry the structured payload
678
+ * (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
679
+ * leak into the visible content rendered to the user — otherwise the renderer
680
+ * stringifies the JSON pseudo-block and shows it next to the actual tool
681
+ * output. See PR #4477 review (CodeRabbit, post-fix-round).
682
+ */
683
+ function isStructuredContentPseudoBlock(item) {
684
+ if (!item || typeof item !== "object")
685
+ return false;
686
+ const type = item.type;
687
+ return type === "structuredContent" || type === "structured_content";
688
+ }
689
+ /**
690
+ * Strip `structuredContent` pseudo-blocks from a tool-result content array
691
+ * before normalization. The structured payload is extracted via the sibling
692
+ * `structuredContent` field (or a dedicated extractor pass on the raw block);
693
+ * the visible content path must not include the pseudo-block itself.
694
+ */
695
+ function stripStructuredContentPseudoBlocks(content) {
696
+ if (!Array.isArray(content))
697
+ return content;
698
+ return content.filter((item) => !isStructuredContentPseudoBlock(item));
699
+ }
640
700
  /** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
641
701
  export function extractToolResultsFromSdkUserMessage(message) {
642
702
  const extracted = [];
@@ -657,8 +717,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
657
717
  extracted.push({
658
718
  toolUseId,
659
719
  result: {
660
- content: normalizeToolResultContent(block.content),
661
- details: {},
720
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
721
+ details: extractStructuredDetailsFromBlock(block),
662
722
  isError: block.is_error === true,
663
723
  },
664
724
  });
@@ -672,8 +732,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
672
732
  extracted.push({
673
733
  toolUseId,
674
734
  result: {
675
- content: normalizeToolResultContent(toolResult.content),
676
- details: {},
735
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
736
+ details: extractStructuredDetailsFromBlock(toolResult),
677
737
  isError: toolResult.is_error === true,
678
738
  },
679
739
  });
@@ -30,7 +30,8 @@ import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
30
30
  import { ensurePlanV2Graph } from "../uok/plan-v2.js";
31
31
  import { resolveUokFlags } from "../uok/flags.js";
32
32
  import { UokGateRunner } from "../uok/gate-runner.js";
33
- import { resetEvidence } from "../safety/evidence-collector.js";
33
+ import { resetEvidence, loadEvidenceFromDisk } from "../safety/evidence-collector.js";
34
+ import { parseUnitId } from "../unit-id.js";
34
35
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
35
36
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
36
37
  import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, supportsStructuredQuestions, } from "../workflow-mcp.js";
@@ -389,6 +390,8 @@ export async function runPreDispatch(ic, loopState) {
389
390
  loopState.recentUnits.length = 0;
390
391
  loopState.stuckRecoveryAttempts = 0;
391
392
  // Worktree lifecycle on milestone transition — merge current, enter next
393
+ // #2909: preflight — warn + stash dirty working tree before merge
394
+ const preflightTransition = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
392
395
  try {
393
396
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
394
397
  }
@@ -405,6 +408,10 @@ export async function runPreDispatch(ic, loopState) {
405
408
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
406
409
  return { action: "break", reason: "merge-failed" };
407
410
  }
411
+ // #2909: postflight — restore stashed changes after successful merge
412
+ if (preflightTransition.stashPushed) {
413
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
414
+ }
408
415
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
409
416
  deps.invalidateAllCaches();
410
417
  state = await deps.deriveState(s.basePath);
@@ -459,6 +466,8 @@ export async function runPreDispatch(ic, loopState) {
459
466
  if (incomplete.length === 0 && state.registry.length > 0) {
460
467
  // All milestones complete — merge milestone branch before stopping
461
468
  if (s.currentMilestoneId) {
469
+ // #2909: preflight — warn + stash dirty working tree before merge
470
+ const preflightAllComplete = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
462
471
  try {
463
472
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
464
473
  // Prevent stopAuto from attempting the same merge (#2645)
@@ -475,6 +484,10 @@ export async function runPreDispatch(ic, loopState) {
475
484
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
476
485
  return { action: "break", reason: "merge-failed" };
477
486
  }
487
+ // #2909: postflight — restore stashed changes after successful merge
488
+ if (preflightAllComplete.stashPushed) {
489
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
490
+ }
478
491
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
479
492
  }
480
493
  deps.sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone", basename(s.originalBasePath || s.basePath));
@@ -539,6 +552,8 @@ export async function runPreDispatch(ic, loopState) {
539
552
  if (state.phase === "complete") {
540
553
  // Milestone merge on complete (before closeout so branch state is clean)
541
554
  if (s.currentMilestoneId) {
555
+ // #2909: preflight — warn + stash dirty working tree before merge
556
+ const preflightComplete = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
542
557
  try {
543
558
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
544
559
  // Prevent stopAuto from attempting the same merge (#2645)
@@ -555,6 +570,10 @@ export async function runPreDispatch(ic, loopState) {
555
570
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
556
571
  return { action: "break", reason: "merge-failed" };
557
572
  }
573
+ // #2909: postflight — restore stashed changes after successful merge
574
+ if (preflightComplete.stashPushed) {
575
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
576
+ }
558
577
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
559
578
  }
560
579
  deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone", basename(s.originalBasePath || s.basePath));
@@ -1028,6 +1047,14 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1028
1047
  const safetyConfig = resolveSafetyHarnessConfig(prefs?.safety_harness);
1029
1048
  if (safetyConfig.enabled && safetyConfig.evidence_collection) {
1030
1049
  resetEvidence();
1050
+ // Restore persisted evidence so session-restart resumes don't produce
1051
+ // false-positive "no bash calls" warnings (Bug #4385).
1052
+ if (s.basePath && unitType === "execute-task") {
1053
+ const { milestone: eMid, slice: eSid, task: eTid } = parseUnitId(unitId);
1054
+ if (eMid && eSid && eTid) {
1055
+ loadEvidenceFromDisk(s.basePath, eMid, eSid, eTid);
1056
+ }
1057
+ }
1031
1058
  }
1032
1059
  // Only checkpoint code-executing units (not lifecycle/planning units)
1033
1060
  if (safetyConfig.enabled && safetyConfig.checkpoints && unitType === "execute-task") {
@@ -1430,6 +1457,20 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
1430
1457
  }
1431
1458
  else {
1432
1459
  // s.pendingVerificationRetry was set by postUnitPreVerification.
1460
+ // Emit a dedicated journal event so forensics can distinguish bounded
1461
+ // verification retries from genuine stuck-loop dispatch repetitions (#4540).
1462
+ const retryInfo = s.pendingVerificationRetry;
1463
+ deps.emitJournalEvent({
1464
+ ts: new Date().toISOString(),
1465
+ flowId: ic.flowId,
1466
+ seq: ic.nextSeq(),
1467
+ eventType: "artifact-verification-retry",
1468
+ data: {
1469
+ unitType: preUnitSnapshot?.type,
1470
+ unitId: retryInfo?.unitId,
1471
+ attempt: retryInfo?.attempt,
1472
+ },
1473
+ });
1433
1474
  // Continue the loop — next iteration will inject the retry context into the prompt.
1434
1475
  debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
1435
1476
  return { action: "continue" };
@@ -92,6 +92,33 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
92
92
  catch (e) {
93
93
  logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
94
94
  }
95
+ // ── Provider request-readiness pre-check (#4555) ──
96
+ // Verify the provider can accept requests before dispatching. If the token
97
+ // has expired since bootstrap, return cancelled immediately so the unit is
98
+ // not wasted on a guaranteed 401.
99
+ {
100
+ const provider = s.currentUnitModel?.provider ?? ctx.model?.provider;
101
+ const registry = ctx.modelRegistry;
102
+ if (provider && registry != null && typeof registry.isProviderRequestReady === "function") {
103
+ let ready = false;
104
+ try {
105
+ ready = registry.isProviderRequestReady(provider);
106
+ }
107
+ catch {
108
+ ready = false;
109
+ }
110
+ if (!ready) {
111
+ return {
112
+ status: "cancelled",
113
+ errorContext: {
114
+ message: `Provider ${provider} is not request-ready (login/token expired)`,
115
+ category: "provider",
116
+ isTransient: false,
117
+ },
118
+ };
119
+ }
120
+ }
121
+ }
95
122
  // ── Send the prompt ──
96
123
  debugLog("runUnit", { phase: "send-message", unitType, unitId });
97
124
  pi.sendMessage({ customType: "gsd-auto", content: prompt, display: s.verbose }, { triggerTurn: true });
@@ -79,6 +79,17 @@ export class AutoSession {
79
79
  lastStateRebuildAt = 0;
80
80
  // ── Sidecar queue ─────────────────────────────────────────────────────
81
81
  sidecarQueue = [];
82
+ // ── Pre-exec gate failure context (#4551) ───────────────────────────
83
+ /**
84
+ * Persisted when a pre-execution gate fails on a plan-slice or refine-slice
85
+ * unit. The planning → plan-slice dispatch rule reads this field and injects
86
+ * the failure details into the next re-dispatch prompt so the LLM can fix the
87
+ * specific issues instead of producing an identical plan.
88
+ *
89
+ * Cleared after it has been consumed (injected into the prompt) to avoid
90
+ * stale context bleeding into unrelated slices.
91
+ */
92
+ lastPreExecFailure = null;
82
93
  // ── Tool invocation errors (#2883) ──────────────────────────────────
83
94
  /** Set when a GSD tool execution ends with isError due to malformed/truncated
84
95
  * JSON arguments. Checked by postUnitPreVerification to break retry loops. */
@@ -199,6 +210,7 @@ export class AutoSession {
199
210
  this.sidecarQueue = [];
200
211
  this.rewriteAttemptCount = 0;
201
212
  this.consecutiveCompleteBootstraps = 0;
213
+ this.lastPreExecFailure = null;
202
214
  this.lastToolInvocationError = null;
203
215
  this.lastGitActionFailure = null;
204
216
  this.lastGitActionStatus = null;
@@ -451,18 +451,31 @@ export const DISPATCH_RULES = [
451
451
  },
452
452
  {
453
453
  name: "planning → plan-slice",
454
- match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry }) => {
454
+ match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry, session }) => {
455
455
  if (state.phase !== "planning")
456
456
  return null;
457
457
  if (!state.activeSlice)
458
458
  return missingSliceStop(mid, state.phase);
459
459
  const sid = state.activeSlice.id;
460
460
  const sTitle = state.activeSlice.title;
461
+ // #4551: Consume any persisted pre-exec failure for this slice so the
462
+ // re-dispatched prompt includes the exact blocked references. Clear the
463
+ // field immediately after reading to prevent stale context leaking into
464
+ // a later, unrelated plan-slice run.
465
+ const unitId = `${mid}/${sid}`;
466
+ let priorPreExecFailure;
467
+ if (session?.lastPreExecFailure?.unitId === unitId) {
468
+ priorPreExecFailure = {
469
+ blockingFindings: session.lastPreExecFailure.blockingFindings,
470
+ verdictExcerpt: session.lastPreExecFailure.verdictExcerpt,
471
+ };
472
+ session.lastPreExecFailure = null;
473
+ }
461
474
  return {
462
475
  action: "dispatch",
463
476
  unitType: "plan-slice",
464
- unitId: `${mid}/${sid}`,
465
- prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { sessionContextWindow, modelRegistry }),
477
+ unitId,
478
+ prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { sessionContextWindow, modelRegistry, priorPreExecFailure }),
466
479
  };
467
480
  },
468
481
  },
@@ -291,7 +291,7 @@ autoModeStartThinkingLevel) {
291
291
  // ADR-005: Adjust active tool set for the selected model's provider capabilities.
292
292
  // Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
293
293
  const activeToolNames = pi.getActiveTools();
294
- const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api);
294
+ const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api, model.provider);
295
295
  let finalToolNames = compatibleTools;
296
296
  // Fire adjust_tool_set hook — extensions can override the filtered tool set
297
297
  if (routingConfig.hooks !== false) {
@@ -30,7 +30,7 @@ import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookSta
30
30
  import { hasPendingCaptures, loadPendingCaptures, revertExecutorResolvedCaptures } from "./captures.js";
31
31
  import { debugLog } from "./debug-logger.js";
32
32
  import { runSafely } from "./auto-utils.js";
33
- import { getEvidence } from "./safety/evidence-collector.js";
33
+ import { getEvidence, clearEvidenceFromDisk } from "./safety/evidence-collector.js";
34
34
  import { validateFileChanges } from "./safety/file-change-validator.js";
35
35
  // crossReferenceEvidence available for future use when verification_evidence is stored in DB
36
36
  // import { crossReferenceEvidence, type ClaimedEvidence } from "./safety/evidence-cross-ref.js";
@@ -535,7 +535,7 @@ export async function postUnitPreVerification(pctx, opts) {
535
535
  if (taskRow) {
536
536
  const expectedOutput = taskRow.expected_output ?? [];
537
537
  const plannedFiles = taskRow.files ?? [];
538
- const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles);
538
+ const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles, safetyConfig.file_change_allowlist);
539
539
  if (audit && audit.violations.length > 0) {
540
540
  const warnings = audit.violations.filter(v => v.severity === "warning");
541
541
  for (const v of warnings) {
@@ -588,6 +588,16 @@ export async function postUnitPreVerification(pctx, opts) {
588
588
  debugLog("postUnit", { phase: "safety-content-validation", error: String(e) });
589
589
  }
590
590
  }
591
+ // Clear persisted evidence file now that post-unit processing is complete
592
+ // (Bug #4385 — prevents stale evidence from affecting retries of same unit ID).
593
+ if (safetyConfig.evidence_collection && s.currentUnit.type === "execute-task" && sMid && sSid && sTid) {
594
+ try {
595
+ clearEvidenceFromDisk(s.basePath, sMid, sSid, sTid);
596
+ }
597
+ catch (e) {
598
+ debugLog("postUnit", { phase: "safety-evidence-clear", error: String(e) });
599
+ }
600
+ }
591
601
  }
592
602
  }
593
603
  catch (e) {
@@ -950,12 +960,25 @@ export async function postUnitPostVerification(pctx) {
950
960
  const suffix = blockingChecks.length > 3 ? `\n \u2022 ...and ${blockingChecks.length - 3} more` : "";
951
961
  const evidenceNote = `\nSee ${sid}-PRE-EXEC-VERIFY.json for full details.`;
952
962
  ctx.ui.notify(`Pre-execution checks failed: ${blockingCount} blocking issue${blockingCount === 1 ? "" : "s"} found\n${details}${suffix}${evidenceNote}`, "error");
963
+ // Persist failure context so the next plan-slice re-dispatch can inject
964
+ // it into the prompt and break the infinite loop (#4551).
965
+ s.lastPreExecFailure = {
966
+ unitId: currentUnit.id,
967
+ blockingFindings: blockingChecks.map(c => `[${c.category}] ${c.target}: ${c.message}`),
968
+ verdictExcerpt: `status=${result.status}; ${blockingCount} blocking issue${blockingCount === 1 ? "" : "s"} detected`,
969
+ };
953
970
  preExecPauseNeeded = true;
954
971
  }
955
972
  else if (result.status === "warn") {
956
973
  ctx.ui.notify(`Pre-execution checks passed with warnings`, "warning");
957
974
  // Strict mode: treat warnings as blocking
958
975
  if (prefs?.enhanced_verification_strict === true) {
976
+ const warnChecks = result.checks.filter(c => !c.passed);
977
+ s.lastPreExecFailure = {
978
+ unitId: currentUnit.id,
979
+ blockingFindings: warnChecks.map(c => `[${c.category}] ${c.target}: ${c.message}`),
980
+ verdictExcerpt: `status=${result.status} (strict mode); ${warnChecks.length} warning${warnChecks.length === 1 ? "" : "s"} treated as blocking`,
981
+ };
959
982
  preExecPauseNeeded = true;
960
983
  }
961
984
  }
@@ -1223,6 +1223,20 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
1223
1223
  prependBlocks.push(`## Prior Sketch Scope (soft hint — non-binding)\n\n${options.softScopeHint.trim()}\n\n` +
1224
1224
  `This scope was captured during an earlier progressive-planning pass that was later disabled. Treat it as context only — you may plan beyond it if the work genuinely requires more scope. Do NOT treat this as a hard boundary.`);
1225
1225
  }
1226
+ // #4551: inject pre-exec failure context so the re-dispatched plan-slice
1227
+ // addresses the exact blocked references rather than reproducing the same plan.
1228
+ if (options?.priorPreExecFailure) {
1229
+ const { blockingFindings, verdictExcerpt } = options.priorPreExecFailure;
1230
+ const findingsList = blockingFindings.length > 0
1231
+ ? blockingFindings.map(f => `- ${f}`).join("\n")
1232
+ : "- (no specific findings recorded)";
1233
+ prependBlocks.push(`## Fix these specific issues from the prior pre-exec check\n\n` +
1234
+ `The previous plan-slice attempt was blocked by pre-execution validation.\n` +
1235
+ `Gate verdict: ${verdictExcerpt}\n\n` +
1236
+ `Blocked references that must be resolved in this plan:\n${findingsList}\n\n` +
1237
+ `Revise the plan so that every reference listed above is satisfied before execution begins. ` +
1238
+ `Do not reproduce the same file paths, package names, or task ordering that caused these failures.`);
1239
+ }
1226
1240
  return renderSlicePrompt({
1227
1241
  mid, sid, sTitle, base,
1228
1242
  level: level ?? resolveInlineLevel(),
@@ -220,12 +220,25 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
220
220
  // RESEARCH file. Without this, resolveExpectedArtifactPath returns null and
221
221
  // the retry/escalation machinery silently re-dispatches forever.
222
222
  //
223
+ // #4068: Also treat a PARALLEL-BLOCKER placeholder as a terminal completion
224
+ // so that timeout-recovery can write the blocker, have verifyExpectedArtifact
225
+ // return true, and let the dispatch loop advance past this unit. Without
226
+ // this, the blocker is written but verification still returns false, the unit
227
+ // is never cleared from unitDispatchCount, and on the next iteration the
228
+ // dispatch rule (which correctly skips parallel-research when PARALLEL-BLOCKER
229
+ // exists) returns null — leaving the loop stuck re-deriving indefinitely.
230
+ //
223
231
  // NOTE: this predicate mirrors the dispatch rule at
224
232
  // auto-dispatch.ts parallel-research-slices — keep the two in sync.
225
233
  if (unitType === "research-slice" && unitId.endsWith("/parallel-research")) {
226
234
  const { milestone: mid } = parseUnitId(unitId);
227
235
  if (!mid)
228
236
  return false;
237
+ // #4068: PARALLEL-BLOCKER written by timeout-recovery is a terminal state.
238
+ const blockerPath = resolveExpectedArtifactPath(unitType, unitId, base);
239
+ if (blockerPath && existsSync(blockerPath)) {
240
+ return true;
241
+ }
229
242
  const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
230
243
  if (!roadmapFile || !existsSync(roadmapFile)) {
231
244
  logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap missing`);