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
@@ -349,6 +349,122 @@ test("runUnit cancels before dispatch when model restore fails after newSession"
349
349
  ]);
350
350
  });
351
351
 
352
+ test("runUnit cancels before dispatch when provider is not request-ready (#4555)", async () => {
353
+ _resetPendingResolve();
354
+
355
+ const ctx = makeMockCtx();
356
+ ctx.model = { provider: "anthropic", id: "claude-opus-4-6" };
357
+ ctx.modelRegistry = {
358
+ isProviderRequestReady: (_provider: string) => false,
359
+ };
360
+
361
+ const pi = makeMockPi();
362
+ const s = makeMockSession();
363
+
364
+ const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
365
+
366
+ assert.equal(result.status, "cancelled");
367
+ assert.equal(result.errorContext?.category, "provider");
368
+ assert.match(
369
+ result.errorContext?.message ?? "",
370
+ /Provider anthropic is not request-ready/,
371
+ );
372
+ assert.equal(pi.calls.length, 0, "sendMessage must not be called when provider is not ready");
373
+ });
374
+
375
+ test("runUnit cancels before dispatch using currentUnitModel provider when set (#4555)", async () => {
376
+ _resetPendingResolve();
377
+
378
+ const ctx = makeMockCtx();
379
+ // ctx.model uses "openai" which IS ready — if the code ignores currentUnitModel
380
+ // and falls back to ctx.model.provider, the unit would NOT be cancelled. The
381
+ // test therefore differentiates: only a bug (wrong provider lookup) would pass.
382
+ ctx.model = { provider: "openai", id: "gpt-4o" };
383
+ // modelRegistry says anthropic is not ready but openai is
384
+ ctx.modelRegistry = {
385
+ isProviderRequestReady: (provider: string) => provider === "openai",
386
+ };
387
+
388
+ const pi = makeMockPi();
389
+ const s = makeMockSession();
390
+ // currentUnitModel overrides the provider used in the readiness check
391
+ s.currentUnitModel = { provider: "anthropic", id: "claude-opus-4-6" };
392
+
393
+ const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
394
+
395
+ assert.equal(result.status, "cancelled");
396
+ assert.equal(result.errorContext?.category, "provider");
397
+ assert.match(
398
+ result.errorContext?.message ?? "",
399
+ /Provider anthropic is not request-ready/,
400
+ );
401
+ assert.equal(pi.calls.length, 0, "sendMessage must not be called — anthropic (currentUnitModel) is not ready");
402
+ });
403
+
404
+ test("runUnit does not cancel before dispatch when provider is request-ready (#4555)", async () => {
405
+ _resetPendingResolve();
406
+
407
+ const ctx = makeMockCtx();
408
+ ctx.model = { provider: "anthropic", id: "claude-opus-4-6" };
409
+ ctx.modelRegistry = {
410
+ isProviderRequestReady: (_provider: string) => true,
411
+ };
412
+
413
+ const pi = makeMockPi();
414
+ const s = makeMockSession();
415
+
416
+ const resultPromise = runUnit(ctx, pi, s, "task", "T01", "prompt");
417
+
418
+ await new Promise((r) => setTimeout(r, 10));
419
+ resolveAgentEnd(makeEvent());
420
+
421
+ const result = await resultPromise;
422
+ assert.equal(result.status, "completed");
423
+ assert.equal(pi.calls.length, 1, "sendMessage must be called when provider is ready");
424
+ });
425
+
426
+ test("runUnit proceeds when modelRegistry is absent (no readiness check available) (#4555)", async () => {
427
+ _resetPendingResolve();
428
+
429
+ const ctx = makeMockCtx();
430
+ ctx.model = { provider: "anthropic", id: "claude-opus-4-6" };
431
+ // No modelRegistry on ctx — pre-check should be skipped
432
+
433
+ const pi = makeMockPi();
434
+ const s = makeMockSession();
435
+
436
+ const resultPromise = runUnit(ctx, pi, s, "task", "T01", "prompt");
437
+
438
+ await new Promise((r) => setTimeout(r, 10));
439
+ resolveAgentEnd(makeEvent());
440
+
441
+ const result = await resultPromise;
442
+ assert.equal(result.status, "completed");
443
+ assert.equal(pi.calls.length, 1);
444
+ });
445
+
446
+ test("runUnit proceeds when isProviderRequestReady throws (defensive) (#4555)", async () => {
447
+ _resetPendingResolve();
448
+
449
+ const ctx = makeMockCtx();
450
+ ctx.model = { provider: "anthropic", id: "claude-opus-4-6" };
451
+ ctx.modelRegistry = {
452
+ isProviderRequestReady: (_provider: string) => {
453
+ throw new Error("registry error");
454
+ },
455
+ };
456
+
457
+ const pi = makeMockPi();
458
+ const s = makeMockSession();
459
+
460
+ const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
461
+
462
+ // When the readyCheck throws, ready=false → unit cancelled
463
+ assert.equal(result.status, "cancelled");
464
+ assert.equal(result.errorContext?.category, "provider");
465
+ assert.equal(pi.calls.length, 0);
466
+ });
467
+
352
468
  // ─── Structural assertions ───────────────────────────────────────────────────
