gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.351157b

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 (312) hide show
  1. package/README.md +46 -134
  2. package/dist/cli.js +44 -6
  3. package/dist/help-text.js +4 -1
  4. package/dist/onboarding.js +15 -8
  5. package/dist/resource-loader.js +18 -3
  6. package/dist/resources/extensions/cmux/index.js +21 -12
  7. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
  8. package/dist/resources/extensions/gsd/auto/loop.js +4 -0
  9. package/dist/resources/extensions/gsd/auto/phases.js +123 -22
  10. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  11. package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
  12. package/dist/resources/extensions/gsd/auto-post-unit.js +45 -10
  13. package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
  14. package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
  15. package/dist/resources/extensions/gsd/auto-start.js +10 -21
  16. package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
  17. package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
  18. package/dist/resources/extensions/gsd/auto.js +19 -2
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +73 -60
  20. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  21. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -1
  24. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  25. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  26. package/dist/resources/extensions/gsd/constants.js +42 -0
  27. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  28. package/dist/resources/extensions/gsd/forensics.js +20 -4
  29. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  30. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  31. package/dist/resources/extensions/gsd/metrics.js +27 -1
  32. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  33. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  34. package/dist/resources/extensions/gsd/preferences.js +7 -2
  35. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  36. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  37. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  38. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  39. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  40. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  41. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  42. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  43. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  44. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  45. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  46. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  47. package/dist/resources/extensions/gsd/state.js +74 -14
  48. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  49. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  50. package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
  51. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  52. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  53. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  54. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  55. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  56. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  57. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  58. package/dist/resources/extensions/mcp-client/index.js +10 -1
  59. package/dist/resources/extensions/ollama/index.js +6 -12
  60. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  61. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  62. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  63. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  64. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  65. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  66. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  67. package/dist/web/standalone/.next/BUILD_ID +1 -1
  68. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  69. package/dist/web/standalone/.next/build-manifest.json +2 -2
  70. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  71. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  72. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  94. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  121. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  130. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  142. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/index.html +1 -1
  150. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  154. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  155. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  156. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  157. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  158. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  159. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  160. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  161. package/package.json +1 -1
  162. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  163. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  164. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  165. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  166. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  167. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  168. package/packages/pi-ai/dist/types.d.ts +16 -1
  169. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  170. package/packages/pi-ai/dist/types.js.map +1 -1
  171. package/packages/pi-ai/src/types.ts +18 -1
  172. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  173. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  175. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  177. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  179. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  181. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  183. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  185. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  188. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
  190. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  192. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  194. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  196. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
  199. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  200. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  201. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  202. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  203. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  204. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  205. package/packages/pi-coding-agent/src/core/model-registry.ts +2 -0
  206. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  207. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  208. package/packages/pi-coding-agent/src/core/sdk.ts +11 -0
  209. package/src/resources/extensions/cmux/index.ts +18 -12
  210. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  211. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  212. package/src/resources/extensions/gsd/auto/phases.ts +156 -34
  213. package/src/resources/extensions/gsd/auto/session.ts +9 -0
  214. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  215. package/src/resources/extensions/gsd/auto-post-unit.ts +53 -12
  216. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  217. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  218. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  219. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  220. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  221. package/src/resources/extensions/gsd/auto.ts +22 -1
  222. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +74 -60
  223. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  224. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  225. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  226. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -1
  227. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  228. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  229. package/src/resources/extensions/gsd/constants.ts +44 -0
  230. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  231. package/src/resources/extensions/gsd/forensics.ts +21 -5
  232. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  233. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  234. package/src/resources/extensions/gsd/metrics.ts +28 -1
  235. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  236. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  237. package/src/resources/extensions/gsd/preferences.ts +9 -2
  238. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  239. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  240. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  241. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  242. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  243. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  244. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  245. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  246. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  247. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  248. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  249. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  250. package/src/resources/extensions/gsd/state.ts +67 -12
  251. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  252. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  253. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  254. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  255. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  256. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  257. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  258. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  259. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  260. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  261. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  262. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  263. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  264. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  265. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  266. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  267. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  268. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  269. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  270. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  271. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  272. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  273. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  274. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  275. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  276. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  277. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  278. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  279. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  280. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  281. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  282. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  283. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  284. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  285. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  286. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  287. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  288. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  289. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  290. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  291. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  292. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  293. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  294. package/src/resources/extensions/gsd/types.ts +44 -22
  295. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  296. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  297. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  298. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  299. package/src/resources/extensions/mcp-client/index.ts +16 -1
  300. package/src/resources/extensions/ollama/index.ts +6 -14
  301. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  302. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  303. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  304. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  305. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  306. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  307. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  308. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  309. package/src/resources/extensions/ollama/types.ts +23 -0
  310. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  311. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
  312. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_ssgManifest.js +0 -0
