gsd-pi 2.77.0-dev.58d3d4d6c → 2.77.0-dev.cfd69e714

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/README.md +1 -1
  2. package/dist/claude-cli-check.js +5 -1
  3. package/dist/headless.js +49 -4
  4. package/dist/resource-loader.d.ts +40 -0
  5. package/dist/resource-loader.js +32 -13
  6. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  7. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  8. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  9. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  10. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  11. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
  14. package/dist/resources/extensions/gsd/auto/loop.js +43 -0
  15. package/dist/resources/extensions/gsd/auto/phases.js +15 -21
  16. package/dist/resources/extensions/gsd/auto/session.js +0 -2
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +102 -24
  18. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +71 -64
  20. package/dist/resources/extensions/gsd/auto-prompts.js +329 -102
  21. package/dist/resources/extensions/gsd/auto-recovery.js +195 -23
  22. package/dist/resources/extensions/gsd/auto-start.js +34 -24
  23. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  24. package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
  25. package/dist/resources/extensions/gsd/auto.js +31 -20
  26. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  27. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  28. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
  29. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  30. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  31. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  32. package/dist/resources/extensions/gsd/component-types.js +69 -0
  33. package/dist/resources/extensions/gsd/detection.js +49 -1
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  35. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  36. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  37. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  38. package/dist/resources/extensions/gsd/git-service.js +126 -2
  39. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  40. package/dist/resources/extensions/gsd/guided-flow.js +17 -5
  41. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  42. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  43. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  44. package/dist/resources/extensions/gsd/model-router.js +6 -0
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  46. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  47. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  48. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  49. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  50. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  51. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  52. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  53. package/dist/resources/extensions/gsd/state.js +44 -33
  54. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  55. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  56. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  57. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  58. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  59. package/dist/resources/extensions/gsd/worktree-manager.js +34 -8
  60. package/dist/resources/extensions/mcp-client/index.js +3 -1
  61. package/dist/resources/extensions/ollama/index.js +5 -1
  62. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  63. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  93. package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
  94. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  95. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  97. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  98. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  99. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  100. package/package.json +2 -3
  101. package/packages/daemon/src/logger.ts +4 -3
  102. package/packages/mcp-server/dist/server.d.ts +24 -0
  103. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  104. package/packages/mcp-server/dist/server.js +88 -87
  105. package/packages/mcp-server/dist/server.js.map +1 -1
  106. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  107. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  108. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  109. package/packages/mcp-server/src/server.ts +131 -105
  110. package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
  111. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  112. package/packages/native/package.json +1 -1
  113. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  114. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  115. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  116. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  117. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  118. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  119. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  120. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  121. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  122. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  123. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  124. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  125. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  127. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  128. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  129. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  130. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  131. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  133. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  134. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  135. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  136. package/packages/pi-ai/dist/models.test.js +36 -11
  137. package/packages/pi-ai/dist/models.test.js.map +1 -1
  138. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  139. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  140. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  141. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  142. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  143. package/packages/pi-ai/src/models.test.ts +48 -11
  144. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  145. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  146. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  148. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  150. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  152. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  154. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  156. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  158. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  160. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  163. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  165. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  167. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  168. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  169. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  170. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  171. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  173. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  175. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/index.js +1 -0
  177. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  189. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  191. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  193. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  194. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  195. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  196. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  197. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  198. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  199. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  200. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  201. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  202. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  203. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  204. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  205. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  206. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  207. package/packages/pi-coding-agent/src/index.ts +1 -0
  208. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  209. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  210. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  211. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  212. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  213. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  214. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  215. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  216. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  217. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  218. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  219. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
  220. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  221. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  222. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  223. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  224. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  225. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  226. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  227. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  228. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  229. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  230. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  231. package/packages/pi-tui/dist/components/editor.js +19 -0
  232. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  233. package/packages/pi-tui/dist/components/image.test.js +6 -5
  234. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  235. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  236. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  237. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  238. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  239. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  240. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
  241. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  242. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  243. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  244. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  245. package/packages/pi-tui/src/components/editor.ts +22 -0
  246. package/packages/pi-tui/src/components/image.test.ts +10 -5
  247. package/packages/pi-tui/src/editor-component.ts +3 -0
  248. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  249. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  250. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  251. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  252. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  253. package/scripts/install.js +15 -1
  254. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  255. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  256. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  257. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  258. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  259. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  260. package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
  261. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
  262. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
  263. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  264. package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
  265. package/src/resources/extensions/gsd/auto/loop.ts +47 -0
  266. package/src/resources/extensions/gsd/auto/phases.ts +16 -20
  267. package/src/resources/extensions/gsd/auto/session.ts +0 -2
  268. package/src/resources/extensions/gsd/auto-dispatch.ts +113 -24
  269. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  270. package/src/resources/extensions/gsd/auto-post-unit.ts +82 -73
  271. package/src/resources/extensions/gsd/auto-prompts.ts +330 -90
  272. package/src/resources/extensions/gsd/auto-recovery.ts +225 -24
  273. package/src/resources/extensions/gsd/auto-start.ts +54 -6
  274. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  275. package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
  276. package/src/resources/extensions/gsd/auto.ts +43 -22
  277. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
  278. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  279. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
  280. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  281. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  282. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  283. package/src/resources/extensions/gsd/component-types.ts +362 -0
  284. package/src/resources/extensions/gsd/detection.ts +58 -1
  285. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  286. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  287. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  288. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  289. package/src/resources/extensions/gsd/git-service.ts +133 -2
  290. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  291. package/src/resources/extensions/gsd/guided-flow.ts +20 -5
  292. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  293. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  294. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  295. package/src/resources/extensions/gsd/model-router.ts +6 -0
  296. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  297. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  298. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  299. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  300. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  301. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  302. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  303. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  304. package/src/resources/extensions/gsd/state.ts +49 -44
  305. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  306. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  307. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  308. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  309. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +94 -289
  310. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  311. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  312. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  313. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  314. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -197
  315. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  316. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  317. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  318. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  319. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  320. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  321. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  322. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  323. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  324. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  325. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  326. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  327. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -3
  328. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  329. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  330. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +9 -105
  331. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  332. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  333. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  334. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -57
  335. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  336. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  337. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  338. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  339. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  340. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  341. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  342. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  343. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  344. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -62
  345. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  346. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  347. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  348. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -49
  349. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  350. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -133
  351. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  352. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  353. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
  354. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
  355. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  356. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  357. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  358. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  359. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  360. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  361. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  362. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  363. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  364. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  365. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  366. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  367. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  368. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -5
  369. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  370. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  371. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  372. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +12 -61
  373. package/src/resources/extensions/gsd/tests/test-helpers.ts +21 -8
  374. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  375. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  376. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  377. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  378. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  379. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  380. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
  381. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -81
  382. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  383. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  384. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  385. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  386. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  387. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  388. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  389. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  390. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  391. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  392. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  393. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  394. package/src/resources/extensions/gsd/types.ts +3 -3
  395. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  396. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  397. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  398. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  399. package/src/resources/extensions/gsd/worktree-manager.ts +55 -7
  400. package/src/resources/extensions/mcp-client/index.ts +3 -1
  401. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  402. package/src/resources/extensions/ollama/index.ts +5 -1
  403. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  404. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  405. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  406. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  407. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  408. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  409. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  410. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  411. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  412. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  413. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  414. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -144
  415. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  416. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  417. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  418. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  419. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  420. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  421. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -75
  422. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  423. package/src/resources/extensions/gsd/tests/forensics-worktree-telemetry.test.ts +0 -145
  424. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  425. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  426. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -130
  427. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -43
  428. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
  429. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