353
469
 
354
470
  test("auto-loop.ts exports autoLoop, runUnit, resolveAgentEnd", async () => {
@@ -409,7 +525,7 @@ test("auto/phases.ts: selectAndApplyModel called exactly once and before updateP
409
525
  // Extract the runUnitPhase function body
410
526
  const fnStart = src.indexOf("export async function runUnitPhase");
411
527
  assert.ok(fnStart > 0, "runUnitPhase should exist in phases.ts");
412
- const fnBody = src.slice(fnStart, fnStart + 12000);
528
+ const fnBody = src.slice(fnStart, fnStart + 16000);
413
529
 
414
530
  // selectAndApplyModel must appear exactly once
415
531
  const allOccurrences = [...fnBody.matchAll(/selectAndApplyModel\(/g)];
@@ -497,6 +613,8 @@ function makeMockDeps(
497
613
  autoWorktreeBranch: () => "auto/M001",
498
614
  resolveMilestoneFile: () => null,
499
615
  reconcileMergeState: () => "clean",
616
+ preflightCleanRoot: () => ({ stashPushed: false, summary: "" }),
617
+ postflightPopStash: () => {},
500
618
  getLedger: () => null,
501
619
  getProjectTotals: () => ({ cost: 0 }),
502
620
  formatCost: (c: number) => `$${c.toFixed(2)}`,
@@ -39,6 +39,18 @@ test("auto.ts validates milestone before restoring paused session (#1664)", () =
39
39
  source.includes('resolveMilestoneFile(base, meta.milestoneId, "SUMMARY")'),
40
40
  "auto.ts must check for SUMMARY file to detect completed milestones",
41
41
  );
42
+
43
+ // Resume path must sanitize paused session file metadata before unlink/recovery.
44
+ assert.ok(
45
+ source.includes("normalizeSessionFilePath(meta.sessionFile ?? null)"),
46
+ "auto.ts must sanitize paused-session metadata sessionFile before using it",
47
+ );
48
+
49
+ // Pause path must sanitize live session file path before persisting metadata.
50
+ assert.ok(
51
+ source.includes("normalizeSessionFilePath(ctx?.sessionManager?.getSessionFile() ?? null)"),
52
+ "auto.ts must sanitize sessionManager getSessionFile output before persisting",
53
+ );
42
54
  });
43
55
 
44
56
  // ─── Filesystem validation unit tests ───────────────────────────────────────
@@ -775,6 +775,55 @@ test("#4414: parallel-research sentinel path does not collide with RESEARCH suff
775
775
  }
776
776
  });
777
777
 
778
+ test("#4068: verifyExpectedArtifact parallel-research treats PARALLEL-BLOCKER as terminal completion", () => {
779
+ // Regression: when a parallel-research unit times out and the timeout-recovery
780
+ // machinery writes a PARALLEL-BLOCKER placeholder, verifyExpectedArtifact must
781
+ // return true so the dispatch loop can advance. Previously it only returned
782
+ // true when every slice had a RESEARCH file — meaning a timeout always left
783
+ // verifyExpectedArtifact returning false, the unit was never cleared from
784
+ // unitDispatchCount, and the dispatch rule re-fired on the next iteration
785
+ // (infinite loop, issue #4068 / #4355).
786
+ const base = makeTmpBase();
787
+ try {
788
+ // Write a minimal roadmap
789
+ writeFileSync(
790
+ join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
791
+ [
792
+ "# M001: Timeout Test",
793
+ "",
794
+ "## Slices",
795
+ "",
796
+ "- [ ] **S01: Alpha** `risk:low` `depends:[]`",
797
+ "- [ ] **S02: Beta** `risk:low` `depends:[]`",
798
+ "",
799
+ ].join("\n"),
800
+ "utf-8",
801
+ );
802
+
803
+ // No RESEARCH files written — subagents timed out
804
+ clearParseCache();
805
+ invalidateAllCaches();
806
+
807
+ // Simulate timeout-recovery writing the PARALLEL-BLOCKER placeholder
808
+ const blockerPath = resolveExpectedArtifactPath("research-slice", "M001/parallel-research", base);
809
+ assert.ok(blockerPath, "PARALLEL-BLOCKER path must resolve for parallel-research unit");
810
+ writeFileSync(blockerPath!, "# BLOCKER — timeout recovery\n\n**Reason**: hard timeout.\n", "utf-8");
811
+
812
+ clearParseCache();
813
+ invalidateAllCaches();
814
+
815
+ // After blocker is written, verifyExpectedArtifact must return true
816
+ // so the dispatch loop treats this unit as complete and moves on.
817
+ assert.equal(
818
+ verifyExpectedArtifact("research-slice", "M001/parallel-research", base),
819
+ true,
820
+ "#4068: PARALLEL-BLOCKER on disk must satisfy verifyExpectedArtifact so the loop does not re-dispatch",
821
+ );
822
+ } finally {
823
+ cleanup(base);
824
+ }
825
+ });
826
+
778
827
  test("#4414: verifyExpectedArtifact parallel-research succeeds when all research-ready slices have RESEARCH", () => {
779
828
  const base = makeTmpBase();
780
829
  try {
@@ -0,0 +1,186 @@
1
+ /**
2
+ * clean-root-preflight.test.ts — Regression tests for #2909.
3
+ *
4
+ * Tests that preflightCleanRoot warns + stashes on dirty trees,
5
+ * is a no-op on clean trees, and that postflightPopStash restores
6
+ * stashed changes after a merge.
7
+ */
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, realpathSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { tmpdir } from "node:os";
14
+ import { execSync } from "node:child_process";
15
+
16
+ import { preflightCleanRoot, postflightPopStash } from "../clean-root-preflight.ts";
17
+
18
+ function run(cmd: string, cwd: string): string {
19
+ return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
20
+ }
21
+
22
+ function createTempRepo(): string {
23
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-preflight-test-")));
24
+ run("git init", dir);
25
+ run("git config user.email test@example.com", dir);
26
+ run("git config user.name Test", dir);
27
+ writeFileSync(join(dir, "README.md"), "# test\n");
28
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
29
+ writeFileSync(join(dir, ".gsd", "STATE.md"), "# State\n");
30
+ run("git add .", dir);
31
+ run("git commit -m init", dir);
32
+ run("git branch -M main", dir);
33
+ return dir;
34
+ }
35
+
36
+ // ── Clean tree: fast-path returns immediately without stashing ─────────────
37
+
38
+ test("preflightCleanRoot — clean tree returns stashPushed=false and emits no notifications", () => {
39
+ const repo = createTempRepo();
40
+ try {
41
+ const notifications: Array<{ msg: string; level: string }> = [];
42
+ const result = preflightCleanRoot(repo, "M001", (msg, level) => {
43
+ notifications.push({ msg, level });
44
+ });
45
+
46
+ assert.equal(result.stashPushed, false, "stashPushed must be false for clean tree");
47
+ assert.equal(result.summary, "", "summary must be empty for clean tree");
48
+ assert.equal(notifications.length, 0, "no notifications on clean tree");
49
+
50
+ // Verify no stash was created
51
+ const stashList = run("git stash list", repo);
52
+ assert.equal(stashList, "", "no stash entry on clean tree");
53
+ } finally {
54
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
55
+ }
56
+ });
57
+
58
+ // ── Dirty tree: warns, stashes, returns stashPushed=true ──────────────────
59
+
60
+ test("preflightCleanRoot — dirty tree warns user and auto-stashes", () => {
61
+ const repo = createTempRepo();
62
+ try {
63
+ // Dirty an existing tracked file
64
+ writeFileSync(join(repo, "README.md"), "# locally modified\n");
65
+
66
+ const notifications: Array<{ msg: string; level: string }> = [];
67
+ const result = preflightCleanRoot(repo, "M002", (msg, level) => {
68
+ notifications.push({ msg, level });
69
+ });
70
+
71
+ assert.equal(result.stashPushed, true, "stashPushed must be true when tree was dirty");
72
+ assert.ok(result.summary.length > 0, "summary must be non-empty when stash was pushed");
73
+
74
+ // A warning notification must have been emitted before stashing
75
+ assert.ok(
76
+ notifications.some(n => n.level === "warning" && n.msg.includes("M002")),
77
+ "warning notification must mention the milestone ID",
78
+ );
79
+
80
+ // Working tree must now be clean (stash pushed)
81
+ const status = run("git status --porcelain", repo);
82
+ assert.equal(status, "", "working tree must be clean after stash push");
83
+
84
+ // The stash entry must exist
85
+ const stashList = run("git stash list", repo);
86
+ assert.ok(stashList.includes("gsd-preflight-stash"), "stash entry must be named gsd-preflight-stash");
87
+ } finally {
88
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
89
+ }
90
+ });
91
+
92
+ // ── Untracked files are also stashed ─────────────────────────────────────
93
+
94
+ test("preflightCleanRoot — untracked file triggers stash with --include-untracked", () => {
95
+ const repo = createTempRepo();
96
+ try {
97
+ // Add an untracked file
98
+ writeFileSync(join(repo, "untracked.ts"), "export const x = 1;\n");
99
+
100
+ const notifications: Array<{ msg: string; level: string }> = [];
101
+ const result = preflightCleanRoot(repo, "M003", (msg, level) => {
102
+ notifications.push({ msg, level });
103
+ });
104
+
105
+ assert.equal(result.stashPushed, true, "stashPushed must be true for untracked file");
106
+
107
+ const status = run("git status --porcelain", repo);
108
+ assert.equal(status, "", "working tree must be clean after stash push");
109
+ } finally {
110
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
111
+ }
112
+ });
113
+
114
+ // ── postflightPopStash: restores stashed changes ──────────────────────────
115
+
116
+ test("postflightPopStash — restores stashed changes and emits info notification", () => {
117
+ const repo = createTempRepo();
118
+ try {
119
+ // Dirty the working tree
120
+ writeFileSync(join(repo, "README.md"), "# stash me\n");
121
+
122
+ const preNotifications: Array<{ msg: string; level: string }> = [];
123
+ const preflight = preflightCleanRoot(repo, "M004", (msg, level) => {
124
+ preNotifications.push({ msg, level });
125
+ });
126
+ assert.equal(preflight.stashPushed, true, "preflight must have stashed");
127
+
128
+ // Simulate the merge (just a no-op commit here)
129
+ writeFileSync(join(repo, "merged.ts"), "export const merged = true;\n");
130
+ run("git add .", repo);
131
+ run('git commit -m "simulate merge"', repo);
132
+
133
+ const postNotifications: Array<{ msg: string; level: string }> = [];
134
+ postflightPopStash(repo, "M004", (msg, level) => {
135
+ postNotifications.push({ msg, level });
136
+ });
137
+
138
+ // The stashed README.md change must be restored
139
+ const content = readFileSync(join(repo, "README.md"), "utf-8");
140
+ assert.equal(content.replace(/\r\n/g, "\n"), "# stash me\n", "stashed file must be restored");
141
+
142
+ // An info notification must have been emitted
143
+ assert.ok(
144
+ postNotifications.some(n => n.level === "info" && n.msg.includes("M004")),
145
+ "info notification must mention milestone ID after pop",
146
+ );
147
+
148
+ // Stash list must be empty
149
+ const stashList = run("git stash list", repo);
150
+ assert.equal(stashList, "", "stash list must be empty after pop");
151
+ } finally {
152
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
153
+ }
154
+ });
155
+
156
+ // ── Round-trip: preflight + merge + postflight preserves changes ──────────
157
+
158
+ test("preflight + merge + postflight round-trip preserves uncommitted changes", () => {
159
+ const repo = createTempRepo();
160
+ try {
161
+ const originalContent = "# my local work\n";
162
+ writeFileSync(join(repo, "README.md"), originalContent);
163
+
164
+ // Preflight: stash
165
+ const preflight = preflightCleanRoot(repo, "M005", () => {});
166
+ assert.equal(preflight.stashPushed, true, "must have stashed");
167
+
168
+ // Merge: introduce a new file (no overlap with README.md)
169
+ writeFileSync(join(repo, "feature.ts"), "export const feature = true;\n");
170
+ run("git add feature.ts", repo);
171
+ run('git commit -m "feat: add feature"', repo);
172
+
173
+ // Postflight: pop stash
174
+ postflightPopStash(repo, "M005", () => {});
175
+
176
+ // README.md must still have our local content
177
+ const restored = readFileSync(join(repo, "README.md"), "utf-8");
178
+ assert.equal(restored.replace(/\r\n/g, "\n"), originalContent, "local changes must survive merge");
179
+
180
+ // feature.ts must also exist (the merge commit landed)
181
+ const featureContent = readFileSync(join(repo, "feature.ts"), "utf-8");
182
+ assert.ok(featureContent.includes("feature"), "merged feature must be present");
183
+ } finally {
184
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
185
+ }
186
+ });
@@ -0,0 +1,123 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ import {
8
+ buildSnapshot,
9
+ readCompactionSnapshot,
10
+ writeCompactionSnapshot,
11
+ DEFAULT_SNAPSHOT_BYTES,
12
+ } from '../compaction-snapshot.ts';
13
+ import { closeDatabase, openDatabase } from '../gsd-db.ts';
14
+ import { createMemory } from '../memory-store.ts';
15
+ import { executeResume } from '../tools/resume-tool.ts';
16
+
17
+ function freshBase(): string {
18
+ return mkdtempSync(join(tmpdir(), 'gsd-snap-'));
19
+ }
20
+
21
+ function cleanup(dir: string): void {
22
+ rmSync(dir, { recursive: true, force: true });
23
+ }
24
+
25
+ test('buildSnapshot: renders memories, exec history, and active context', () => {
26
+ const snap = buildSnapshot({
27
+ generatedAt: new Date('2026-04-20T12:00:00.000Z'),
28
+ activeContext: 'M001 / S01 / T01 — wire gsd_exec',
29
+ memories: [
30
+ { id: 'MEM001', category: 'gotcha', content: 'FTS5 needs Porter tokenizer', confidence: 0.9,
31
+ source_unit_type: null, source_unit_id: null, created_at: '', updated_at: '',
32
+ superseded_by: null, hit_count: 0, scope: 'project', seq: 1, tags: [], structured_fields: null },
33
+ ],
34
+ execHistory: [
35
+ {
36
+ id: 'abc',
37
+ runtime: 'bash',
38
+ purpose: 'count TODOs',
39
+ started_at: '', finished_at: '', duration_ms: 10,
40
+ exit_code: 0, signal: null, timed_out: false,
41
+ stdout_bytes: 1, stderr_bytes: 0, stdout_truncated: false, stderr_truncated: false,
42
+ stdout_path: '/tmp/abc.stdout', stderr_path: '/tmp/abc.stderr', meta_path: '/tmp/abc.meta.json',
43
+ },
44
+ ],
45
+ });
46
+ assert.match(snap, /Active context/);
47
+ assert.match(snap, /M001 \/ S01 \/ T01/);
48
+ assert.match(snap, /FTS5 needs Porter tokenizer/);
49
+ assert.match(snap, /\[abc\] bash exit:0 — count TODOs/);
50
+ });
51
+
52
+ test('buildSnapshot: enforces the byte cap with a truncation marker', () => {
53
+ const longMemories = Array.from({ length: 50 }, (_v, i) => ({
54
+ id: `MEM${String(i).padStart(3, '0')}`,
55
+ category: 'gotcha',
56
+ content: 'x'.repeat(200),
57
+ confidence: 0.8,
58
+ source_unit_type: null,
59
+ source_unit_id: null,
60
+ created_at: '',
61
+ updated_at: '',
62
+ superseded_by: null,
63
+ hit_count: 0,
64
+ scope: 'project',
65
+ seq: i,
66
+ tags: [] as string[],
67
+ structured_fields: null,
68
+ }));
69
+ const snap = buildSnapshot(
70
+ { generatedAt: new Date(), memories: longMemories, execHistory: [] },
71
+ { maxBytes: 512, maxMemories: 50 },
72
+ );
73
+ assert.ok(Buffer.byteLength(snap, 'utf-8') <= 512, 'should respect cap');
74
+ assert.match(snap, /\[truncated\]/, 'should include truncation marker');
75
+ });
76
+
77
+ test('buildSnapshot: handles empty state with an explanatory placeholder', () => {
78
+ const snap = buildSnapshot({ generatedAt: new Date(), memories: [], execHistory: [] });
79
+ assert.match(snap, /_No durable memories/);
80
+ assert.ok(Buffer.byteLength(snap, 'utf-8') <= DEFAULT_SNAPSHOT_BYTES);
81
+ });
82
+
83
+ test('writeCompactionSnapshot + readCompactionSnapshot + executeResume: end-to-end', () => {
84
+ const base = freshBase();
85
+ try {
86
+ openDatabase(':memory:');
87
+ createMemory({ category: 'architecture', content: 'Single-writer DB through gsd-db.ts', confidence: 0.95 });
88
+ createMemory({ category: 'convention', content: 'Prefer typed helpers over raw SQL', confidence: 0.9 });
89
+
90
+ const out = writeCompactionSnapshot(base, { activeContext: 'M099 resume check' });
91
+ assert.ok(out.path.endsWith('last-snapshot.md'));
92
+ assert.ok(out.bytes > 0);
93
+ assert.equal(out.memories, 2);
94
+
95
+ const contents = readCompactionSnapshot(base);
96
+ assert.ok(contents);
97
+ assert.match(contents!, /Single-writer DB through gsd-db\.ts/);
98
+ assert.match(contents!, /M099 resume check/);
99
+
100
+ const tool = executeResume({}, { baseDir: base });
101
+ assert.ok(!tool.isError);
102
+ assert.equal(tool.details.found, true);
103
+ assert.match(tool.content[0].text, /Single-writer DB through gsd-db\.ts/);
104
+
105
+ // also verify the file content matches (without trailing newline)
106
+ const raw = readFileSync(out.path, 'utf-8');
107
+ assert.ok(raw.endsWith('\n'));
108
+ } finally {
109
+ closeDatabase();
110
+ cleanup(base);
111
+ }
112
+ });
113
+
114
+ test('executeResume: reports friendly empty state when no snapshot exists', () => {
115
+ const base = freshBase();
116
+ try {
117
+ const result = executeResume({}, { baseDir: base });
118
+ assert.equal(result.details.found, false);
119
+ assert.match(result.content[0].text, /No snapshot found/);
120
+ } finally {
121
+ cleanup(base);
122
+ }
123
+ });
@@ -125,9 +125,9 @@ console.log('\n=== complete-slice: schema v6 migration ===');
125
125
 
126
126
  const adapter = _getAdapter()!;
127
127
 
128
- // Verify schema version is current (v21ADR-013 structured_fields column)
128
+ // Verify schema version is current (v22quality_gates DDL fix)
129
129
  const versionRow = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
130
- assertEq(versionRow?.['v'], 21, 'schema version should be 21');
130
+ assertEq(versionRow?.['v'], 22, 'schema version should be 22');
131
131
 
132
132
  // Verify slices table has full_summary_md and full_uat_md columns
133
133
  const cols = adapter.prepare("PRAGMA table_info(slices)").all();
@@ -109,9 +109,9 @@ console.log('\n=== complete-task: schema v5 migration ===');
109
109
 
110
110
  const adapter = _getAdapter()!;
111
111
 
112
- // Verify schema version is current (v21ADR-013 structured_fields column)
112
+ // Verify schema version is current (v22quality_gates DDL fix)
113
113
  const versionRow = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
114
- assertEq(versionRow?.['v'], 21, 'schema version should be 21');
114
+ assertEq(versionRow?.['v'], 22, 'schema version should be 22');
115
115
 
116
116
  // Verify all 4 new tables exist
117
117
  const tables = adapter.prepare(
@@ -179,6 +179,8 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
179
179
  autoWorktreeBranch: () => "auto/M001",
180
180
  resolveMilestoneFile: () => null,
181
181
  reconcileMergeState: () => "clean",
182
+ preflightCleanRoot: () => ({ stashPushed: false, summary: "" }),
183
+ postflightPopStash: () => {},
182
184
  getLedger: () => null,
183
185
  getProjectTotals: () => ({ cost: 0 }),
184
186
  formatCost: (c: number) => `$${c.toFixed(2)}`,
@@ -768,6 +768,37 @@ test("runProviderChecks detects claude.cmd in PATH on Windows (#4503)", { skip:
768
768
  });
769
769
  });
770
770
 
771
+ test("runProviderChecks detects claude.exe in PATH on Windows (#4548)", { skip: process.platform !== "win32" }, () => {
772
+ const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cc-exe-home-")));
773
+ const binDir = join(tmpHome, "bin");
774
+ mkdirSync(binDir, { recursive: true });
775
+
776
+ // Some Windows installs ship a direct claude.exe binary (not a .cmd shim).
777
+ const fakeClaudeExe = join(binDir, "claude.exe");
778
+ writeFileSync(fakeClaudeExe, "");
779
+
780
+ withEnv({
781
+ HOME: tmpHome,
782
+ ANTHROPIC_API_KEY: undefined,
783
+ ANTHROPIC_OAUTH_TOKEN: undefined,
784
+ COPILOT_GITHUB_TOKEN: undefined,
785
+ GH_TOKEN: undefined,
786
+ GITHUB_TOKEN: undefined,
787
+ PATH: `${binDir};${process.env.PATH ?? ""}`,
788
+ PATHEXT: ".COM;.EXE;.BAT;.CMD",
789
+ }, () => {
790
+ try {
791
+ const results = runProviderChecks();
792
+ const anthropic = results.find(r => r.name === "anthropic");
793
+ assert.ok(anthropic, "anthropic result should exist");
794
+ assert.equal(anthropic!.status, "ok", "should be ok when claude.exe is in PATH (#4548)");
795
+ assert.ok(anthropic!.message.toLowerCase().includes("claude"), "should mention claude-code as source");
796
+ } finally {
797
+ rmSync(tmpHome, { recursive: true, force: true });
798
+ }
799
+ });
800
+ });
801
+
771
802
  test("PROVIDER_ROUTES includes google-gemini-cli as route for google (#2922)", async () => {
772
803
  const { readFileSync: readFS } = await import("node:fs");
773
804
  const { dirname: dirn, join: joinPath } = await import("node:path");
@@ -42,7 +42,7 @@ describe("double mergeAndExit guard (#2645)", () => {
42
42
  const allCompleteIdx = phasesSrc.indexOf("incomplete.length === 0");
43
43
  assert.ok(allCompleteIdx > 0, "phases.ts should have an all-milestones-complete check");
44
44
 
45
- const afterAllComplete = phasesSrc.slice(allCompleteIdx, allCompleteIdx + 600);
45
+ const afterAllComplete = phasesSrc.slice(allCompleteIdx, allCompleteIdx + 800);
46
46
  const mergeIdx = afterAllComplete.indexOf("deps.resolver.mergeAndExit");
47
47
  const flagIdx = afterAllComplete.indexOf("s.milestoneMergedInPhases = true");
48
48
 
@@ -389,7 +389,7 @@ describe('ensure-db-open', () => {
389
389
  assert.ok(db, 'adapter should be available after ensureDbOpen');
390
390
  assert.equal(
391
391
  db.prepare('SELECT MAX(version) as version FROM schema_version').get()?.version,
392
- 21,
392
+ 22,
393
393
  'legacy DB should migrate to current schema version',
394
394
  );
395
395
 
@@ -348,7 +348,7 @@ test("ADR-011 P2: schema v20 fresh DB has all escalation columns on tasks + sour
348
348
  assert.ok(decCols.includes("source"), "decisions table must have source column");
349
349
 
350
350
  const version = adapter.prepare("SELECT MAX(version) as v FROM schema_version").get();
351
- assert.equal(version?.["v"], 21);
351
+ assert.equal(version?.["v"], 22);
352
352
  });
353
353
 
354
354
  test("ADR-011 P2: findUnappliedEscalationOverride returns null when escalation_pending=1 (still pending)", (t) => {