@@ -412,6 +412,16 @@ test("unrecognized format warning is emitted at most once (#2373)", () => {
412
412
  }
413
413
  });
414
414
 
415
+ test("parsePreferencesMarkdown parses heading+list format without frontmatter (#2036)", () => {
416
+ // A GSD agent recovery session wrote preferences in markdown heading+list
417
+ // format instead of YAML frontmatter. Since the heading+list fallback parser
418
+ // was added, this format is now handled gracefully.
419
+ const content = "## Git\n\n- isolation: none\n";
420
+ const result = parsePreferencesMarkdown(content);
421
+ assert.notEqual(result, null, "heading+list content should be parsed");
422
+ assert.deepStrictEqual(result!.git, { isolation: "none" });
423
+ });
424
+
415
425
  // ── Experimental preferences ─────────────────────────────────────────────────
416
426
 
417
427
  test("experimental.rtk: true is accepted and stored", () => {
@@ -231,6 +231,31 @@ test("complete-slice prompt uses camelCase parameter names matching TypeBox sche
231
231
  assert.match(toolCallLine!, /sliceId/);
232
232
  });
233
233
 
234
+ // ─── File system safety: complete-slice parity with complete-milestone (#2935) ──
235
+
236
+ test("complete-slice prompt includes filesystem safety guard against EISDIR", () => {
237
+ const prompt = readPrompt("complete-slice");
238
+ assert.match(
239
+ prompt,
240
+ /File system safety/i,
241
+ "complete-slice.md must include a 'File system safety' instruction to prevent EISDIR errors when the LLM passes a directory path to the read tool"
242
+ );
243
+ assert.match(
244
+ prompt,
245
+ /never pass.*directory path.*directly to the.*read.*tool/i,
246
+ "complete-slice.md must warn against passing directory paths to the read tool"
247
+ );
248
+ });
249
+
250
+ test("complete-milestone prompt still has its filesystem safety guard (regression)", () => {
251
+ const prompt = readPrompt("complete-milestone");
252
+ assert.match(
253
+ prompt,
254
+ /File system safety/i,
255
+ "complete-milestone.md must keep its filesystem safety guard"
256
+ );
257
+ });
258
+
234
259
  test("reactive-execute prompt references tool calls instead of checkbox updates", () => {
235
260
  const prompt = readPrompt("reactive-execute");
236
261
  assert.doesNotMatch(prompt, /checkbox updates/);
@@ -133,6 +133,19 @@ test("parseRoadmapSlices: table with glyph completion markers (#2841)", () => {
133
133
  assert.equal(slices[3]?.done, true);
134
134
  });
135
135
 
136
+ test("parseRoadmapSlices: table with heavy check mark U+2714 (#2940)", () => {
137
+ const tableContent = [
138
+ "# M003: Heavy Check", "", "## Slices", "",
139
+ "| Slice | Title | Risk | Status |", "|---|---|---|---|",
140
+ "| S01 | First | Low | \u2714 |",
141
+ "| S02 | Second | High | Pending |", "",
142
+ ].join("\n");
143
+ const slices = parseRoadmapSlices(tableContent);
144
+ assert.equal(slices.length, 2);
145
+ assert.equal(slices[0]?.done, true, "U+2714 heavy check mark should mark slice as done");
146
+ assert.equal(slices[1]?.done, false);
147
+ });
148
+
136
149
  test("parseRoadmapSlices: table with dependencies column (#1736)", () => {
137
150
  const tableContent = [
138
151
  "# M004: Deps", "", "## Slices", "",
@@ -393,3 +406,59 @@ test("parseRoadmapSlices: indented H3 headers under ## Slices (#2567)", () => {
393
406
  assert.equal(slices[1]?.id, "S02");
394
407
  assert.equal(slices[1]?.title, "Build");
395
408
  });
409
+
410
+ // ── Regression tests for #1884: ✅ (U+2705) completion marker ──────────────
411
+
412
+ test("parseRoadmapSlices: prose headers with ✅ suffix detected as done (#1884)", () => {
413
+ const proseContent = `# M013: Prose Roadmap
414
+
415
+ ### S01: Plan Limits & Billing Foundation ✅
416
+ All tasks done.
417
+
418
+ ### S02: Usage Tracking
419
+ Not done yet.
420
+
421
+ ### S03: Notification System ✅
422
+ Also done.
423
+ `;
424
+ const slices = parseRoadmapSlices(proseContent);
425
+ assert.equal(slices.length, 3);
426
+ assert.equal(slices[0]?.id, "S01");
427
+ assert.equal(slices[0]?.done, true, "S01 with trailing ✅ should be done");
428
+ assert.equal(slices[0]?.title, "Plan Limits & Billing Foundation");
429
+ assert.equal(slices[1]?.done, false);
430
+ assert.equal(slices[2]?.done, true, "S03 with trailing ✅ should be done");
431
+ assert.equal(slices[2]?.title, "Notification System");
432
+ });
433
+
434
+ test("parseRoadmapSlices: prose headers with ✅ prefix before title detected as done (#1884)", () => {
435
+ const proseContent = `# M014: Prose
436
+
437
+ ## ✅ S01: Done Slice
438
+ Complete.
439
+
440
+ ## S02: Pending Slice
441
+ Not done.
442
+ `;
443
+ const slices = parseRoadmapSlices(proseContent);
444
+ assert.equal(slices.length, 2);
445
+ assert.equal(slices[0]?.done, true, "prefix ✅ should mark as done");
446
+ assert.equal(slices[0]?.title, "Done Slice");
447
+ assert.equal(slices[1]?.done, false);
448
+ });
449
+
450
+ test("parseRoadmapSlices: prose headers with ✅ after separator detected as done (#1884)", () => {
451
+ const proseContent = `# M015: Prose
452
+
453
+ ## S01: ✅ First Feature
454
+ Done.
455
+
456
+ ## S02: Second Feature
457
+ Not done.
458
+ `;
459
+ const slices = parseRoadmapSlices(proseContent);
460
+ assert.equal(slices.length, 2);
461
+ assert.equal(slices[0]?.done, true, "✅ after colon should mark as done");
462
+ assert.equal(slices[0]?.title, "First Feature");
463
+ assert.equal(slices[1]?.done, false);
464
+ });
@@ -68,6 +68,36 @@ describe('shared-wal', async () => {
68
68
  'forward-slash worktree path resolves correctly');
69
69
  }
70
70
 
71
+ // ─── Test (e1): external-state worktree resolves to project state DB (#2952) ───
72
+ console.log('\n=== shared-wal: resolve external-state worktree path (#2952) ===');
73
+ {
74
+ // External-state layout: ~/.gsd/projects/<hash>/worktrees/<MID>
75
+ // Should resolve to: ~/.gsd/projects/<hash>/gsd.db
76
+ const stateRoot = '/home/user/.gsd/projects/a1b2c3d4';
77
+ const worktreePath = join(stateRoot, 'worktrees', 'M002');
78
+ const result = resolveProjectRootDbPath(worktreePath);
79
+ assert.deepStrictEqual(result, join(stateRoot, 'gsd.db'),
80
+ 'external-state worktree path resolves to project state DB (#2952)');
81
+ }
82
+
83
+ // ─── Test (e2): external-state worktree nested subdir (#2952) ─────────
84
+ console.log('\n=== shared-wal: resolve external-state worktree nested subdir (#2952) ===');
85
+ {
86
+ const stateRoot = '/home/user/.gsd/projects/deadbeef42';
87
+ const nestedPath = join(stateRoot, 'worktrees', 'M003', 'src', 'lib');
88
+ const result = resolveProjectRootDbPath(nestedPath);
89
+ assert.deepStrictEqual(result, join(stateRoot, 'gsd.db'),
90
+ 'external-state nested worktree subdir resolves to project state DB (#2952)');
91
+ }
92
+
93
+ // ─── Test (e3): external-state worktree with forward slashes (#2952) ──
94
+ console.log('\n=== shared-wal: resolve external-state worktree forward-slash (#2952) ===');
95
+ {
96
+ const result = resolveProjectRootDbPath('/Users/dev/.gsd/projects/cafe0123/worktrees/M001');
97
+ assert.deepStrictEqual(result, join('/Users/dev/.gsd/projects/cafe0123', 'gsd.db'),
98
+ 'external-state forward-slash worktree path resolves correctly (#2952)');
99
+ }
100
+
71
101
  // ─── Test (e): Concurrent writes — 3 connections to same WAL DB ───────
72
102
  console.log('\n=== shared-wal: concurrent writes via WAL ===');
73
103
  {
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Regression test: S##-CONTEXT.md from slice discussion must be
3
+ * injected into all 5 downstream prompt builders (#3452).
4
+ *
5
+ * Scans auto-prompts.ts for the 5 builder functions and verifies
6
+ * each one resolves and inlines the slice-level CONTEXT file.
7
+ */
8
+
9
+ import { describe, test } from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { readFileSync } from "node:fs";
12
+ import { join, dirname } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const autoPromptsPath = join(__dirname, "..", "auto-prompts.ts");
17
+ const source = readFileSync(autoPromptsPath, "utf-8");
18
+
19
+ const BUILDERS = [
20
+ "buildResearchSlicePrompt",
21
+ "buildPlanSlicePrompt",
22
+ "buildCompleteSlicePrompt",
23
+ "buildReplanSlicePrompt",
24
+ "buildReassessRoadmapPrompt",
25
+ ];
26
+
27
+ describe("slice CONTEXT.md injection into prompt builders (#3452)", () => {
28
+ for (const builder of BUILDERS) {
29
+ test(`${builder} resolves slice CONTEXT file`, () => {
30
+ // Find the function body
31
+ const fnStart = source.indexOf(`export async function ${builder}`);
32
+ assert.ok(fnStart !== -1, `${builder} should exist in auto-prompts.ts`);
33
+
34
+ // Get a reasonable chunk after the function start (enough to cover the inlining section)
35
+ const chunk = source.slice(fnStart, fnStart + 3000);
36
+
37
+ // Must resolve the slice CONTEXT path
38
+ assert.ok(
39
+ chunk.includes('resolveSliceFile(base, mid,') && chunk.includes('"CONTEXT"'),
40
+ `${builder} should call resolveSliceFile with "CONTEXT"`,
41
+ );
42
+
43
+ // Must inline it with inlineFileOptional
44
+ assert.ok(
45
+ chunk.includes('Slice Context'),
46
+ `${builder} should inline slice CONTEXT with a "Slice Context" label`,
47
+ );
48
+ });
49
+ }
50
+ });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Tests for slice-level parallel conflict detection.
3
+ * Verifies hasFileConflict() correctly identifies when two slices
4
+ * touch too many of the same files to safely run in parallel.
5
+ */
6
+
7
+ import { describe, it, beforeEach, afterEach } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+
13
+ import { hasFileConflict } from "../slice-parallel-conflict.js";
14
+
15
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
16
+
17
+ function makeTmpBase(): string {
18
+ const base = mkdtempSync(join(tmpdir(), "gsd-slice-conflict-test-"));
19
+ mkdirSync(join(base, ".gsd"), { recursive: true });
20
+ return base;
21
+ }
22
+
23
+ function writeSlicePlan(base: string, mid: string, sid: string, content: string): void {
24
+ const dir = join(base, ".gsd", "milestones", mid, sid);
25
+ mkdirSync(dir, { recursive: true });
26
+ writeFileSync(join(dir, "PLAN.md"), content, "utf-8");
27
+ }
28
+
29
+ describe("hasFileConflict", () => {
30
+ let base: string;
31
+
32
+ beforeEach(() => {
33
+ base = makeTmpBase();
34
+ });
35
+
36
+ afterEach(() => {
37
+ rmSync(base, { recursive: true, force: true });
38
+ });
39
+
40
+ it("two slices with >5 overlapping file paths → blocked (true)", () => {
41
+ const planA = `# Plan S01
42
+ ## Tasks
43
+ - T01: Update src/auth/login.ts
44
+ - T02: Update src/auth/register.ts
45
+ - T03: Update src/auth/session.ts
46
+ - T04: Update src/auth/middleware.ts
47
+ - T05: Update src/auth/types.ts
48
+ - T06: Update src/auth/utils.ts
49
+ `;
50
+ const planB = `# Plan S02
51
+ ## Tasks
52
+ - T01: Refactor src/auth/login.ts
53
+ - T02: Refactor src/auth/register.ts
54
+ - T03: Refactor src/auth/session.ts
55
+ - T04: Refactor src/auth/middleware.ts
56
+ - T05: Refactor src/auth/types.ts
57
+ - T06: Refactor src/auth/utils.ts
58
+ `;
59
+ writeSlicePlan(base, "M001", "S01", planA);
60
+ writeSlicePlan(base, "M001", "S02", planB);
61
+ assert.equal(hasFileConflict(base, "M001", "S01", "S02"), true);
62
+ });
63
+
64
+ it("two slices with 0 overlapping paths → allowed (false)", () => {
65
+ const planA = `# Plan S01
66
+ ## Tasks
67
+ - T01: Create src/api/routes.ts
68
+ - T02: Create src/api/handlers.ts
69
+ `;
70
+ const planB = `# Plan S02
71
+ ## Tasks
72
+ - T01: Create src/ui/components.ts
73
+ - T02: Create src/ui/styles.ts
74
+ `;
75
+ writeSlicePlan(base, "M001", "S01", planA);
76
+ writeSlicePlan(base, "M001", "S02", planB);
77
+ assert.equal(hasFileConflict(base, "M001", "S01", "S02"), false);
78
+ });
79
+
80
+ it("missing PLAN.md → conservative block (true)", () => {
81
+ // Only create one slice's plan
82
+ writeSlicePlan(base, "M001", "S01", "# Plan\n- T01: src/foo.ts");
83
+ // S02 has no plan at all
84
+ assert.equal(hasFileConflict(base, "M001", "S01", "S02"), true);
85
+ });
86
+
87
+ it("one slice empty plan → allowed (false)", () => {
88
+ writeSlicePlan(base, "M001", "S01", "# Plan S01\n## Tasks\n- T01: Create src/foo.ts");
89
+ writeSlicePlan(base, "M001", "S02", "# Plan S02\n## Tasks\n(no tasks yet)");
90
+ assert.equal(hasFileConflict(base, "M001", "S01", "S02"), false);
91
+ });
92
+ });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Tests for slice-level parallel eligibility.
3
+ * Verifies getEligibleSlices() correctly determines which slices
4
+ * can run in parallel based on dependency satisfaction.
5
+ */
6
+
7
+ import { describe, it } from "node:test";
8
+ import assert from "node:assert/strict";
9
+
10
+ import { getEligibleSlices } from "../slice-parallel-eligibility.js";
11
+
12
+ describe("getEligibleSlices", () => {
13
+ it("diamond DAG: S01 done, S02 depends:[S01], S03 depends:[S01] → both eligible", () => {
14
+ const slices = [
15
+ { id: "S01", done: true, depends: [] },
16
+ { id: "S02", done: false, depends: ["S01"] },
17
+ { id: "S03", done: false, depends: ["S01"] },
18
+ ];
19
+ const completed = new Set(["S01"]);
20
+ const result = getEligibleSlices(slices, completed);
21
+ const ids = result.map(s => s.id);
22
+ assert.deepStrictEqual(ids.sort(), ["S02", "S03"]);
23
+ });
24
+
25
+ it("linear chain: S01→S02→S03, only S01 done → only S02 eligible", () => {
26
+ const slices = [
27
+ { id: "S01", done: true, depends: [] },
28
+ { id: "S02", done: false, depends: ["S01"] },
29
+ { id: "S03", done: false, depends: ["S02"] },
30
+ ];
31
+ const completed = new Set(["S01"]);
32
+ const result = getEligibleSlices(slices, completed);
33
+ assert.equal(result.length, 1);
34
+ assert.equal(result[0].id, "S02");
35
+ });
36
+
37
+ it("no deps declared: S01 done, S02 no deps, S03 no deps → only S02 eligible (positional fallback)", () => {
38
+ const slices = [
39
+ { id: "S01", done: true, depends: [] },
40
+ { id: "S02", done: false, depends: [] },
41
+ { id: "S03", done: false, depends: [] },
42
+ ];
43
+ const completed = new Set(["S01"]);
44
+ const result = getEligibleSlices(slices, completed);
45
+ // Positional fallback: when no deps declared, only the first non-done slice
46
+ // after all positionally-earlier slices are done is eligible
47
+ assert.equal(result.length, 1);
48
+ assert.equal(result[0].id, "S02");
49
+ });
50
+
51
+ it("all done: empty result", () => {
52
+ const slices = [
53
+ { id: "S01", done: true, depends: [] },
54
+ { id: "S02", done: true, depends: ["S01"] },
55
+ { id: "S03", done: true, depends: ["S02"] },
56
+ ];
57
+ const completed = new Set(["S01", "S02", "S03"]);
58
+ const result = getEligibleSlices(slices, completed);
59
+ assert.equal(result.length, 0);
60
+ });
61
+
62
+ it("empty input: empty result", () => {
63
+ const result = getEligibleSlices([], new Set());
64
+ assert.equal(result.length, 0);
65
+ });
66
+
67
+ it("mixed deps and no-deps: only dep-satisfied slices with explicit deps are eligible alongside positional", () => {
68
+ const slices = [
69
+ { id: "S01", done: true, depends: [] },
70
+ { id: "S02", done: false, depends: ["S01"] }, // explicit dep satisfied
71
+ { id: "S03", done: false, depends: [] }, // no deps, positional fallback
72
+ { id: "S04", done: false, depends: ["S01"] }, // explicit dep satisfied
73
+ ];
74
+ const completed = new Set(["S01"]);
75
+ const result = getEligibleSlices(slices, completed);
76
+ const ids = result.map(s => s.id);
77
+ // S02 and S04 have explicit deps satisfied; S03 has no deps but
78
+ // positionally S02 (before it) is not done, so S03 is blocked by positional rule
79
+ assert.ok(ids.includes("S02"), "S02 should be eligible (dep on S01 satisfied)");
80
+ assert.ok(ids.includes("S04"), "S04 should be eligible (dep on S01 satisfied)");
81
+ });
82
+
83
+ it("unsatisfied dependency blocks slice", () => {
84
+ const slices = [
85
+ { id: "S01", done: false, depends: [] },
86
+ { id: "S02", done: false, depends: ["S01"] },
87
+ ];
88
+ const completed = new Set<string>();
89
+ const result = getEligibleSlices(slices, completed);
90
+ // S01 has no deps and is first → eligible by positional
91
+ // S02 depends on S01 which is not completed → blocked
92
+ assert.equal(result.length, 1);
93
+ assert.equal(result[0].id, "S01");
94
+ });
95
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Structural tests for slice-level parallel orchestrator.
3
+ * Verifies the orchestrator module exists and has the correct shape,
4
+ * env var usage, and preference gating.
5
+ */
6
+
7
+ import { describe, it } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { readFileSync } from "node:fs";
10
+ import { join, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const gsdDir = join(__dirname, "..");
15
+
16
+ describe("slice-parallel-orchestrator structural tests", () => {
17
+ it("orchestrator uses GSD_SLICE_LOCK env var", () => {
18
+ const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
19
+ assert.ok(
20
+ source.includes("GSD_SLICE_LOCK"),
21
+ "Orchestrator must use GSD_SLICE_LOCK env var to isolate slice workers",
22
+ );
23
+ });
24
+
25
+ it("orchestrator sets GSD_PARALLEL_WORKER=1 to prevent nesting", () => {
26
+ const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
27
+ assert.ok(
28
+ source.includes("GSD_PARALLEL_WORKER"),
29
+ "Orchestrator must set GSD_PARALLEL_WORKER to prevent nested parallel",
30
+ );
31
+ });
32
+
33
+ it("maxWorkers default is 2", () => {
34
+ const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
35
+ // Check that default max workers is 2 (in opts.maxWorkers ?? 2 or similar)
36
+ assert.ok(
37
+ source.includes("maxWorkers") && source.includes("2"),
38
+ "Default maxWorkers should be 2",
39
+ );
40
+ });
41
+
42
+ it("orchestrator imports GSD_MILESTONE_LOCK for milestone isolation", () => {
43
+ const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
44
+ assert.ok(
45
+ source.includes("GSD_MILESTONE_LOCK"),
46
+ "Orchestrator must also pass GSD_MILESTONE_LOCK for milestone context",
47
+ );
48
+ });
49
+ });
50
+
51
+ describe("slice_parallel preference gating", () => {
52
+ it("preferences-types.ts includes slice_parallel in interface", () => {
53
+ const source = readFileSync(join(gsdDir, "preferences-types.ts"), "utf-8");
54
+ assert.ok(
55
+ source.includes("slice_parallel"),
56
+ "GSDPreferences should have slice_parallel field",
57
+ );
58
+ });
59
+
60
+ it("slice_parallel is in KNOWN_PREFERENCE_KEYS", () => {
61
+ const source = readFileSync(join(gsdDir, "preferences-types.ts"), "utf-8");
62
+ assert.ok(
63
+ source.includes('"slice_parallel"'),
64
+ 'KNOWN_PREFERENCE_KEYS should include "slice_parallel"',
65
+ );
66
+ });
67
+
68
+ it("state.ts checks GSD_SLICE_LOCK for slice isolation", () => {
69
+ const source = readFileSync(join(gsdDir, "state.ts"), "utf-8");
70
+ assert.ok(
71
+ source.includes("GSD_SLICE_LOCK"),
72
+ "State derivation should check GSD_SLICE_LOCK for slice-level parallel isolation",
73
+ );
74
+ });
75
+
76
+ it("auto.ts imports slice parallel orchestrator when enabled", () => {
77
+ const source = readFileSync(join(gsdDir, "auto.ts"), "utf-8");
78
+ assert.ok(
79
+ source.includes("slice_parallel") || source.includes("slice-parallel"),
80
+ "auto.ts should reference slice_parallel for dispatch gating",
81
+ );
82
+ });
83
+ });
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Regression tests for #2883: gsd_complete_slice tool invocation fails with
3
+ * JSON truncation, causing stuck retry loop.
4
+ *
5
+ * When a GSD tool is invoked with malformed/truncated JSON arguments, the tool
6
+ * execution fails (isError: true). But postUnitPreVerification only checks if
7
+ * the expected artifact exists on disk — it does not know the tool itself failed.
8
+ * When the artifact is missing (because the tool never ran), it sets up
9
+ * pendingVerificationRetry, re-dispatching the same unit with the same truncated
10
+ * input, creating a stuck loop.
11
+ *
12
+ * The fix adds a `lastToolInvocationError` field to AutoSession. When a GSD tool
13
+ * execution ends with isError, the error is recorded. postUnitPreVerification
14
+ * checks this field before retrying — if a tool invocation error occurred, it
15
+ * pauses auto-mode instead of retrying.
16
+ */
17
+ import { describe, test } from "node:test";
18
+ import assert from "node:assert/strict";
19
+ import { AutoSession } from "../auto/session.ts";
20
+
21
+ // ─── AutoSession.lastToolInvocationError field ───────────────────────────
22
+
23
+ describe("#2883: tool invocation error tracking on AutoSession", () => {
24
+ test("lastToolInvocationError defaults to null", () => {
25
+ const s = new AutoSession();
26
+ assert.equal(s.lastToolInvocationError, null);
27
+ });
28
+
29
+ test("lastToolInvocationError is cleared on reset()", () => {
30
+ const s = new AutoSession();
31
+ s.lastToolInvocationError = "Validation failed for tool gsd_complete_slice";
32
+ assert.ok(s.lastToolInvocationError);
33
+ s.reset();
34
+ assert.equal(s.lastToolInvocationError, null);
35
+ });
36
+
37
+ test("lastToolInvocationError can store truncated JSON error", () => {
38
+ const s = new AutoSession();
39
+ const errorMsg = "Expected ',' or '}' in JSON at position 4096";
40
+ s.lastToolInvocationError = errorMsg;
41
+ assert.equal(s.lastToolInvocationError, errorMsg);
42
+ });
43
+ });
44
+
45
+ // ─── isToolInvocationError classifier ────────────────────────────────────
46
+
47
+ import { isToolInvocationError } from "../auto-tool-tracking.ts";
48
+
49
+ describe("#2883: isToolInvocationError classification", () => {
50
+ test("detects JSON validation failure pattern", () => {
51
+ assert.equal(
52
+ isToolInvocationError("Validation failed for tool gsd_complete_slice: Expected ',' or '}' in JSON"),
53
+ true,
54
+ );
55
+ });
56
+
57
+ test("detects truncated JSON parse error", () => {
58
+ assert.equal(
59
+ isToolInvocationError("Expected ',' or '}' in JSON at position 4096"),
60
+ true,
61
+ );
62
+ });
63
+
64
+ test("detects Unexpected end of JSON input", () => {
65
+ assert.equal(
66
+ isToolInvocationError("Unexpected end of JSON input"),
67
+ true,
68
+ );
69
+ });
70
+
71
+ test("detects Unexpected token in JSON", () => {
72
+ assert.equal(
73
+ isToolInvocationError("Unexpected token < in JSON at position 0"),
74
+ true,
75
+ );
76
+ });
77
+
78
+ test("detects 'Validation failed for tool' prefix", () => {
79
+ assert.equal(
80
+ isToolInvocationError("Validation failed for tool gsd_slice_complete"),
81
+ true,
82
+ );
83
+ });
84
+
85
+ test("returns false for normal tool errors (business logic)", () => {
86
+ assert.equal(
87
+ isToolInvocationError("Slice S01 is already complete"),
88
+ false,
89
+ );
90
+ });
91
+
92
+ test("returns false for empty string", () => {
93
+ assert.equal(isToolInvocationError(""), false);
94
+ });
95
+
96
+ test("returns false for generic error", () => {
97
+ assert.equal(isToolInvocationError("Something went wrong"), false);
98
+ });
99
+
100
+ test("returns false for network errors (handled elsewhere)", () => {
101
+ assert.equal(isToolInvocationError("ECONNRESET"), false);
102
+ });
103
+ });