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
@@ -82,7 +82,7 @@ function parseTableSlices(section: string): RoadmapSliceEntry[] {
82
82
  const fullRow = line.toLowerCase();
83
83
  const done =
84
84
  /\[x\]/i.test(line) ||
85
- /[✅☑✓]/.test(line) ||
85
+ /[✅☑✓✔]/.test(line) ||
86
86
  /\bdone\b/.test(fullRow) ||
87
87
  /\bcomplete(?:d)?\b/.test(fullRow);
88
88
 
@@ -222,11 +222,11 @@ function parseProseSliceHeaders(content: string): RoadmapSliceEntry[] {
222
222
  // numeric prefixes (e.g., "1.", "(1)"), bracketed IDs (e.g., "[S01]"),
223
223
  // optional checkmark completion marker, and optional leading indentation.
224
224
  // Separator after the ID is flexible: colon, dash, em/en dash, dot, or just whitespace.
225
- const headerPattern = /^\s*#{1,4}\s+\*{0,2}(?:\u2713\s+)?(?:\d+[.)]\s+)?(?:\(\d+\)\s+)?(?:Slice\s+)?\[?(S\d+)\]?\*{0,2}[:\s.\u2014\u2013-]*\s*(.+)/gm;
225
+ const headerPattern = /^\s*#{1,4}\s+\*{0,2}(?:[\u2713\u2705]\s+)?(?:\d+[.)]\s+)?(?:\(\d+\)\s+)?(?:Slice\s+)?\[?(S\d+)\]?\*{0,2}[:\s.\u2014\u2013-]*\s*(.+)/gm;
226
226
  let match: RegExpExecArray | null;
227
227
 
228
228
  // Check for checkmark before the slice ID (e.g., "## checkmark S01: Title")
229
- const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}\u2713\s+/;
229
+ const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}[\u2713\u2705]\s+/;
230
230
 