@@ -1,168 +1,308 @@
1
1
  /**
2
- * Parallel research slices dispatch — structural tests.
2
+ * parallel-research-dispatch.test.tsbehaviour tests for the
3
+ * "planning (multiple slices need research) → parallel-research-slices"
4
+ * dispatch rule and its prompt builder.
3
5
  *
4
- * Verifies the dispatch rule and prompt builder exist with correct structure.
6
+ * These tests invoke the real functions (resolveDispatch,
7
+ * buildParallelResearchSlicesPrompt) against on-disk fixtures. They do
8
+ * not read source files and string-match identifiers.
9
+ *
10
+ * See #4784 for why the previous source-grep version of this file was
11
+ * replaced.
5
12
  */
6
13
 
7
- import test, { afterEach } from "node:test";
14
+ import { describe, test, beforeEach, afterEach, after } from "node:test";
8
15
  import assert from "node:assert/strict";
9
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
10
- import { join, dirname } from "node:path";
16
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
17
+ import { join } from "node:path";
11
18
  import { tmpdir } from "node:os";
12
- import { fileURLToPath } from "node:url";
13
19
 
14
- import { resolveDispatch } from "../auto-dispatch.ts";
15
- import { extractSourceRegion } from "./test-helpers.ts";
20
+ // Point GSD_HOME at a throwaway directory *before* the prompt-loader
21
+ // module is imported (via the dynamic imports below) so templates
22
+ // resolve from the in-tree prompts/ directory instead of a developer's
23
+ // ~/.gsd/ copy (which may be a stale cached version from a prior
24
+ // install — see #4784 fallout). Static imports above are hoisted, so
25
+ // `tmpdir` and `join` are already available at this point; the dynamic
26
+ // imports below observe the value we set here. The previous value is
27
+ // captured and restored in an after() hook to avoid leaking module-scope
28
+ // env mutation into other suites if the runner ever moves to a shared
29
+ // process model.
30
+ const previousGsdHome = process.env.GSD_HOME;
31
+ process.env.GSD_HOME = process.env.GSD_HOME_TEST_OVERRIDE
32
+ ?? join(tmpdir(), `gsd-test-home-${process.pid}-${Date.now()}`);
16
33
 
