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
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Regression tests for issue #4540:
3
+ * Bug 1 — Invalid quality_gates migration bricks gsd.db
4
+ * Bug 2 — Artifact retries emit no journal event, look like stuck loops
5
+ */
6
+ import { describe, test, beforeEach, afterEach } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { mkdtempSync, rmSync, mkdirSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+ import { createRequire } from "node:module";
12
+
13
+ import {
14
+ openDatabase,
15
+ closeDatabase,
16
+ insertGateRow,
17
+ getPendingGates,
18
+ _getAdapter,
19
+ } from "../gsd-db.ts";
20
+
21
+ import { emitJournalEvent, queryJournal } from "../journal.ts";
22
+
23
+ const _require = createRequire(import.meta.url);
24
+
25
+ // ─── helpers ─────────────────────────────────────────────────────────────────
26
+
27
+ function tmpDb(): { dir: string; dbPath: string } {
28
+ const dir = mkdtempSync(join(tmpdir(), "gsd-4540-"));
29
+ return { dir, dbPath: join(dir, "gsd.db") };
30
+ }
31
+
32
+ function cleanup(dir: string): void {
33
+ try { rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
34
+ }
35
+
36
+ /**
37
+ * Builds a v12 database with the broken quality_gates DDL:
38
+ * task_id is nullable and there is no proper multi-column PK.
39
+ * This simulates a DB that was created before the v12 fix was applied.
40
+ */
41
+ function createBrokenV12Db(dbPath: string): void {
42
+ const sqlite = _require("node:sqlite");
43
+ const db = new sqlite.DatabaseSync(dbPath);
44
+ db.exec("PRAGMA journal_mode=WAL");
45
+
46
+ db.exec(`CREATE TABLE schema_version (version INTEGER NOT NULL, applied_at TEXT NOT NULL)`);
47
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (?, ?)").run(12, "2025-01-01T00:00:00.000Z");
48
+
49
+ db.exec(`
50
+ CREATE TABLE decisions (
51
+ seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE,
52
+ when_context TEXT NOT NULL DEFAULT '', scope TEXT NOT NULL DEFAULT '',
53
+ decision TEXT NOT NULL DEFAULT '', choice TEXT NOT NULL DEFAULT '',
54
+ rationale TEXT NOT NULL DEFAULT '', revisable TEXT NOT NULL DEFAULT '',
55
+ made_by TEXT NOT NULL DEFAULT 'agent', superseded_by TEXT DEFAULT NULL
56
+ );
57
+ CREATE VIEW active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL;
58
+ CREATE TABLE requirements (
59
+ id TEXT PRIMARY KEY, class TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT '',
60
+ description TEXT NOT NULL DEFAULT '', why TEXT NOT NULL DEFAULT '',
61
+ source TEXT NOT NULL DEFAULT '', primary_owner TEXT NOT NULL DEFAULT '',
62
+ supporting_slices TEXT NOT NULL DEFAULT '', validation TEXT NOT NULL DEFAULT '',
63
+ notes TEXT NOT NULL DEFAULT '', full_content TEXT NOT NULL DEFAULT '',
64
+ superseded_by TEXT DEFAULT NULL
65
+ );
66
+ CREATE TABLE artifacts (
67
+ path TEXT PRIMARY KEY, artifact_type TEXT NOT NULL DEFAULT '',
68
+ milestone_id TEXT DEFAULT NULL, slice_id TEXT DEFAULT NULL, task_id TEXT DEFAULT NULL,
69
+ full_content TEXT NOT NULL DEFAULT '', imported_at TEXT NOT NULL DEFAULT ''
70
+ );
71
+ CREATE TABLE memories (
72
+ seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE,
73
+ category TEXT NOT NULL, content TEXT NOT NULL, confidence REAL NOT NULL DEFAULT 0.8,
74
+ source_unit_type TEXT, source_unit_id TEXT, created_at TEXT NOT NULL,
75
+ updated_at TEXT NOT NULL, superseded_by TEXT DEFAULT NULL,
76
+ hit_count INTEGER NOT NULL DEFAULT 0,
77
+ scope TEXT NOT NULL DEFAULT 'project', tags TEXT NOT NULL DEFAULT '[]',
78
+ structured_fields TEXT DEFAULT NULL
79
+ );
80
+ CREATE TABLE memory_sources (
81
+ id TEXT PRIMARY KEY, kind TEXT NOT NULL DEFAULT '', path TEXT DEFAULT NULL,
82
+ imported_at TEXT NOT NULL DEFAULT '',
83
+ scope TEXT NOT NULL DEFAULT 'project', tags TEXT NOT NULL DEFAULT '[]'
84
+ );
85
+ CREATE TABLE memory_relations (
86
+ from_id TEXT NOT NULL, to_id TEXT NOT NULL, relation TEXT NOT NULL DEFAULT '',
87
+ PRIMARY KEY (from_id, to_id)
88
+ );
89
+ CREATE TABLE memory_processed_units (
90
+ unit_key TEXT PRIMARY KEY, activity_file TEXT, processed_at TEXT NOT NULL
91
+ );
92
+ CREATE TABLE milestones (
93
+ id TEXT PRIMARY KEY, title TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'active',
94
+ depends_on TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL DEFAULT '',
95
+ completed_at TEXT DEFAULT NULL, vision TEXT NOT NULL DEFAULT '',
96
+ success_criteria TEXT NOT NULL DEFAULT '[]', key_risks TEXT NOT NULL DEFAULT '[]',
97
+ proof_strategy TEXT NOT NULL DEFAULT '[]', verification_contract TEXT NOT NULL DEFAULT '',
98
+ verification_integration TEXT NOT NULL DEFAULT '',
99
+ verification_operational TEXT NOT NULL DEFAULT '', verification_uat TEXT NOT NULL DEFAULT '',
100
+ definition_of_done TEXT NOT NULL DEFAULT '[]', requirement_coverage TEXT NOT NULL DEFAULT '',
101
+ boundary_map_markdown TEXT NOT NULL DEFAULT ''
102
+ );
103
+ CREATE TABLE slices (
104
+ milestone_id TEXT NOT NULL, id TEXT NOT NULL, title TEXT NOT NULL DEFAULT '',
105
+ status TEXT NOT NULL DEFAULT 'pending', risk TEXT NOT NULL DEFAULT 'medium',
106
+ depends TEXT NOT NULL DEFAULT '[]', demo TEXT NOT NULL DEFAULT '',
107
+ created_at TEXT NOT NULL DEFAULT '', completed_at TEXT DEFAULT NULL,
108
+ full_summary_md TEXT NOT NULL DEFAULT '', full_uat_md TEXT NOT NULL DEFAULT '',
109
+ goal TEXT NOT NULL DEFAULT '', success_criteria TEXT NOT NULL DEFAULT '',
110
+ proof_level TEXT NOT NULL DEFAULT '', integration_closure TEXT NOT NULL DEFAULT '',
111
+ observability_impact TEXT NOT NULL DEFAULT '', sequence INTEGER DEFAULT 0,
112
+ replan_triggered_at TEXT DEFAULT NULL,
113
+ is_sketch INTEGER NOT NULL DEFAULT 0, sketch_scope TEXT NOT NULL DEFAULT '',
114
+ PRIMARY KEY (milestone_id, id), FOREIGN KEY (milestone_id) REFERENCES milestones(id)
115
+ );
116
+ CREATE TABLE tasks (
117
+ milestone_id TEXT NOT NULL, slice_id TEXT NOT NULL, id TEXT NOT NULL,
118
+ title TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'pending',
119
+ one_liner TEXT NOT NULL DEFAULT '', narrative TEXT NOT NULL DEFAULT '',
120
+ verification_result TEXT NOT NULL DEFAULT '',
121
+ escalation_pending INTEGER NOT NULL DEFAULT 0,
122
+ PRIMARY KEY (milestone_id, slice_id, id),
123
+ FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
124
+ );
125
+ CREATE TABLE assessments (
126
+ path TEXT PRIMARY KEY, milestone_id TEXT NOT NULL DEFAULT '',
127
+ slice_id TEXT DEFAULT NULL, task_id TEXT DEFAULT NULL,
128
+ status TEXT NOT NULL DEFAULT '', scope TEXT NOT NULL DEFAULT '',
129
+ full_content TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT '',
130
+ FOREIGN KEY (milestone_id) REFERENCES milestones(id)
131
+ );
132
+ CREATE TABLE replan_history (
133
+ id INTEGER PRIMARY KEY AUTOINCREMENT, milestone_id TEXT NOT NULL,
134
+ slice_id TEXT DEFAULT NULL, task_id TEXT DEFAULT NULL,
135
+ reason TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT ''
136
+ );
137
+ CREATE TABLE verification_evidence (
138
+ id INTEGER PRIMARY KEY AUTOINCREMENT, milestone_id TEXT NOT NULL DEFAULT '',
139
+ slice_id TEXT NOT NULL DEFAULT '', task_id TEXT NOT NULL DEFAULT '',
140
+ unit_type TEXT NOT NULL DEFAULT '', unit_id TEXT NOT NULL DEFAULT '',
141
+ evidence_type TEXT NOT NULL DEFAULT '', content TEXT NOT NULL DEFAULT '',
142
+ created_at TEXT NOT NULL DEFAULT '',
143
+ command TEXT NOT NULL DEFAULT '', verdict TEXT NOT NULL DEFAULT ''
144
+ );
145
+ `);
146
+
147
+ // Broken quality_gates: task_id nullable, no multi-column PK
148
+ db.exec(`
149
+ CREATE TABLE quality_gates (
150
+ milestone_id TEXT NOT NULL, slice_id TEXT NOT NULL, gate_id TEXT NOT NULL,
151
+ scope TEXT NOT NULL DEFAULT 'slice', task_id TEXT DEFAULT NULL,
152
+ status TEXT NOT NULL DEFAULT 'pending', verdict TEXT NOT NULL DEFAULT '',
153
+ rationale TEXT NOT NULL DEFAULT '', findings TEXT NOT NULL DEFAULT '',
154
+ evaluated_at TEXT DEFAULT NULL,
155
+ FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
156
+ )
157
+ `);
158
+
159
+ // Parent rows + gate row with NULL task_id
160
+ db.prepare("INSERT INTO milestones (id, title, status) VALUES (?, ?, ?)").run("M001", "Milestone 1", "active");
161
+ db.prepare("INSERT INTO slices (milestone_id, id, title, status, risk, depends) VALUES (?, ?, ?, ?, ?, ?)").run("M001", "S01", "Slice 1", "pending", "medium", "[]");
162
+ db.prepare("INSERT INTO quality_gates (milestone_id, slice_id, gate_id, scope, task_id, status) VALUES (?, ?, ?, ?, ?, ?)").run("M001", "S01", "Q3", "slice", null, "pending");
163
+
164
+ db.close();
165
+ }
166
+
167
+ // ─── Bug 1 tests ─────────────────────────────────────────────────────────────
168
+
169
+ describe("Bug 1 — quality_gates migration repair (#4540)", () => {
170
+ let dir: string;
171
+ let dbPath: string;
172
+
173
+ beforeEach(() => {
174
+ ({ dir, dbPath } = tmpDb());
175
+ });
176
+
177
+ afterEach(() => {
178
+ closeDatabase();
179
+ cleanup(dir);
180
+ });
181
+
182
+ test("fresh DB: quality_gates task_id is NOT NULL with empty-string default", () => {
183
+ openDatabase(dbPath);
184
+ const adapter = _getAdapter()!;
185
+ const cols = adapter.prepare("PRAGMA table_info(quality_gates)").all() as Array<Record<string, unknown>>;
186
+ const col = cols.find((c) => c["name"] === "task_id");
187
+ assert.ok(col, "task_id column must exist");
188
+ assert.equal(col["notnull"], 1, "task_id must be NOT NULL");
189
+ assert.equal(col["dflt_value"], "''", "task_id default must be ''");
190
+ });
191
+
192
+ test("fresh DB: insertGateRow with no taskId stores '' and is idempotent", () => {
193
+ openDatabase(dbPath);
194
+ const adapter = _getAdapter()!;
195
+ adapter.prepare("INSERT OR IGNORE INTO milestones (id, title, status) VALUES (?, ?, ?)").run("M001", "Test", "active");
196
+ adapter.prepare("INSERT OR IGNORE INTO slices (milestone_id, id, title, status, risk, depends) VALUES (?, ?, ?, ?, ?, ?)").run("M001", "S01", "Slice", "pending", "medium", "[]");
197
+
198
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
199
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
200
+ const rows = getPendingGates("M001", "S01");
201
+ assert.equal(rows.length, 1);
202
+ assert.equal(rows[0].task_id, "");
203
+ });
204
+
205
+ test("v22 repair: broken v12 DB opens without error", () => {
206
+ createBrokenV12Db(dbPath);
207
+ assert.doesNotThrow(() => openDatabase(dbPath));
208
+ });
209
+
210
+ test("v22 repair: task_id becomes NOT NULL after healing broken v12 DB", () => {
211
+ createBrokenV12Db(dbPath);
212
+ openDatabase(dbPath);
213
+ const adapter = _getAdapter()!;
214
+ const cols = adapter.prepare("PRAGMA table_info(quality_gates)").all() as Array<Record<string, unknown>>;
215
+ const col = cols.find((c) => c["name"] === "task_id");
216
+ assert.ok(col, "task_id column must exist after repair");
217
+ assert.equal(col["notnull"], 1, "task_id must be NOT NULL after repair");
218
+ });
219
+
220
+ test("v22 repair: existing NULL task_id row is COALESCE'd to '' during repair", () => {
221
+ createBrokenV12Db(dbPath);
222
+ openDatabase(dbPath);
223
+ const adapter = _getAdapter()!;
224
+ const row = adapter.prepare(
225
+ "SELECT task_id FROM quality_gates WHERE milestone_id = 'M001' AND slice_id = 'S01' AND gate_id = 'Q3'"
226
+ ).get() as Record<string, unknown> | undefined;
227
+ assert.ok(row, "original gate row must survive the repair migration");
228
+ assert.equal(row["task_id"], "", "NULL task_id must be repaired to ''");
229
+ });
230
+
231
+ test("v22 repair: scope column present on quality_gates after open", () => {
232
+ openDatabase(dbPath);
233
+ const adapter = _getAdapter()!;
234
+ const cols = adapter.prepare("PRAGMA table_info(quality_gates)").all() as Array<Record<string, unknown>>;
235
+ assert.ok(cols.some((c) => c["name"] === "scope"), "quality_gates.scope must exist");
236
+ });
237
+
238
+ test("v22 repair: scope column present on assessments after open", () => {
239
+ openDatabase(dbPath);
240
+ const adapter = _getAdapter()!;
241
+ const cols = adapter.prepare("PRAGMA table_info(assessments)").all() as Array<Record<string, unknown>>;
242
+ assert.ok(cols.some((c) => c["name"] === "scope"), "assessments.scope must exist");
243
+ });
244
+ });
245
+
246
+ // ─── Bug 2 tests ─────────────────────────────────────────────────────────────
247
+
248
+ describe("Bug 2 — artifact-verification-retry journal event (#4540)", () => {
249
+ test("emitJournalEvent accepts artifact-verification-retry event type", () => {
250
+ const basePath = mkdtempSync(join(tmpdir(), "gsd-journal-4540-"));
251
+ try {
252
+ mkdirSync(join(basePath, ".gsd"), { recursive: true });
253
+ emitJournalEvent(basePath, {
254
+ ts: new Date().toISOString(),
255
+ flowId: "flow-4540",
256
+ seq: 1,
257
+ eventType: "artifact-verification-retry",
258
+ data: { unitType: "plan-slice", unitId: "M001/S01", attempt: 1 },
259
+ });
260
+ const entries = queryJournal(basePath, { flowId: "flow-4540" });
261
+ assert.equal(entries.length, 1);
262
+ assert.equal(entries[0].eventType, "artifact-verification-retry");
263
+ } finally {
264
+ rmSync(basePath, { recursive: true, force: true });
265
+ }
266
+ });
267
+
268
+ test("artifact-verification-retry event carries attempt count", () => {
269
+ const basePath = mkdtempSync(join(tmpdir(), "gsd-journal-4540b-"));
270
+ try {
271
+ mkdirSync(join(basePath, ".gsd"), { recursive: true });
272
+ emitJournalEvent(basePath, {
273
+ ts: new Date().toISOString(),
274
+ flowId: "flow-4540b",
275
+ seq: 1,
276
+ eventType: "artifact-verification-retry",
277
+ data: { unitType: "exec-slice", unitId: "M002/S02", attempt: 2 },
278
+ });
279
+ const entries = queryJournal(basePath, { flowId: "flow-4540b", eventType: "artifact-verification-retry" });
280
+ assert.equal(entries.length, 1);
281
+ const payload = entries[0].data as Record<string, unknown>;
282
+ assert.equal(payload["attempt"], 2);
283
+ assert.equal(payload["unitId"], "M002/S02");
284
+ } finally {
285
+ rmSync(basePath, { recursive: true, force: true });
286
+ }
287
+ });
288
+ });
@@ -77,6 +77,8 @@ function makeMockDeps(
77
77
  autoWorktreeBranch: () => "auto/M001",
78
78
  resolveMilestoneFile: () => null,
79
79
  reconcileMergeState: () => "clean",
80
+ preflightCleanRoot: () => ({ stashPushed: false, summary: "" }),
81
+ postflightPopStash: () => {},
80
82
  getLedger: () => ({ units: [] }),
81
83
  getProjectTotals: () => ({ cost: 0 }),
82
84
  formatCost: (c: number) => `$${c.toFixed(2)}`,
@@ -143,6 +143,13 @@ test("PROVIDER_REGISTRY includes all major LLM providers", () => {
143
143
  assert.ok(ids.includes("groq"));
144
144
  });
145
145
 
146
+ test("PROVIDER_REGISTRY includes claude-code as a first-class LLM provider (#4541)", () => {
147
+ const entry = PROVIDER_REGISTRY.find((p) => p.id === "claude-code");
148
+ assert.ok(entry, "claude-code must be in PROVIDER_REGISTRY");
149
+ assert.equal(entry!.category, "llm");
150
+ assert.ok(entry!.hasOAuth, "claude-code uses OAuth (CLI auth)");
151
+ });
152
+
146
153
  test("PROVIDER_REGISTRY includes all tool/search providers", () => {
147
154
  const ids = PROVIDER_REGISTRY.map((p) => p.id);
148
155
  assert.ok(ids.includes("tavily"));
@@ -363,7 +363,7 @@ test('md-importer: schema v1→v2 migration', () => {
363
363
  openDatabase(':memory:');
364
364
  const adapter = _getAdapter();
365
365
  const version = adapter?.prepare('SELECT MAX(version) as v FROM schema_version').get();
366
- assert.deepStrictEqual(version?.v, 21, 'new DB should be at schema version 21');
366
+ assert.deepStrictEqual(version?.v, 22, 'new DB should be at schema version 22');
367
367
 
368
368
  // Artifacts table should exist
369
369
  const tableCheck = adapter?.prepare("SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='artifacts'").get();
@@ -323,9 +323,9 @@ test('memory-store: schema includes memories table', () => {
323
323
  const viewCount = adapter.prepare('SELECT count(*) as cnt FROM active_memories').get();
324
324
  assert.deepStrictEqual(viewCount?.['cnt'], 0, 'active_memories view should exist');
325
325
 
326
- // Verify schema version is 21 (ADR-013 structured_fields column included)
326
+ // Verify schema version is 22 (v22 quality_gates DDL fix included)
327
327
  const version = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
328
- assert.deepStrictEqual(version?.["v"], 21, 'schema version should be 21');
328
+ assert.deepStrictEqual(version?.["v"], 22, 'schema version should be 22');
329
329
 
330
330
  closeDatabase();
331
331
  });
@@ -110,6 +110,25 @@ test("template: parallel-research-slices.md has required variables", () => {
110
110
  assert.ok(templateSrc.includes("{{subagentPrompts}}"), "template should use subagentPrompts");
111
111
  });
112
112
 
113
+ test("#4068: template: parallel-research-slices retry cap prevents infinite subagent loop", () => {
114
+ // The template must cap retries at 1 ("retry it once") and instruct the
115
+ // agent to write a BLOCKER note on the second failure rather than looping.
116
+ // Without this, a timing-out subagent causes the orchestrating agent to
117
+ // retry indefinitely (issue #4068 / #4355).
118
+ assert.ok(
119
+ templateSrc.includes("once") || templateSrc.includes("one retry") || templateSrc.match(/retry.{0,20}once/),
120
+ "template should cap subagent retries at one",
121
+ );
122
+ assert.ok(
123
+ templateSrc.toLowerCase().includes("blocker"),
124
+ "template should instruct writing a BLOCKER note instead of infinite retries",
125
+ );
126
+ assert.ok(
127
+ !templateSrc.match(/re-run it individually\s*\n/),
128
+ "template must not have unbounded re-run instruction without a retry cap",
129
+ );
130
+ });
131
+
113
132
  // ─── Validate milestone prompt ────────────────────────────────────────────
114
133
 
115
134
  test("template: validate-milestone uses parallel reviewers", () => {
@@ -30,6 +30,20 @@ describe('normalizeFilePath backtick stripping (#3649)', () => {
30
30
  assert.equal(normalizeFilePath('``src/foo.ts`` (current state)'), 'src/foo.ts')
31
31
  })
32
32
 
33
+ it('strips stray backticks from dash-annotated bare paths (#4550)', () => {
34
+ assert.equal(
35
+ normalizeFilePath('.gsd/KNOWLEDGE.md` — append-only S05 lessons section'),
36
+ '.gsd/KNOWLEDGE.md',
37
+ )
38
+ })
39
+
40
+ it('prefers a backticked path inside a dash-annotated prefix (#4550)', () => {
41
+ assert.equal(
42
+ normalizeFilePath('Input `src/foo.ts` — current state'),
43
+ 'src/foo.ts',
44
+ )
45
+ })
46
+
33
47
  it('strips backticks even when mixed with other normalization', () => {
34
48
  assert.equal(normalizeFilePath('`./src//bar.ts`'), 'src/bar.ts')
35
49
  })
@@ -0,0 +1,272 @@
1
+ /**
2
+ * pre-exec-gate-loop.test.ts — Regression tests for #4551.
3
+ *
4
+ * Verifies that when a pre-execution gate fails on a plan-slice unit:
5
+ * 1. `s.lastPreExecFailure` is populated on the AutoSession with the blocking
6
+ * findings and a verdict excerpt.
7
+ * 2. The `planning → plan-slice` dispatch rule reads that field and injects a
8
+ * "Fix these specific issues" section into the prompt.
9
+ * 3. The field is cleared (consumed) after the prompt is built so that stale
10
+ * context does not bleed into an unrelated future plan-slice run.
11
+ * 4. When the failure belongs to a *different* unit ID, the dispatch rule
12
+ * does NOT inject the stale context into the prompt.
13
+ */
14
+
15
+ import test from "node:test";
16
+ import assert from "node:assert/strict";
17
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { tmpdir } from "node:os";
20
+
21
+ import { AutoSession } from "../auto/session.ts";
22
+ import { resolveDispatch } from "../auto-dispatch.ts";
23
+ import type { DispatchContext } from "../auto-dispatch.ts";
24
+ import { buildPlanSlicePrompt } from "../auto-prompts.ts";
25
+ import {
26
+ openDatabase,
27
+ closeDatabase,
28
+ insertMilestone,
29
+ insertSlice,
30
+ insertTask,
31
+ } from "../gsd-db.ts";
32
+ import { deriveStateFromDb } from "../state.ts";
33
+ import { _clearGsdRootCache } from "../paths.ts";
34
+ import { invalidateAllCaches } from "../cache.ts";
35
+
36
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
37
+
38
+ function makeTempBase(): string {
39
+ const base = mkdtempSync(join(tmpdir(), "gsd-4551-"));
40
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01"), { recursive: true });
41
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
42
+ return base;
43
+ }
44
+
45
+ function seedPlanningState(base: string): void {
46
+ openDatabase(join(base, ".gsd", "gsd.db"));
47
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
48
+ insertSlice({
49
+ id: "S01",
50
+ milestoneId: "M001",
51
+ title: "Core Slice",
52
+ status: "pending",
53
+ risk: "medium",
54
+ depends: [],
55
+ demo: "demo",
56
+ sequence: 1,
57
+ isSketch: false,
58
+ });
59
+ // Write minimal ROADMAP so state derivation doesn't error
60
+ writeFileSync(
61
+ join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
62
+ "# Roadmap\n",
63
+ );
64
+ }
65
+
66
+ function cleanup(base: string, originalCwd: string): void {
67
+ try { closeDatabase(); } catch { /* noop */ }
68
+ try { process.chdir(originalCwd); } catch { /* noop */ }
69
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
70
+ }
71
+
72
+ // ─── Tests ────────────────────────────────────────────────────────────────────
73
+
74
+ test("#4551: AutoSession.lastPreExecFailure defaults to null", () => {
75
+ const s = new AutoSession();
76
+ assert.equal(s.lastPreExecFailure, null, "lastPreExecFailure must start null");
77
+ });
78
+
79
+ test("#4551: AutoSession.reset() clears lastPreExecFailure", () => {
80
+ const s = new AutoSession();
81
+ s.lastPreExecFailure = {
82
+ unitId: "M001/S01",
83
+ blockingFindings: ["[file] src/foo.ts: file not found"],
84
+ verdictExcerpt: "status=fail; 1 blocking issue detected",
85
+ };
86
+ s.reset();
87
+ assert.equal(s.lastPreExecFailure, null, "reset() must clear lastPreExecFailure");
88
+ });
89
+
90
+ test("#4551: buildPlanSlicePrompt injects fix section when priorPreExecFailure provided", async (t) => {
91
+ const originalCwd = process.cwd();
92
+ const base = makeTempBase();
93
+ t.after(() => cleanup(base, originalCwd));
94
+
95
+ seedPlanningState(base);
96
+ process.chdir(base);
97
+ _clearGsdRootCache();
98
+ invalidateAllCaches();
99
+
100
+ const prompt = await buildPlanSlicePrompt(
101
+ "M001", "Test Milestone", "S01", "Core Slice", base,
102
+ undefined,
103
+ {
104
+ priorPreExecFailure: {
105
+ blockingFindings: [
106
+ "[file] src/utils/helper.ts: file not found",
107
+ "[package] nonexistent-pkg: package not found on npm",
108
+ ],
109
+ verdictExcerpt: "status=fail; 2 blocking issues detected",
110
+ },
111
+ },
112
+ );
113
+
114
+ assert.ok(
115
+ prompt.includes("Fix these specific issues from the prior pre-exec check"),
116
+ "prompt must contain the fix section heading",
117
+ );
118
+ assert.ok(
119
+ prompt.includes("src/utils/helper.ts: file not found"),
120
+ "prompt must include the specific file finding",
121
+ );
122
+ assert.ok(
123
+ prompt.includes("nonexistent-pkg: package not found on npm"),
124
+ "prompt must include the specific package finding",
125
+ );
126
+ assert.ok(
127
+ prompt.includes("status=fail; 2 blocking issues detected"),
128
+ "prompt must include the verdict excerpt",
129
+ );
130
+ });
131
+
132
+ test("#4551: buildPlanSlicePrompt with no priorPreExecFailure does NOT include fix section", async (t) => {
133
+ const originalCwd = process.cwd();
134
+ const base = makeTempBase();
135
+ t.after(() => cleanup(base, originalCwd));
136
+
137
+ seedPlanningState(base);
138
+ process.chdir(base);
139
+ _clearGsdRootCache();
140
+ invalidateAllCaches();
141
+
142
+ const prompt = await buildPlanSlicePrompt(
143
+ "M001", "Test Milestone", "S01", "Core Slice", base,
144
+ undefined,
145
+ { /* no priorPreExecFailure */ },
146
+ );
147
+
148
+ assert.ok(
149
+ !prompt.includes("Fix these specific issues from the prior pre-exec check"),
150
+ "prompt must NOT include the fix section when no failure context is given",
151
+ );
152
+ });
153
+
154
+ test("#4551: dispatch rule injects failure context and clears session field", async (t) => {
155
+ const originalCwd = process.cwd();
156
+ const base = makeTempBase();
157
+ t.after(() => cleanup(base, originalCwd));
158
+
159
+ seedPlanningState(base);
160
+ // Write a RESEARCH file so the dispatch rule skips research-slice and reaches
161
+ // plan-slice (which is the phase we're testing).
162
+ writeFileSync(
163
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-RESEARCH.md"),
164
+ "# Research\n",
165
+ );
166
+ process.chdir(base);
167
+ _clearGsdRootCache();
168
+ invalidateAllCaches();
169
+
170
+ const state = await deriveStateFromDb(base);
171
+ assert.equal(state.phase, "planning", "state must be in planning phase");
172
+
173
+ const session = new AutoSession();
174
+ session.basePath = base;
175
+ session.active = true;
176
+ session.lastPreExecFailure = {
177
+ unitId: "M001/S01",
178
+ blockingFindings: ["[file] src/missing.ts: file not found"],
179
+ verdictExcerpt: "status=fail; 1 blocking issue detected",
180
+ };
181
+
182
+ const ctx: DispatchContext = {
183
+ basePath: base,
184
+ mid: "M001",
185
+ midTitle: "Test Milestone",
186
+ state,
187
+ prefs: { phases: { reassess_after_slice: false, skip_research: true } } as any,
188
+ session,
189
+ };
190
+
191
+ const result = await resolveDispatch(ctx);
192
+ assert.equal(result.action, "dispatch", "must dispatch a unit");
193
+ if (result.action !== "dispatch") throw new Error("unreachable");
194
+ assert.equal(result.unitType, "plan-slice", "must be a plan-slice unit");
195
+
196
+ // The fix section must appear in the prompt
197
+ assert.ok(
198
+ result.prompt.includes("Fix these specific issues from the prior pre-exec check"),
199
+ "dispatched prompt must include the fix section",
200
+ );
201
+ assert.ok(
202
+ result.prompt.includes("src/missing.ts: file not found"),
203
+ "dispatched prompt must include the specific blocking finding",
204
+ );
205
+
206
+ // Field must be cleared after consumption
207
+ assert.equal(
208
+ session.lastPreExecFailure,
209
+ null,
210
+ "lastPreExecFailure must be cleared after being consumed by the dispatch rule",
211
+ );
212
+ });
213
+
214
+ test("#4551: dispatch rule does NOT inject stale failure for a different slice", async (t) => {
215
+ const originalCwd = process.cwd();
216
+ const base = makeTempBase();
217
+ t.after(() => cleanup(base, originalCwd));
218
+
219
+ seedPlanningState(base);
220
+ // Write a RESEARCH file so dispatch reaches plan-slice, making the assertion
221
+ // about the prompt meaningful (we can check it's a plan-slice prompt without
222
+ // the fix section rather than a research-slice prompt without it).
223
+ writeFileSync(
224
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-RESEARCH.md"),
225
+ "# Research\n",
226
+ );
227
+ process.chdir(base);
228
+ _clearGsdRootCache();
229
+ invalidateAllCaches();
230
+
231
+ const state = await deriveStateFromDb(base);
232
+
233
+ const session = new AutoSession();
234
+ session.basePath = base;
235
+ session.active = true;
236
+ // Failure belongs to a different slice (S02), not the active one (S01)
237
+ session.lastPreExecFailure = {
238
+ unitId: "M001/S02",
239
+ blockingFindings: ["[file] src/other.ts: file not found"],
240
+ verdictExcerpt: "status=fail; 1 blocking issue detected",
241
+ };
242
+
243
+ const ctx: DispatchContext = {
244
+ basePath: base,
245
+ mid: "M001",
246
+ midTitle: "Test Milestone",
247
+ state,
248
+ prefs: { phases: { reassess_after_slice: false, skip_research: true } } as any,
249
+ session,
250
+ };
251
+
252
+ const result = await resolveDispatch(ctx);
253
+ assert.equal(result.action, "dispatch");
254
+ if (result.action !== "dispatch") throw new Error("unreachable");
255
+
256
+ // The stale fix section must NOT appear
257
+ assert.ok(
258
+ !result.prompt.includes("Fix these specific issues from the prior pre-exec check"),
259
+ "prompt must NOT include fix section for a mismatched unit ID",
260
+ );
261
+ assert.ok(
262
+ !result.prompt.includes("src/other.ts"),
263
+ "prompt must NOT include findings from a different slice",
264
+ );
265
+
266
+ // Field must remain untouched (not consumed)
267
+ assert.notEqual(
268
+ session.lastPreExecFailure,
269
+ null,
270
+ "lastPreExecFailure must NOT be cleared when unit IDs don't match",
271
+ );
272
+ });