231
231
  while ((match = headerPattern.exec(content)) !== null) {
232
232
  const id = match[1]!;
@@ -240,9 +240,14 @@ function parseProseSliceHeaders(content: string): RoadmapSliceEntry[] {
240
240
  const line = match[0];
241
241
  let done = prefixCheckPattern.test(line);
242
242
 
243
- if (!done && title.startsWith("\u2713")) {
243
+ if (!done && /^[\u2713\u2705]/.test(title)) {
244
244
  done = true;
245
- title = title.replace(/^\u2713\s*/, "");
245
+ title = title.replace(/^[\u2713\u2705]\s*/, "");
246
+ }
247
+
248
+ if (!done && /[\u2705]\s*$/.test(title)) {
249
+ done = true;
250
+ title = title.replace(/\s*[\u2705]\s*$/, "");
246
251
  }
247
252
 
248
253
  if (!done && /\(Complete\)\s*$/i.test(title)) {
@@ -0,0 +1,86 @@
1
+ /**
2
+ * GSD Slice Parallel Conflict Detection — File overlap analysis between slices.
3
+ *
4
+ * Reads PLAN.md for each slice and extracts file paths mentioned in task
5
+ * descriptions. If two slices share more than 5 file paths, they are considered
6
+ * conflicting and should not run in parallel.
7
+ *
8
+ * Conservative by default: missing PLAN = block parallel execution.
9
+ */
10
+
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+
14
+ // ─── File Path Extraction ─────────────────────────────────────────────────────
15
+
16
+ /**
17
+ * Extract file paths from a PLAN.md content string.
18
+ * Matches common patterns like `src/...`, `lib/...`, paths with extensions.
19
+ */
20
+ function extractFilePaths(content: string): Set<string> {
21
+ const paths = new Set<string>();
22
+
23
+ // Match file-like patterns: word/word paths with extensions, or src/lib/etc prefixed paths
24
+ const patterns = [
25
+ // Paths like src/foo/bar.ts, lib/utils.js, etc.
26
+ /(?:src|lib|test|tests|app|pkg|cmd|internal|components|pages|api|utils|config|scripts|dist|build)\/[\w./-]+\.\w+/g,
27
+ // Generic path with at least one slash and extension
28
+ /(?<!\w)[\w-]+\/[\w./-]+\.\w{1,5}(?!\w)/g,
29
+ ];
30
+
31
+ for (const pattern of patterns) {
32
+ const matches = content.matchAll(pattern);
33
+ for (const match of matches) {
34
+ paths.add(match[0]);
35
+ }
36
+ }
37
+
38
+ return paths;
39
+ }
40
+
41
+ // ─── Conflict Detection ──────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * Check if two slices have file conflicts that would block parallel execution.
45
+ *
46
+ * @param basePath Project root path.
47
+ * @param mid Milestone ID.
48
+ * @param sliceA First slice ID.
49
+ * @param sliceB Second slice ID.
50
+ * @returns true if parallel is unsafe (>5 shared files or missing plan).
51
+ */
52
+ export function hasFileConflict(
53
+ basePath: string,
54
+ mid: string,
55
+ sliceA: string,
56
+ sliceB: string,
57
+ ): boolean {
58
+ const planPathA = join(basePath, ".gsd", "milestones", mid, sliceA, "PLAN.md");
59
+ const planPathB = join(basePath, ".gsd", "milestones", mid, sliceB, "PLAN.md");
60
+
61
+ // Conservative: missing PLAN = block
62
+ if (!existsSync(planPathA) || !existsSync(planPathB)) {
63
+ return true;
64
+ }
65
+
66
+ const contentA = readFileSync(planPathA, "utf-8");
67
+ const contentB = readFileSync(planPathB, "utf-8");
68
+
69
+ const filesA = extractFilePaths(contentA);
70
+ const filesB = extractFilePaths(contentB);
71
+
72
+ // If either has no files extracted, no conflict detectable → allow
73
+ if (filesA.size === 0 || filesB.size === 0) {
74
+ return false;
75
+ }
76
+
77
+ // Count shared files
78
+ let sharedCount = 0;
79
+ for (const file of filesA) {
80
+ if (filesB.has(file)) {
81
+ sharedCount++;
82
+ }
83
+ }
84
+
85
+ return sharedCount > 5;
86
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * GSD Slice Parallel Eligibility — Pure function to determine which slices
3
+ * within a milestone can run in parallel based on dependency satisfaction.
4
+ *
5
+ * This is the slice-level equivalent of parallel-eligibility.ts (which operates
6
+ * at milestone scope). The key difference is the positional fallback: slices
7
+ * without explicit dependencies use sequential ordering as an implicit constraint.
8
+ */
9
+
10
+ // ─── Types ────────────────────────────────────────────────────────────────────
11
+
12
+ export interface SliceInput {
13
+ id: string;
14
+ done: boolean;
15
+ depends: string[];
16
+ }
17
+
18
+ export interface EligibleSlice {
19
+ id: string;
20
+ }
21
+
22
+ // ─── Core Logic ───────────────────────────────────────────────────────────────
23
+
24
+ /**
25
+ * Determine which slices are eligible for parallel execution.
26
+ *
27
+ * Rules:
28
+ * 1. Done slices are never eligible (nothing to do).
29
+ * 2. A slice with explicit `depends` entries is eligible when ALL deps
30
+ * appear in `completedSliceIds`.
31
+ * 3. A slice with NO `depends` entries uses positional fallback: it is
32
+ * eligible only when every positionally-earlier slice is done.
33
+ * This preserves backward compatibility with roadmaps that don't
34
+ * declare inter-slice dependencies.
35
+ *
36
+ * @param slices All slices in the milestone (ordered by position).
37
+ * @param completedSliceIds Set of slice IDs that are already complete.
38
+ * @returns Array of eligible slice descriptors.
39
+ */
40
+ export function getEligibleSlices(
41
+ slices: SliceInput[],
42
+ completedSliceIds: Set<string>,
43
+ ): EligibleSlice[] {
44
+ const eligible: EligibleSlice[] = [];
45
+
46
+ for (let i = 0; i < slices.length; i++) {
47
+ const slice = slices[i];
48
+
49
+ // Rule 1: skip done slices
50
+ if (slice.done) continue;
51
+
52
+ const hasExplicitDeps = slice.depends.length > 0;
53
+
54
+ if (hasExplicitDeps) {
55
+ // Rule 2: explicit dependencies — all must be satisfied
56
+ const allDepsSatisfied = slice.depends.every(dep => completedSliceIds.has(dep));
57
+ if (allDepsSatisfied) {
58
+ eligible.push({ id: slice.id });
59
+ }
60
+ } else {
61
+ // Rule 3: no deps declared — positional fallback
62
+ // Eligible only if all positionally-earlier slices are done
63
+ const allEarlierDone = slices.slice(0, i).every(
64
+ earlier => earlier.done || completedSliceIds.has(earlier.id),
65
+ );
66
+ if (allEarlierDone) {
67
+ eligible.push({ id: slice.id });
68
+ }
69
+ }
70
+ }
71
+
72
+ return eligible;
73
+ }
@@ -0,0 +1,477 @@
1
+ /**
2
+ * GSD Slice Parallel Orchestrator — Engine for parallel slice execution
3
+ * within a single milestone.
4
+ *
5
+ * Mirrors the existing parallel-orchestrator.ts pattern at slice scope
6
+ * instead of milestone scope. Workers are separate processes spawned via
7
+ * child_process, each running in its own git worktree with GSD_SLICE_LOCK
8
+ * + GSD_MILESTONE_LOCK env vars set.
9
+ *
10
+ * Key differences from milestone-level parallelism:
11
+ * - Scope: slices within one milestone, not milestones within a project
12
+ * - Lock env: GSD_SLICE_LOCK (in addition to GSD_MILESTONE_LOCK)
13
+ * - Conflict check: file overlap between slice plans (slice-parallel-conflict.ts)
14
+ */
15
+
16
+ import { spawn, type ChildProcess } from "node:child_process";
17
+ import {
18
+ appendFileSync,
19
+ existsSync,
20
+ writeFileSync,
21
+ readFileSync,
22
+ mkdirSync,
23
+ } from "node:fs";
24
+ import { join, dirname } from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+ import { gsdRoot } from "./paths.js";
27
+ import { createWorktree, worktreePath, removeWorktree } from "./worktree-manager.js";
28
+ import { autoWorktreeBranch, runWorktreePostCreateHook } from "./auto-worktree.js";
29
+ import {
30
+ writeSessionStatus,
31
+ removeSessionStatus,
32
+ } from "./session-status-io.js";
33
+ import { hasFileConflict } from "./slice-parallel-conflict.js";
34
+ import { getErrorMessage } from "./error-utils.js";
35
+
36
+ // ─── Types ─────────────────────────────────────────────────────────────────
37
+
38
+ export interface SliceWorkerInfo {
39
+ milestoneId: string;
40
+ sliceId: string;
41
+ pid: number;
42
+ process: ChildProcess | null;
43
+ worktreePath: string;
44
+ startedAt: number;
45
+ state: "running" | "stopped" | "error";
46
+ completedUnits: number;
47
+ cost: number;
48
+ cleanup?: () => void;
49
+ }
50
+
51
+ export interface SliceOrchestratorState {
52
+ active: boolean;
53
+ workers: Map<string, SliceWorkerInfo>;
54
+ totalCost: number;
55
+ budgetCeiling?: number;
56
+ maxWorkers: number;
57
+ startedAt: number;
58
+ basePath: string;
59
+ }
60
+
61
+ export interface StartSliceParallelOpts {
62
+ maxWorkers?: number;
63
+ budgetCeiling?: number;
64
+ }
65
+
66
+ // ─── Module State ──────────────────────────────────────────────────────────
67
+
68
+ let sliceState: SliceOrchestratorState | null = null;
69
+
70
+ // ─── Public API ────────────────────────────────────────────────────────────
71
+
72
+ /**
73
+ * Check whether slice-level parallel is currently active.
74
+ */
75
+ export function isSliceParallelActive(): boolean {
76
+ return sliceState?.active === true;
77
+ }
78
+
79
+ /**
80
+ * Get current slice orchestrator state (read-only snapshot).
81
+ */
82
+ export function getSliceOrchestratorState(): SliceOrchestratorState | null {
83
+ return sliceState;
84
+ }
85
+
86
+ /**
87
+ * Start parallel execution for eligible slices within a milestone.
88
+ *
89
+ * For each eligible slice: create a worktree, spawn `gsd --mode json --print "/gsd auto"`
90
+ * with env GSD_SLICE_LOCK=<SID> + GSD_MILESTONE_LOCK=<MID> + GSD_PARALLEL_WORKER=1.
91
+ */
92
+ export async function startSliceParallel(
93
+ basePath: string,
94
+ milestoneId: string,
95
+ eligibleSlices: Array<{ id: string }>,
96
+ opts: StartSliceParallelOpts = {},
97
+ ): Promise<{ started: string[]; errors: Array<{ sid: string; error: string }> }> {
98
+ // Prevent nesting: if already a parallel worker, refuse
99
+ if (process.env.GSD_PARALLEL_WORKER) {
100
+ return { started: [], errors: [{ sid: "all", error: "Cannot start slice-parallel from within a parallel worker" }] };
101
+ }
102
+
103
+ const maxWorkers = opts.maxWorkers ?? 2;
104
+ const budgetCeiling = opts.budgetCeiling;
105
+
106
+ // Initialize orchestrator state
107
+ sliceState = {
108
+ active: true,
109
+ workers: new Map(),
110
+ totalCost: 0,
111
+ budgetCeiling,
112
+ maxWorkers,
113
+ startedAt: Date.now(),
114
+ basePath,
115
+ };
116
+
117
+ const started: string[] = [];
118
+ const errors: Array<{ sid: string; error: string }> = [];
119
+
120
+ // Filter out conflicting slices (conservative: check all pairs)
121
+ const safeSlices = filterConflictingSlices(basePath, milestoneId, eligibleSlices);
122
+
123
+ // Limit to maxWorkers
124
+ const toSpawn = safeSlices.slice(0, maxWorkers);
125
+
126
+ for (const slice of toSpawn) {
127
+ try {
128
+ // Create worktree for this slice
129
+ const wtBranch = `slice/${milestoneId}/${slice.id}`;
130
+ const wtName = `${milestoneId}-${slice.id}`;
131
+ const wtPath = worktreePath(basePath, wtName);
132
+
133
+ if (!existsSync(wtPath)) {
134
+ createWorktree(basePath, wtName, { branch: wtBranch });
135
+ }
136
+
137
+ // Create worker info
138
+ const worker: SliceWorkerInfo = {
139
+ milestoneId,
140
+ sliceId: slice.id,
141
+ pid: 0,
142
+ process: null,
143
+ worktreePath: wtPath,
144
+ startedAt: Date.now(),
145
+ state: "running",
146
+ completedUnits: 0,
147
+ cost: 0,
148
+ };
149
+
150
+ sliceState.workers.set(slice.id, worker);
151
+
152
+ // Spawn worker
153
+ const spawned = spawnSliceWorker(basePath, milestoneId, slice.id);
154
+ if (spawned) {
155
+ started.push(slice.id);
156
+ } else {
157
+ errors.push({ sid: slice.id, error: "Failed to spawn worker process" });
158
+ worker.state = "error";
159
+ }
160
+ } catch (err) {
161
+ errors.push({ sid: slice.id, error: getErrorMessage(err) });
162
+ // Best-effort cleanup of partially created worktree
163
+ const wtName = `${milestoneId}-${slice.id}`;
164
+ try {
165
+ removeWorktree(basePath, wtName, { deleteBranch: true, force: true });
166
+ } catch { /* ignore cleanup failures */ }
167
+ }
168
+ }
169
+
170
+ // If nothing started, deactivate
171
+ if (started.length === 0) {
172
+ sliceState.active = false;
173
+ }
174
+
175
+ return { started, errors };
176
+ }
177
+
178
+ /**
179
+ * Stop all slice-parallel workers and deactivate.
180
+ */
181
+ export function stopSliceParallel(): void {
182
+ if (!sliceState) return;
183
+
184
+ for (const worker of sliceState.workers.values()) {
185
+ if (worker.process) {
186
+ try {
187
+ worker.process.kill("SIGTERM");
188
+ } catch { /* already dead */ }
189
+ }
190
+ worker.cleanup?.();
191
+ worker.cleanup = undefined;
192
+ worker.process = null;
193
+ worker.state = "stopped";
194
+
195
+ // Clean up worktree created for this worker
196
+ const wtName = `${worker.milestoneId}-${worker.sliceId}`;
197
+ try {
198
+ removeWorktree(sliceState.basePath, wtName, { deleteBranch: true, force: true });
199
+ } catch { /* best-effort cleanup */ }
200
+ }
201
+
202
+ sliceState.active = false;
203
+ }
204
+
205
+ /**
206
+ * Get aggregate cost across all slice workers.
207
+ */
208
+ export function getSliceAggregateCost(): number {
209
+ if (!sliceState) return 0;
210
+ let total = 0;
211
+ for (const w of sliceState.workers.values()) {
212
+ total += w.cost;
213
+ }
214
+ return total;
215
+ }
216
+
217
+ /**
218
+ * Check if budget ceiling has been exceeded.
219
+ */
220
+ export function isSliceBudgetExceeded(): boolean {
221
+ if (!sliceState?.budgetCeiling) return false;
222
+ return getSliceAggregateCost() >= sliceState.budgetCeiling;
223
+ }
224
+
225
+ /**
226
+ * Reset module state (for testing).
227
+ */
228
+ export function resetSliceOrchestrator(): void {
229
+ if (sliceState) {
230
+ for (const w of sliceState.workers.values()) {
231
+ w.cleanup?.();
232
+ }
233
+ }
234
+ sliceState = null;
235
+ }
236
+
237
+ // ─── Internal: Conflict Filtering ──────────────────────────────────────────
238
+
239
+ /**
240
+ * Remove slices that have file conflicts with each other.
241
+ * Greedy: add slices to the safe set in order; skip any that conflict
242
+ * with an already-included slice.
243
+ */
244
+ function filterConflictingSlices(
245
+ basePath: string,
246
+ milestoneId: string,
247
+ slices: Array<{ id: string }>,
248
+ ): Array<{ id: string }> {
249
+ const safe: Array<{ id: string }> = [];
250
+
251
+ for (const candidate of slices) {
252
+ let conflictsWithSafe = false;
253
+ for (const existing of safe) {
254
+ if (hasFileConflict(basePath, milestoneId, candidate.id, existing.id)) {
255
+ conflictsWithSafe = true;
256
+ break;
257
+ }
258
+ }
259
+ if (!conflictsWithSafe) {
260
+ safe.push(candidate);
261
+ }
262
+ }
263
+
264
+ return safe;
265
+ }
266
+
267
+ // ─── Internal: Worker Spawning ─────────────────────────────────────────────
268
+
269
+ /**
270
+ * Resolve the GSD CLI binary path.
271
+ * Same logic as parallel-orchestrator.ts resolveGsdBin().
272
+ */
273
+ function resolveGsdBin(): string | null {
274
+ if (process.env.GSD_BIN_PATH && existsSync(process.env.GSD_BIN_PATH)) {
275
+ return process.env.GSD_BIN_PATH;
276
+ }
277
+
278
+ let thisDir: string;
279
+ try {
280
+ thisDir = dirname(fileURLToPath(import.meta.url));
281
+ } catch {
282
+ thisDir = process.cwd();
283
+ }
284
+ const candidates = [
285
+ join(thisDir, "..", "..", "..", "loader.js"),
286
+ join(thisDir, "..", "..", "..", "..", "dist", "loader.js"),
287
+ ];
288
+ for (const candidate of candidates) {
289
+ if (existsSync(candidate)) return candidate;
290
+ }
291
+
292
+ return null;
293
+ }
294
+
295
+ /**
296
+ * Spawn a worker process for a slice.
297
+ * The worker runs `gsd --mode json --print "/gsd auto"` in the slice's worktree
298
+ * with GSD_SLICE_LOCK, GSD_MILESTONE_LOCK, and GSD_PARALLEL_WORKER set.
299
+ */
300
+ function spawnSliceWorker(
301
+ basePath: string,
302
+ milestoneId: string,
303
+ sliceId: string,
304
+ ): boolean {
305
+ if (!sliceState) return false;
306
+ const worker = sliceState.workers.get(sliceId);
307
+ if (!worker) return false;
308
+ if (worker.process) return true;
309
+
310
+ const binPath = resolveGsdBin();
311
+ if (!binPath) return false;
312
+
313
+ let child: ChildProcess;
314
+ try {
315
+ child = spawn(process.execPath, [binPath, "--mode", "json", "--print", "/gsd auto"], {
316
+ cwd: worker.worktreePath,
317
+ env: {
318
+ ...process.env,
319
+ GSD_SLICE_LOCK: sliceId,
320
+ GSD_MILESTONE_LOCK: milestoneId,
321
+ GSD_PROJECT_ROOT: basePath,
322
+ GSD_PARALLEL_WORKER: "1",
323
+ },
324
+ stdio: ["ignore", "pipe", "pipe"],
325
+ detached: false,
326
+ });
327
+ } catch {
328
+ return false;
329
+ }
330
+
331
+ child.on("error", () => {
332
+ if (!sliceState) return;
333
+ const w = sliceState.workers.get(sliceId);
334
+ if (w) {
335
+ w.process = null;
336
+ }
337
+ });
338
+
339
+ worker.process = child;
340
+ worker.pid = child.pid ?? 0;
341
+
342
+ if (!child.pid) {
343
+ worker.process = null;
344
+ return false;
345
+ }
346
+
347
+ // ── NDJSON stdout monitoring ────────────────────────────────────────
348
+ if (child.stdout) {
349
+ let stdoutBuffer = "";
350
+ child.stdout.on("data", (data: Buffer) => {
351
+ stdoutBuffer += data.toString();
352
+ const lines = stdoutBuffer.split("\n");
353
+ stdoutBuffer = lines.pop() || "";
354
+ for (const line of lines) {
355
+ processSliceWorkerLine(basePath, milestoneId, sliceId, line);
356
+ }
357
+ });
358
+ child.stdout.on("close", () => {
359
+ if (stdoutBuffer.trim()) {
360
+ processSliceWorkerLine(basePath, milestoneId, sliceId, stdoutBuffer);
361
+ }
362
+ });
363
+ }
364
+
365
+ if (child.stderr) {
366
+ child.stderr.on("data", (data: Buffer) => {
367
+ appendSliceWorkerLog(basePath, milestoneId, sliceId, data.toString());
368
+ });
369
+ }
370
+
371
+ // Update session status
372
+ writeSessionStatus(basePath, {
373
+ milestoneId: `${milestoneId}/${sliceId}`,
374
+ pid: worker.pid,
375
+ state: "running",
376
+ currentUnit: null,
377
+ completedUnits: worker.completedUnits,
378
+ cost: worker.cost,
379
+ lastHeartbeat: Date.now(),
380
+ startedAt: worker.startedAt,
381
+ worktreePath: worker.worktreePath,
382
+ });
383
+
384
+ // Store cleanup function
385
+ worker.cleanup = () => {
386
+ child.stdout?.removeAllListeners();
387
+ child.stderr?.removeAllListeners();
388
+ child.removeAllListeners();
389
+ };
390
+
391
+ // Handle worker exit
392
+ child.on("exit", (code) => {
393
+ if (!sliceState) return;
394
+ const w = sliceState.workers.get(sliceId);
395
+ if (!w) return;
396
+
397
+ w.cleanup?.();
398
+ w.cleanup = undefined;
399
+ w.process = null;
400
+
401
+ if (w.state === "stopped") return;
402
+
403
+ if (code === 0) {
404
+ w.state = "stopped";
405
+ } else {
406
+ w.state = "error";
407
+ appendSliceWorkerLog(basePath, milestoneId, sliceId,
408
+ `\n[slice-orchestrator] worker exited with code ${code ?? "null"}\n`);
409
+ }
410
+
411
+ writeSessionStatus(basePath, {
412
+ milestoneId: `${milestoneId}/${sliceId}`,
413
+ pid: w.pid,
414
+ state: w.state,
415
+ currentUnit: null,
416
+ completedUnits: w.completedUnits,
417
+ cost: w.cost,
418
+ lastHeartbeat: Date.now(),
419
+ startedAt: w.startedAt,
420
+ worktreePath: w.worktreePath,
421
+ });
422
+ });
423
+
424
+ return true;
425
+ }
426
+
427
+ // ─── NDJSON Processing ──────────────────────────────────────────────────────
428
+
429
+ /**
430
+ * Process a single NDJSON line from a slice worker's stdout.
431
+ * Extracts cost from message_end events.
432
+ */
433
+ function processSliceWorkerLine(
434
+ _basePath: string,
435
+ _milestoneId: string,
436
+ sliceId: string,
437
+ line: string,
438
+ ): void {
439
+ if (!line.trim() || !sliceState) return;
440
+
441
+ let event: Record<string, unknown>;
442
+ try {
443
+ event = JSON.parse(line);
444
+ } catch {
445
+ return;
446
+ }
447
+
448
+ const type = String(event.type ?? "");
449
+ if (type === "message_end") {
450
+ const worker = sliceState.workers.get(sliceId);
451
+ if (worker) {
452
+ const usage = event.usage as Record<string, unknown> | undefined;
453
+ if (usage?.cost && typeof usage.cost === "number") {
454
+ worker.cost += usage.cost;
455
+ sliceState.totalCost += usage.cost;
456
+ }
457
+ worker.completedUnits++;
458
+ }
459
+ }
460
+ }
461
+
462
+ // ─── Logging ────────────────────────────────────────────────────────────────
463
+
464
+ function sliceLogDir(basePath: string): string {
465
+ return join(gsdRoot(basePath), "parallel", "slice-logs");
466
+ }
467
+
468
+ function appendSliceWorkerLog(
469
+ basePath: string,
470
+ milestoneId: string,
471
+ sliceId: string,
472
+ text: string,
473
+ ): void {
474
+ const dir = sliceLogDir(basePath);
475
+ mkdirSync(dir, { recursive: true });
476
+ appendFileSync(join(dir, `${milestoneId}-${sliceId}.log`), text);
477
+ }