17
- const __dirname = dirname(fileURLToPath(import.meta.url));
34
+ after(() => {
35
+ if (previousGsdHome === undefined) {
36
+ delete process.env.GSD_HOME;
37
+ } else {
38
+ process.env.GSD_HOME = previousGsdHome;
39
+ }
40
+ });
18
41
 
19
- const dispatchSrc = readFileSync(join(__dirname, "..", "auto-dispatch.ts"), "utf-8");
20
- const promptsSrc = readFileSync(join(__dirname, "..", "auto-prompts.ts"), "utf-8");
21
- const templatePath = join(__dirname, "..", "prompts", "parallel-research-slices.md");
22
- const templateSrc = readFileSync(templatePath, "utf-8");
42
+ const { resolveDispatch } = await import("../auto-dispatch.ts");
43
+ const { buildParallelResearchSlicesPrompt } = await import("../auto-prompts.ts");
23
44
 
24
- const tmpDirs: string[] = [];
45
+ type DispatchState = Parameters<typeof resolveDispatch>[0]["state"];
25
46
 
26
- function makeTmpProject(): string {
27
- const base = mkdtempSync(join(tmpdir(), "parallel-research-"));
28
- tmpDirs.push(base);
29
- const milestoneDir = join(base, ".gsd", "milestones", "M001");
47
+ function writeRoadmap(
48
+ base: string,
49
+ mid: string,
50
+ slices: Array<{ id: string; title: string; done?: boolean; depends?: string[] }>,
51
+ ): void {
52
+ const milestoneDir = join(base, ".gsd", "milestones", mid);
30
53
  mkdirSync(milestoneDir, { recursive: true });
54
+ const lines = [
55
+ `# ${mid}: Parallel Research Milestone`,
56
+ "",
57
+ "**Vision:** Research-ready slices.",
58
+ "",
59
+ "**Success Criteria:**",
60
+ "- Research all slices",
61
+ "",
62
+ "## Slices",
63
+ "",
64
+ ];
65
+ for (const s of slices) {
66
+ const box = s.done ? "x" : " ";
67
+ const deps = s.depends ? `depends:[${s.depends.join(",")}]` : "depends:[]";
68
+ lines.push(`- [${box}] **${s.id}: ${s.title}** \`risk:low\` \`${deps}\``);
69
+ }
70
+ lines.push("", "## Boundary Map", "");
31
71
  writeFileSync(
32
- join(milestoneDir, "M001-ROADMAP.md"),
33
- [
34
- "# M001: Parallel Research Milestone",
35
- "",
36
- "**Vision:** Research-ready slices.",
37
- "",
38
- "**Success Criteria:**",
39
- "- Research both slices",
40
- "",
41
- "## Slices",
42
- "",
43
- "- [ ] **S01: Alpha** `risk:low` `depends:[]`",
44
- "- [ ] **S02: Beta** `risk:low` `depends:[]`",
45
- "",
46
- "## Boundary Map",
47
- "",
48
- ].join("\n"),
72
+ join(milestoneDir, `${mid}-ROADMAP.md`),
73
+ lines.join("\n"),
49
74
  "utf-8",
50
75
  );
51
- return base;
52
76
  }
53
77
 
54
- afterEach(() => {
55
- for (const dir of tmpDirs) {
56
- try {
57
- rmSync(dir, { recursive: true, force: true });
58
- } catch {
59
- // Best-effort cleanup only.
60
- }
78
+ function assertNotParallelResearchDispatch(
79
+ action: Awaited<ReturnType<typeof resolveDispatch>>,
80
+ mid = "M001",
81
+ ): void {
82
+ if (action.action === "dispatch") {
83
+ assert.notEqual(action.unitId, `${mid}/parallel-research`);
61
84
  }
62
- tmpDirs.length = 0;
63
- });
85
+ }
64
86
 
65
- // ─── Dispatch rule ────────────────────────────────────────────────────────
87
+ function baseState(activeSliceId = "S01", activeSliceTitle = "Alpha"): DispatchState {
88
+ return {
89
+ phase: "planning",
90
+ activeMilestone: { id: "M001", title: "Parallel Research Milestone", status: "active" },
91
+ activeSlice: { id: activeSliceId, title: activeSliceTitle },
92
+ activeTask: null,
93
+ registry: [],
94
+ blockers: [],
95
+ } as unknown as DispatchState;
96
+ }
66
97
 
67
- test("dispatch: parallel-research-slices rule exists", () => {
68
- assert.ok(
69
- dispatchSrc.includes("parallel-research-slices"),
70
- "dispatch table should have parallel-research-slices rule",
71
- );
72
- });
98
+ describe("parallel-research-slices dispatch rule", () => {
99
+ let base: string;
73
100
 
74
- test("dispatch: parallel-research-slices requires 2+ slices", () => {
75
- assert.ok(
76
- dispatchSrc.includes("researchReadySlices.length < 2"),
77
- "rule should require at least 2 slices for parallel dispatch",
78
- );
79
- });
101
+ beforeEach(() => {
102
+ base = mkdtempSync(join(tmpdir(), "parallel-research-"));
103
+ });
80
104
 
81
- test("dispatch: parallel-research-slices respects skip_research", () => {
82
- const ruleIdx = dispatchSrc.indexOf("parallel-research-slices");
83
- // Pin to the located occurrence — if "parallel-research-slices" appears
84
- // more than once in the source, fromIdx keeps the anchor deterministic.
85
- const ruleBlock = extractSourceRegion(dispatchSrc, "parallel-research-slices", { fromIdx: ruleIdx });
86
- assert.ok(
87
- ruleBlock.includes("skip_research") || dispatchSrc.slice(ruleIdx - 300, ruleIdx).includes("skip_research"),
88
- "rule should check skip_research preference",
89
- );
90
- });
105
+ afterEach(() => {
106
+ rmSync(base, { recursive: true, force: true });
107
+ });
91
108
 
92
- // ─── Prompt builder ───────────────────────────────────────────────────────
109
+ test("dispatches parallel research when 2+ slices need research", async () => {
110
+ writeRoadmap(base, "M001", [
111
+ { id: "S01", title: "Alpha" },
112
+ { id: "S02", title: "Beta" },
113
+ ]);
93
114
 
94
- test("prompt: buildParallelResearchSlicesPrompt exported", () => {
95
- assert.ok(
96
- promptsSrc.includes("export async function buildParallelResearchSlicesPrompt"),
97
- "buildParallelResearchSlicesPrompt should be exported",
98
- );
99
- });
115
+ const action = await resolveDispatch({
116
+ basePath: base,
117
+ mid: "M001",
118
+ midTitle: "Parallel Research Milestone",
119
+ state: baseState(),
120
+ prefs: undefined,
121
+ });
100
122
 
101
- test("prompt: builds per-slice subagent prompts", () => {
102
- assert.ok(
103
- promptsSrc.includes("buildResearchSlicePrompt"),
104
- "parallel prompt builder should delegate to per-slice research prompts",
105
- );
106
- });
123
+ assert.equal(action.action, "dispatch");
124
+ if (action.action === "dispatch") {
125
+ assert.equal(action.unitType, "research-slice");
126
+ assert.equal(action.unitId, "M001/parallel-research");
127
+ }
128
+ });
107
129
 
108
- // ─── Template ─────────────────────────────────────────────────────────────
130
+ test("does not dispatch parallel research with only one ready slice", async () => {
131
+ writeRoadmap(base, "M001", [{ id: "S01", title: "Alpha" }]);
109
132
 
110
- test("template: parallel-research-slices.md has required variables", () => {
111
- assert.ok(templateSrc.includes("{{sliceCount}}"), "template should use sliceCount");
112
- assert.ok(templateSrc.includes("{{mid}}"), "template should use mid");
113
- assert.ok(templateSrc.includes("{{subagentPrompts}}"), "template should use subagentPrompts");
114
- });
133
+ const action = await resolveDispatch({
134
+ basePath: base,
135
+ mid: "M001",
136
+ midTitle: "Parallel Research Milestone",
137
+ state: baseState(),
138
+ prefs: undefined,
139
+ });
115
140
 
116
- test("#4068: template: parallel-research-slices retry cap prevents infinite subagent loop", () => {
117
- // The template must cap retries at 1 ("retry it once") and instruct the
118
- // agent to write a BLOCKER note on the second failure rather than looping.
119
- // Without this, a timing-out subagent causes the orchestrating agent to
120
- // retry indefinitely (issue #4068 / #4355).
121
- assert.ok(
122
- templateSrc.includes("once") || templateSrc.includes("one retry") || templateSrc.match(/retry.{0,20}once/),
123
- "template should cap subagent retries at one",
124
- );
125
- assert.ok(
126
- templateSrc.toLowerCase().includes("blocker"),
127
- "template should instruct writing a BLOCKER note instead of infinite retries",
128
- );
129
- assert.ok(
130
- !templateSrc.match(/re-run it individually\s*\n/),
131
- "template must not have unbounded re-run instruction without a retry cap",
132
- );
133
- });
141
+ assertNotParallelResearchDispatch(action);
142
+ });
134
143
 
135
- // ─── Validate milestone prompt ────────────────────────────────────────────
144
+ test("does not dispatch parallel research when skip_research is set", async () => {
145
+ writeRoadmap(base, "M001", [
146
+ { id: "S01", title: "Alpha" },
147
+ { id: "S02", title: "Beta" },
148
+ ]);
136
149
 
137
- test("template: validate-milestone uses parallel reviewers", () => {
138
- const validateSrc = readFileSync(join(__dirname, "..", "prompts", "validate-milestone.md"), "utf-8");
139
- assert.ok(
140
- validateSrc.includes("Reviewer A") && validateSrc.includes("Reviewer B") && validateSrc.includes("Reviewer C"),
141
- "validate-milestone should dispatch 3 parallel reviewers",
142
- );
150
+ const action = await resolveDispatch({
151
+ basePath: base,
152
+ mid: "M001",
153
+ midTitle: "Parallel Research Milestone",
154
+ state: baseState(),
155
+ prefs: { phases: { skip_research: true } } as Parameters<typeof resolveDispatch>[0]["prefs"],
156
+ });
157
+
158
+ assertNotParallelResearchDispatch(action);
159
+ });
160
+
161
+ test("does not dispatch parallel research when skip_slice_research is set", async () => {
162
+ writeRoadmap(base, "M001", [
163
+ { id: "S01", title: "Alpha" },
164
+ { id: "S02", title: "Beta" },
165
+ ]);
166
+
167
+ const action = await resolveDispatch({
168
+ basePath: base,
169
+ mid: "M001",
170
+ midTitle: "Parallel Research Milestone",
171
+ state: baseState(),
172
+ prefs: { phases: { skip_slice_research: true } } as Parameters<typeof resolveDispatch>[0]["prefs"],
173
+ });
174
+
175
+ assertNotParallelResearchDispatch(action);
176
+ });
177
+
178
+ test("does not dispatch when a PARALLEL-BLOCKER placeholder exists (#4414)", async () => {
179
+ writeRoadmap(base, "M001", [
180
+ { id: "S01", title: "Alpha" },
181
+ { id: "S02", title: "Beta" },
182
+ ]);
183
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
184
+ writeFileSync(
185
+ join(milestoneDir, "M001-PARALLEL-BLOCKER.md"),
186
+ "# Parallel research escalated\nPrevious dispatch failed; need per-slice fallback.\n",
187
+ "utf-8",
188
+ );
189
+
190
+ const action = await resolveDispatch({
191
+ basePath: base,
192
+ mid: "M001",
193
+ midTitle: "Parallel Research Milestone",
194
+ state: baseState(),
195
+ prefs: undefined,
196
+ });
197
+
198
+ assertNotParallelResearchDispatch(action);
199
+ });
200
+
201
+ test("excludes slices that already have a RESEARCH file (falls back to <2)", async () => {
202
+ writeRoadmap(base, "M001", [
203
+ { id: "S01", title: "Alpha" },
204
+ { id: "S02", title: "Beta" },
205
+ ]);
206
+ // S01 already has research → only S02 remains → <2 ready → no parallel dispatch
207
+ const s01Dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
208
+ mkdirSync(s01Dir, { recursive: true });
209
+ writeFileSync(
210
+ join(s01Dir, "S01-RESEARCH.md"),
211
+ "# Research\n",
212
+ "utf-8",
213
+ );
214
+
215
+ const action = await resolveDispatch({
216
+ basePath: base,
217
+ mid: "M001",
218
+ midTitle: "Parallel Research Milestone",
219
+ state: baseState(),
220
+ prefs: undefined,
221
+ });
222
+
223
+ assertNotParallelResearchDispatch(action);
224
+ });
143
225
  });
144
226
 
145
- test("resolveDispatch prefers parallel research when multiple slices are ready", async () => {
146
- const base = makeTmpProject();
147
-
148
- const action = await resolveDispatch({
149
- basePath: base,
150
- mid: "M001",
151
- midTitle: "Parallel Research Milestone",
152
- state: {
153
- phase: "planning",
154
- activeMilestone: { id: "M001", title: "Parallel Research Milestone", status: "active" },
155
- activeSlice: { id: "S01", title: "Alpha" },
156
- activeTask: null,
157
- registry: [],
158
- blockers: [],
159
- } as any,
160
- prefs: undefined,
227
+ describe("buildParallelResearchSlicesPrompt", () => {
228
+ let base: string;
229
+
230
+ beforeEach(() => {
231
+ base = mkdtempSync(join(tmpdir(), "parallel-research-prompt-"));
232
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
161
233
  });
162
234
 
163
- assert.equal(action.action, "dispatch");
164
- if (action.action === "dispatch") {
165
- assert.equal(action.unitType, "research-slice");
166
- assert.equal(action.unitId, "M001/parallel-research");
167
- }
235
+ afterEach(() => {
236
+ rmSync(base, { recursive: true, force: true });
237
+ });
238
+
239
+ test("renders slice count and every slice title into the prompt", async () => {
240
+ const prompt = await buildParallelResearchSlicesPrompt(
241
+ "M001",
242
+ "Parallel Research Milestone",
243
+ [
244
+ { id: "S01", title: "Alpha slice title" },
245
+ { id: "S02", title: "Beta slice title" },
246
+ { id: "S03", title: "Gamma slice title" },
247
+ ],
248
+ base,
249
+ );
250
+
251
+ assert.match(
252
+ prompt,
253
+ /\b3\b[^.\n]{0,40}\bslices?\b/i,
254
+ "prompt should include slice count 3 in slice context",
255
+ );
256
+ assert.match(prompt, /Alpha slice title/);
257
+ assert.match(prompt, /Beta slice title/);
258
+ assert.match(prompt, /Gamma slice title/);
259
+ });
260
+
261
+ test("#4068: prompt caps subagent retries at one and instructs writing a BLOCKER on second failure", async () => {
262
+ // Regression for infinite-retry loop (#4068 / #4355 / #4570). A correct
263
+ // fix must both cap retries AND instruct the agent to escalate to a
264
+ // BLOCKER note on the second failure.
265
+ const prompt = await buildParallelResearchSlicesPrompt(
266
+ "M001",
267
+ "Parallel Research Milestone",
268
+ [
269
+ { id: "S01", title: "Alpha" },
270
+ { id: "S02", title: "Beta" },
271
+ ],
272
+ base,
273
+ );
274
+
275
+ // Cap: the rendered prompt must bound retries. "once" alone is too
276
+ // permissive — the word appears for unrelated reasons across a ~10KB
277
+ // rendered prompt — so the pattern must require retry-context
278
+ // pairing: either "retry ... once" within 30 chars, or the phrase
279
+ // "one retry", or "retry it once".
280
+ assert.match(
281
+ prompt,
282
+ /\bone retry\b|retry[^.?!]{0,30}\bonce\b|retry it \*{0,2}once/i,
283
+ "rendered prompt should cap retries to one",
284
+ );
285
+
286
+ // Escalation: on second failure the agent must write a BLOCKER
287
+ // rather than loop. Bare /blocker/i is too permissive — require
288
+ // retry/failure context within the same sentence so a stray
289
+ // "blocker" elsewhere in the ~10KB prompt cannot satisfy the
290
+ // assertion.
291
+ assert.match(
292
+ prompt,
293
+ /blocker[^.?!]{0,100}(second|retry|fail)|(second|retry|fail)[^.?!]{0,100}blocker/i,
294
+ "rendered prompt should instruct writing a BLOCKER on repeated failure",
295
+ );
296
+
297
+ // Anti-pattern: the unbounded "re-run it individually" instruction
298
+ // that caused the original infinite loop must not appear on its own.
299
+ // The negative lookahead is bounded to the surrounding 100
300
+ // characters so a later unrelated "once" elsewhere in the prompt
301
+ // cannot falsely satisfy the "paired with a retry bound" exception.
302
+ assert.doesNotMatch(
303
+ prompt,
304
+ /re-run it individually(?![\s\S]{0,100}\bonce\b)/i,
305
+ "rendered prompt must not contain the unbounded re-run instruction",
306
+ );
307
+ });
168
308
  });