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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/README.md +1 -1
  2. package/dist/claude-cli-check.js +5 -1
  3. package/dist/headless.js +49 -4
  4. package/dist/resource-loader.d.ts +40 -0
  5. package/dist/resource-loader.js +32 -13
  6. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  7. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  8. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  9. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  10. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  11. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
  14. package/dist/resources/extensions/gsd/auto/loop.js +43 -0
  15. package/dist/resources/extensions/gsd/auto/phases.js +15 -21
  16. package/dist/resources/extensions/gsd/auto/session.js +0 -2
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +102 -24
  18. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +71 -64
  20. package/dist/resources/extensions/gsd/auto-prompts.js +329 -102
  21. package/dist/resources/extensions/gsd/auto-recovery.js +195 -23
  22. package/dist/resources/extensions/gsd/auto-start.js +34 -24
  23. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  24. package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
  25. package/dist/resources/extensions/gsd/auto.js +31 -20
  26. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  27. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  28. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
  29. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  30. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  31. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  32. package/dist/resources/extensions/gsd/component-types.js +69 -0
  33. package/dist/resources/extensions/gsd/detection.js +49 -1
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  35. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  36. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  37. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  38. package/dist/resources/extensions/gsd/git-service.js +126 -2
  39. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  40. package/dist/resources/extensions/gsd/guided-flow.js +17 -5
  41. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  42. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  43. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  44. package/dist/resources/extensions/gsd/model-router.js +6 -0
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  46. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  47. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  48. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  49. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  50. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  51. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  52. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  53. package/dist/resources/extensions/gsd/state.js +44 -33
  54. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  55. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  56. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  57. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  58. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  59. package/dist/resources/extensions/gsd/worktree-manager.js +34 -8
  60. package/dist/resources/extensions/mcp-client/index.js +3 -1
  61. package/dist/resources/extensions/ollama/index.js +5 -1
  62. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  63. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  93. package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
  94. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  95. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  97. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  98. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  99. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  100. package/package.json +2 -3
  101. package/packages/daemon/src/logger.ts +4 -3
  102. package/packages/mcp-server/dist/server.d.ts +24 -0
  103. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  104. package/packages/mcp-server/dist/server.js +88 -87
  105. package/packages/mcp-server/dist/server.js.map +1 -1
  106. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  107. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  108. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  109. package/packages/mcp-server/src/server.ts +131 -105
  110. package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
  111. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  112. package/packages/native/package.json +1 -1
  113. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  114. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  115. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  116. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  117. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  118. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  119. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  120. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  121. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  122. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  123. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  124. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  125. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  127. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  128. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  129. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  130. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  131. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  133. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  134. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  135. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  136. package/packages/pi-ai/dist/models.test.js +36 -11
  137. package/packages/pi-ai/dist/models.test.js.map +1 -1
  138. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  139. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  140. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  141. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  142. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  143. package/packages/pi-ai/src/models.test.ts +48 -11
  144. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  145. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  146. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  148. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  150. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  152. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  154. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  156. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  158. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  160. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  163. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  165. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  167. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  168. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  169. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  170. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  171. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  173. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  175. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/index.js +1 -0
  177. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  189. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  191. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  193. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  194. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  195. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  196. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  197. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  198. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  199. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  200. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  201. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  202. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  203. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  204. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  205. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  206. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  207. package/packages/pi-coding-agent/src/index.ts +1 -0
  208. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  209. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  210. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  211. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  212. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  213. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  214. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  215. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  216. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  217. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  218. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  219. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
  220. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  221. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  222. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  223. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  224. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  225. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  226. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  227. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  228. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  229. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  230. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  231. package/packages/pi-tui/dist/components/editor.js +19 -0
  232. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  233. package/packages/pi-tui/dist/components/image.test.js +6 -5
  234. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  235. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  236. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  237. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  238. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  239. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  240. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
  241. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  242. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  243. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  244. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  245. package/packages/pi-tui/src/components/editor.ts +22 -0
  246. package/packages/pi-tui/src/components/image.test.ts +10 -5
  247. package/packages/pi-tui/src/editor-component.ts +3 -0
  248. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  249. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  250. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  251. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  252. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  253. package/scripts/install.js +15 -1
  254. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  255. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  256. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  257. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  258. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  259. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  260. package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
  261. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
  262. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
  263. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  264. package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
  265. package/src/resources/extensions/gsd/auto/loop.ts +47 -0
  266. package/src/resources/extensions/gsd/auto/phases.ts +16 -20
  267. package/src/resources/extensions/gsd/auto/session.ts +0 -2
  268. package/src/resources/extensions/gsd/auto-dispatch.ts +113 -24
  269. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  270. package/src/resources/extensions/gsd/auto-post-unit.ts +82 -73
  271. package/src/resources/extensions/gsd/auto-prompts.ts +330 -90
  272. package/src/resources/extensions/gsd/auto-recovery.ts +225 -24
  273. package/src/resources/extensions/gsd/auto-start.ts +54 -6
  274. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  275. package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
  276. package/src/resources/extensions/gsd/auto.ts +43 -22
  277. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
  278. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  279. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
  280. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  281. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  282. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  283. package/src/resources/extensions/gsd/component-types.ts +362 -0
  284. package/src/resources/extensions/gsd/detection.ts +58 -1
  285. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  286. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  287. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  288. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  289. package/src/resources/extensions/gsd/git-service.ts +133 -2
  290. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  291. package/src/resources/extensions/gsd/guided-flow.ts +20 -5
  292. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  293. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  294. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  295. package/src/resources/extensions/gsd/model-router.ts +6 -0
  296. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  297. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  298. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  299. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  300. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  301. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  302. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  303. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  304. package/src/resources/extensions/gsd/state.ts +49 -44
  305. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  306. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  307. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  308. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  309. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +94 -289
  310. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  311. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  312. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  313. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  314. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -197
  315. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  316. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  317. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  318. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  319. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  320. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  321. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  322. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  323. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  324. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  325. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  326. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  327. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -3
  328. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  329. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  330. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +9 -105
  331. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  332. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  333. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  334. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -57
  335. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  336. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  337. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  338. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  339. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  340. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  341. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  342. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  343. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  344. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -62
  345. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  346. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  347. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  348. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -49
  349. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  350. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -133
  351. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  352. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  353. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
  354. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
  355. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  356. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  357. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  358. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  359. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  360. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  361. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  362. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  363. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  364. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  365. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  366. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  367. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  368. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -5
  369. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  370. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  371. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  372. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +12 -61
  373. package/src/resources/extensions/gsd/tests/test-helpers.ts +21 -8
  374. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  375. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  376. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  377. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  378. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  379. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  380. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
  381. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -81
  382. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  383. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  384. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  385. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  386. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  387. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  388. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  389. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  390. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  391. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  392. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  393. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  394. package/src/resources/extensions/gsd/types.ts +3 -3
  395. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  396. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  397. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  398. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  399. package/src/resources/extensions/gsd/worktree-manager.ts +55 -7
  400. package/src/resources/extensions/mcp-client/index.ts +3 -1
  401. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  402. package/src/resources/extensions/ollama/index.ts +5 -1
  403. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  404. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  405. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  406. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  407. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  408. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  409. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  410. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  411. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  412. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  413. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  414. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -144
  415. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  416. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  417. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  418. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  419. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  420. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  421. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -75
  422. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  423. package/src/resources/extensions/gsd/tests/forensics-worktree-telemetry.test.ts +0 -145
  424. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  425. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  426. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -130
  427. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -43
  428. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
  429. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
@@ -13,13 +13,16 @@
13
13
  * - Conflict check: file overlap between slice plans (slice-parallel-conflict.ts)
14
14
  */
15
15
 
16
- import { spawn, type ChildProcess } from "node:child_process";
16
+ import { execFileSync, spawn, type ChildProcess } from "node:child_process";
17
+ import { randomUUID } from "node:crypto";
17
18
  import {
18
19
  appendFileSync,
19
20
  existsSync,
20
21
  writeFileSync,
21
22
  readFileSync,
22
23
  mkdirSync,
24
+ renameSync,
25
+ unlinkSync,
23
26
  } from "node:fs";
24
27
  import { join, dirname } from "node:path";
25
28
  import { fileURLToPath } from "node:url";
@@ -40,6 +43,8 @@ export interface SliceWorkerInfo {
40
43
  milestoneId: string;
41
44
  sliceId: string;
42
45
  pid: number;
46
+ workerToken: string;
47
+ processStartFingerprint: string | null;
43
48
  process: ChildProcess | null;
44
49
  worktreePath: string;
45
50
  startedAt: number;
@@ -69,13 +74,276 @@ export interface StartSliceParallelOpts {
69
74
 
70
75
  let sliceState: SliceOrchestratorState | null = null;
71
76
 
77
+ // ─── Persisted State (crash recovery) ──────────────────────────────────────
78
+ //
79
+ // Mirrors parallel-orchestrator.ts. Without persistence, a coordinator crash
80
+ // leaves orphaned worktrees on disk with no way to detect or clean them up
81
+ // on next session start. (Issue #4980 HIGH-8)
82
+
83
+ const SLICE_ORCHESTRATOR_STATE_FILE = "slice-orchestrator.json";
84
+ const TMP_SUFFIX = ".tmp";
85
+
86
+ interface PersistedSliceWorker {
87
+ milestoneId: string;
88
+ sliceId: string;
89
+ pid: number;
90
+ workerToken?: string;
91
+ processStartFingerprint?: string | null;
92
+ worktreePath: string;
93
+ startedAt: number;
94
+ state: "running" | "stopped" | "error";
95
+ completedUnits: number;
96
+ cost: number;
97
+ }
98
+
99
+ interface PersistedSliceState {
100
+ active: boolean;
101
+ workers: PersistedSliceWorker[];
102
+ totalCost: number;
103
+ budgetCeiling?: number;
104
+ maxWorkers: number;
105
+ startedAt: number;
106
+ basePath: string;
107
+ }
108
+
109
+ function sliceStateFilePath(basePath: string): string {
110
+ return join(gsdRoot(basePath), SLICE_ORCHESTRATOR_STATE_FILE);
111
+ }
112
+
113
+ function isPidAlive(pid: number): boolean {
114
+ if (!Number.isInteger(pid) || pid <= 0) return false;
115
+ try {
116
+ process.kill(pid, 0);
117
+ return true;
118
+ } catch (err) {
119
+ if ((err as NodeJS.ErrnoException).code === "EPERM") return true;
120
+ return false;
121
+ }
122
+ }
123
+
124
+ function readLinuxProcessStartFingerprint(pid: number): string | null {
125
+ try {
126
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf-8");
127
+ const afterCommand = stat.slice(stat.lastIndexOf(")") + 2).trim();
128
+ const fields = afterCommand.split(/\s+/);
129
+ const startTimeTicks = fields[19];
130
+ return startTimeTicks ? `linux-stat:${startTimeTicks}` : null;
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ function readPsProcessStartFingerprint(pid: number): string | null {
137
+ try {
138
+ const raw = execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
139
+ stdio: ["ignore", "pipe", "ignore"],
140
+ encoding: "utf-8",
141
+ }).trim().replace(/\s+/g, " ");
142
+ return raw ? `ps-lstart:${raw}` : null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ function readProcessStartFingerprint(pid: number): string | null {
149
+ if (!Number.isInteger(pid) || pid <= 0) return null;
150
+ return readLinuxProcessStartFingerprint(pid) ?? readPsProcessStartFingerprint(pid);
151
+ }
152
+
153
+ function linuxProcessEnvContains(pid: number, key: string, value: string): boolean | null {
154
+ if (process.platform !== "linux") return null;
155
+ try {
156
+ const env = readFileSync(`/proc/${pid}/environ`, "utf-8");
157
+ return env.split("\0").includes(`${key}=${value}`);
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+
163
+ function createWorkerToken(milestoneId: string, sliceId: string): string {
164
+ return `slice:${milestoneId}:${sliceId}:${Date.now()}:${randomUUID()}`;
165
+ }
166
+
167
+ function isRecoveredSliceWorkerAlive(worker: {
168
+ pid: number;
169
+ workerToken?: string;
170
+ processStartFingerprint?: string | null;
171
+ }): boolean {
172
+ if (!isPidAlive(worker.pid)) return false;
173
+ if (!worker.processStartFingerprint) return false;
174
+
175
+ const currentFingerprint = readProcessStartFingerprint(worker.pid);
176
+ if (!currentFingerprint || currentFingerprint !== worker.processStartFingerprint) {
177
+ return false;
178
+ }
179
+
180
+ if (worker.workerToken) {
181
+ const envMatches = linuxProcessEnvContains(
182
+ worker.pid,
183
+ "GSD_SLICE_WORKER_TOKEN",
184
+ worker.workerToken,
185
+ );
186
+ if (envMatches === false) return false;
187
+ }
188
+
189
+ return true;
190
+ }
191
+
192
+ /**
193
+ * Persist current slice orchestrator state. Atomic write (tmp + rename) to
194
+ * prevent partial reads if the coordinator dies mid-write.
195
+ */
196
+ function persistSliceState(): void {
197
+ if (!sliceState) return;
198
+ try {
199
+ const dir = gsdRoot(sliceState.basePath);
200
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
201
+
202
+ const persisted: PersistedSliceState = {
203
+ active: sliceState.active,
204
+ workers: [...sliceState.workers.values()].map((w) => ({
205
+ milestoneId: w.milestoneId,
206
+ sliceId: w.sliceId,
207
+ pid: w.pid,
208
+ workerToken: w.workerToken,
209
+ processStartFingerprint: w.processStartFingerprint,
210
+ worktreePath: w.worktreePath,
211
+ startedAt: w.startedAt,
212
+ state: w.state,
213
+ completedUnits: w.completedUnits,
214
+ cost: w.cost,
215
+ })),
216
+ totalCost: sliceState.totalCost,
217
+ budgetCeiling: sliceState.budgetCeiling,
218
+ maxWorkers: sliceState.maxWorkers,
219
+ startedAt: sliceState.startedAt,
220
+ basePath: sliceState.basePath,
221
+ };
222
+
223
+ const dest = sliceStateFilePath(sliceState.basePath);
224
+ const tmp = dest + TMP_SUFFIX;
225
+ writeFileSync(tmp, JSON.stringify(persisted, null, 2), "utf-8");
226
+ renameSync(tmp, dest);
227
+ lastPersistTs = Date.now();
228
+ } catch {
229
+ /* non-fatal: persistence is best-effort */
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Throttled wrapper around `persistSliceState`. Skips if the last successful
235
+ * persist was less than `PERSIST_THROTTLE_MS` ago; otherwise persists
236
+ * immediately. Use this on hot paths (e.g. `message_end` events) where we
237
+ * receive many events per second per worker. Terminal events (worker exit,
238
+ * crash, stop) should call `persistSliceState()` directly to guarantee the
239
+ * final state hits disk regardless of timing.
240
+ */
241
+ const PERSIST_THROTTLE_MS = 1000;
242
+ let lastPersistTs = 0;
243
+
244
+ function persistSliceStateThrottled(): void {
245
+ if (Date.now() - lastPersistTs < PERSIST_THROTTLE_MS) return;
246
+ persistSliceState();
247
+ }
248
+
249
+ function removeSliceStateFile(basePath: string): void {
250
+ try {
251
+ const p = sliceStateFilePath(basePath);
252
+ if (existsSync(p)) unlinkSync(p);
253
+ } catch {
254
+ /* non-fatal */
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Restore slice orchestrator state from disk. Filters dead-PID workers and
260
+ * removes their orphaned worktrees so a clean restart is possible.
261
+ *
262
+ * Returns null if no state file exists or no workers survive.
263
+ */
264
+ export function restoreSliceState(basePath: string): PersistedSliceState | null {
265
+ try {
266
+ const p = sliceStateFilePath(basePath);
267
+ if (!existsSync(p)) return null;
268
+ const persisted = JSON.parse(readFileSync(p, "utf-8")) as PersistedSliceState;
269
+
270
+ const survivors: PersistedSliceWorker[] = [];
271
+ const dead: PersistedSliceWorker[] = [];
272
+ for (const w of persisted.workers) {
273
+ if (w.state === "running" && isRecoveredSliceWorkerAlive(w)) {
274
+ survivors.push(w);
275
+ } else if (w.state === "running") {
276
+ dead.push(w);
277
+ } else {
278
+ survivors.push(w);
279
+ }
280
+ }
281
+
282
+ // Best-effort cleanup of orphaned worktrees from dead workers.
283
+ for (const w of dead) {
284
+ const wtName = `${w.milestoneId}-${w.sliceId}`;
285
+ try {
286
+ removeWorktree(persisted.basePath, wtName, { deleteBranch: true, force: true });
287
+ } catch {
288
+ /* worktree may already be gone */
289
+ }
290
+ }
291
+
292
+ persisted.workers = survivors;
293
+
294
+ if (survivors.length === 0) {
295
+ removeSliceStateFile(basePath);
296
+ return null;
297
+ }
298
+
299
+ return persisted;
300
+ } catch {
301
+ return null;
302
+ }
303
+ }
304
+
72
305
  // ─── Public API ────────────────────────────────────────────────────────────
73
306
 
74
307
  /**
75
308
  * Check whether slice-level parallel is currently active.
309
+ *
310
+ * If in-memory state is unset but a persisted state file exists with at
311
+ * least one live-PID worker, treat as active and rehydrate so a coordinator
312
+ * crash followed by a fresh process is detectable. (Issue #4980 HIGH-8)
76
313
  */
77
- export function isSliceParallelActive(): boolean {
78
- return sliceState?.active === true;
314
+ export function isSliceParallelActive(basePath?: string): boolean {
315
+ if (sliceState?.active === true) return true;
316
+ if (!basePath) return false;
317
+ const restored = restoreSliceState(basePath);
318
+ if (!restored || restored.workers.length === 0) return false;
319
+
320
+ // Rehydrate in-memory state from disk; processes are detached so we have
321
+ // no ChildProcess handles, only PIDs.
322
+ sliceState = {
323
+ active: restored.active,
324
+ workers: new Map(),
325
+ totalCost: restored.totalCost,
326
+ budgetCeiling: restored.budgetCeiling,
327
+ maxWorkers: restored.maxWorkers,
328
+ startedAt: restored.startedAt,
329
+ basePath: restored.basePath,
330
+ };
331
+ for (const w of restored.workers) {
332
+ sliceState.workers.set(w.sliceId, {
333
+ milestoneId: w.milestoneId,
334
+ sliceId: w.sliceId,
335
+ pid: w.pid,
336
+ process: null,
337
+ worktreePath: w.worktreePath,
338
+ workerToken: w.workerToken ?? "",
339
+ processStartFingerprint: w.processStartFingerprint ?? null,
340
+ startedAt: w.startedAt,
341
+ state: w.state,
342
+ completedUnits: w.completedUnits,
343
+ cost: w.cost,
344
+ });
345
+ }
346
+ return true;
79
347
  }
80
348
 
81
349
  /**
@@ -146,6 +414,8 @@ export async function startSliceParallel(
146
414
  milestoneId,
147
415
  sliceId: slice.id,
148
416
  pid: 0,
417
+ workerToken: createWorkerToken(milestoneId, slice.id),
418
+ processStartFingerprint: null,
149
419
  process: null,
150
420
  worktreePath: wtPath,
151
421
  startedAt: Date.now(),
@@ -162,12 +432,16 @@ export async function startSliceParallel(
162
432
  started.push(slice.id);
163
433
  } else {
164
434
  errors.push({ sid: slice.id, error: "Failed to spawn worker process" });
165
- worker.state = "error";
435
+ sliceState.workers.delete(slice.id);
436
+ try {
437
+ removeWorktree(basePath, wtName, { deleteBranch: true, force: true });
438
+ } catch { /* ignore cleanup failures */ }
166
439
  }
167
440
  } catch (err) {
168
441
  errors.push({ sid: slice.id, error: getErrorMessage(err) });
169
442
  // Best-effort cleanup of partially created worktree
170
443
  const wtName = `${milestoneId}-${slice.id}`;
444
+ sliceState.workers.delete(slice.id);
171
445
  try {
172
446
  removeWorktree(basePath, wtName, { deleteBranch: true, force: true });
173
447
  } catch { /* ignore cleanup failures */ }
@@ -177,6 +451,10 @@ export async function startSliceParallel(
177
451
  // If nothing started, deactivate
178
452
  if (started.length === 0) {
179
453
  sliceState.active = false;
454
+ removeSliceStateFile(basePath);
455
+ } else {
456
+ // Persist state for crash recovery (Issue #4980 HIGH-8).
457
+ persistSliceState();
180
458
  }
181
459
 
182
460
  return { started, errors };
@@ -187,13 +465,16 @@ export async function startSliceParallel(
187
465
  */
188
466
  export function stopSliceParallel(): void {
189
467
  if (!sliceState) return;
468
+ const basePath = sliceState.basePath;
190
469
 
191
470
  for (const worker of sliceState.workers.values()) {
192
- if (worker.process) {
193
- try {
471
+ try {
472
+ if (worker.process) {
194
473
  worker.process.kill("SIGTERM");
195
- } catch { /* already dead */ }
196
- }
474
+ } else if (worker.state === "running" && isRecoveredSliceWorkerAlive(worker)) {
475
+ process.kill(worker.pid, "SIGTERM");
476
+ }
477
+ } catch { /* already dead */ }
197
478
  worker.cleanup?.();
198
479
  worker.cleanup = undefined;
199
480
  worker.process = null;
@@ -207,6 +488,9 @@ export function stopSliceParallel(): void {
207
488
  }
208
489
 
209
490
  sliceState.active = false;
491
+ // Clear persisted state — clean shutdown means no recovery on next start.
492
+ // (Issue #4980 HIGH-8)
493
+ removeSliceStateFile(basePath);
210
494
  }
211
495
 
212
496
  /**
@@ -239,6 +523,7 @@ export function resetSliceOrchestrator(): void {
239
523
  }
240
524
  }
241
525
  sliceState = null;
526
+ lastPersistTs = 0;
242
527
  }
243
528
 
244
529
  // ─── Internal: Conflict Filtering ──────────────────────────────────────────
@@ -339,6 +624,7 @@ function spawnSliceWorker(
339
624
  GSD_MILESTONE_LOCK: milestoneId,
340
625
  GSD_PROJECT_ROOT: basePath,
341
626
  GSD_PARALLEL_WORKER: "1",
627
+ GSD_SLICE_WORKER_TOKEN: worker.workerToken,
342
628
  },
343
629
  stdio: ["ignore", "pipe", "pipe"],
344
630
  detached: false,
@@ -357,9 +643,15 @@ function spawnSliceWorker(
357
643
 
358
644
  worker.process = child;
359
645
  worker.pid = child.pid ?? 0;
646
+ worker.processStartFingerprint = worker.pid > 0
647
+ ? readProcessStartFingerprint(worker.pid)
648
+ : null;
360
649
 
361
650
  if (!child.pid) {
362
651
  worker.process = null;
652
+ worker.pid = 0;
653
+ worker.processStartFingerprint = null;
654
+ try { child.kill("SIGTERM"); } catch { /* best-effort */ }
363
655
  return false;
364
656
  }
365
657
 
@@ -438,6 +730,10 @@ function spawnSliceWorker(
438
730
  startedAt: w.startedAt,
439
731
  worktreePath: w.worktreePath,
440
732
  });
733
+
734
+ // Persist worker terminal state for crash recovery.
735
+ // (Issue #4980 HIGH-8)
736
+ persistSliceState();
441
737
  });
442
738
 
443
739
  return true;
@@ -474,6 +770,11 @@ function processSliceWorkerLine(
474
770
  sliceState.totalCost += usage.cost;
475
771
  }
476
772
  worker.completedUnits++;
773
+ // Persist cost / progress updates so a crash mid-run preserves them.
774
+ // Throttled (~1/s per process) so high-frequency message_end traffic
775
+ // does not saturate disk I/O. Worker exit / start / stop paths persist
776
+ // unthrottled to guarantee the terminal state lands. (Issue #4980 HIGH-8)
777
+ persistSliceStateThrottled();
477
778
  }
478
779
  }
479
780
  }
@@ -267,7 +267,7 @@ export async function deriveState(basePath: string): Promise<GSDState> {
267
267
  let synced = false;
268
268
  for (const diskId of diskIds) {
269
269
  if (!isGhostMilestone(basePath, diskId)) {
270
- insertMilestone({ id: diskId, status: 'active' });
270
+ insertMilestone(diskMilestoneInsert(basePath, diskId));
271
271
  synced = true;
272
272
  }
273
273
  }
@@ -327,6 +327,42 @@ function extractContextTitle(content: string | null, fallback: string): string {
327
327
  // Alias kept for backward compatibility within this file.
328
328
  const isStatusDone = isClosedStatus;
329
329
 
330
+ function loadSync(path: string | null): string | null {
331
+ if (!path) return null;
332
+ try {
333
+ return readFileSync(path, "utf-8");
334
+ } catch {
335
+ return null;
336
+ }
337
+ }
338
+
339
+ function diskMilestoneInsert(basePath: string, mid: string): {
340
+ id: string;
341
+ title?: string;
342
+ status: string;
343
+ depends_on: string[];
344
+ } {
345
+ const contextContent = loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT"));
346
+ const draftContent = !contextContent ? loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT")) : null;
347
+ const roadmapContent = loadSync(resolveMilestoneFile(basePath, mid, "ROADMAP"));
348
+ const summaryContent = loadSync(resolveMilestoneFile(basePath, mid, "SUMMARY"));
349
+ const roadmap = roadmapContent ? parseRoadmap(roadmapContent) : null;
350
+ const summary = summaryContent ? parseSummary(summaryContent) : null;
351
+ const summaryTerminal = summaryContent != null && isTerminalMilestoneSummaryContent(summaryContent);
352
+ const parked = resolveMilestoneFile(basePath, mid, "PARKED") !== null;
353
+
354
+ return {
355
+ id: mid,
356
+ title: roadmap
357
+ ? stripMilestonePrefix(roadmap.title)
358
+ : (contextContent || draftContent)
359
+ ? extractContextTitle(contextContent || draftContent, mid)
360
+ : (summary?.title || mid),
361
+ status: parked ? "parked" : summaryTerminal ? "complete" : "active",
362
+ depends_on: parseContextDependsOn(contextContent ?? draftContent),
363
+ };
364
+ }
365
+
330
366
  /**
331
367
  * Derive GSD state from the milestones/slices/tasks DB tables.
332
368
  * Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
@@ -342,7 +378,7 @@ function reconcileDiskToDb(basePath: string): MilestoneRow[] {
342
378
  let synced = false;
343
379
  for (const diskId of diskIds) {
344
380
  if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
345
- insertMilestone({ id: diskId, status: 'active' });
381
+ insertMilestone(diskMilestoneInsert(basePath, diskId));
346
382
  synced = true;
347
383
  }
348
384
  }
@@ -677,32 +713,12 @@ function resolveSliceDependencies(activeMilestoneSlices: SliceRow[]): { activeSl
677
713
  }
678
714
  }
679
715
 
680
- let bestFallback: SliceRow | null = null;
681
- let bestFallbackSatisfied = -1;
682
-
683
716
  for (const s of activeMilestoneSlices) {
684
717
  if (isStatusDone(s.status)) continue;
685
718
  if (isDeferredStatus(s.status)) continue;
686
719
  if (s.depends.every(dep => doneSliceIds.has(dep))) {
687
720
  return { activeSlice: { id: s.id, title: s.title }, activeSliceRow: s };
688
721
  }
689
- const satisfied = s.depends.filter(dep => doneSliceIds.has(dep)).length;
690
- if (satisfied > bestFallbackSatisfied || (satisfied === bestFallbackSatisfied && !bestFallback)) {
691
- bestFallback = s;
692
- bestFallbackSatisfied = satisfied;
693
- }
694
- }
695
-
696
- if (bestFallback) {
697
- const unmet = bestFallback.depends.filter(dep => !doneSliceIds.has(dep));
698
- logWarning(
699
- "state",
700
- `No slice has all deps satisfied — falling back to ${bestFallback.id} ` +
701
- `(${bestFallbackSatisfied}/${bestFallback.depends.length} deps met, ` +
702
- `unmet: ${unmet.join(", ")})`,
703
- { mid: activeMilestoneSlices[0]?.milestone_id, sid: bestFallback.id },
704
- );
705
- return { activeSlice: { id: bestFallback.id, title: bestFallback.title }, activeSliceRow: bestFallback };
706
722
  }
707
723
 
708
724
  return { activeSlice: null, activeSliceRow: null };
@@ -716,14 +732,19 @@ async function reconcileSliceTasks(
716
732
  ): Promise<TaskRow[]> {
717
733
  let tasks = getSliceTasks(milestoneId, sliceId);
718
734
 
719
- if (tasks.length === 0 && planFile) {
735
+ // #3600/#4974: import missing plan-file tasks even when the DB already has
736
+ // a partial task set. Existing DB task statuses stay authoritative.
737
+ if (planFile) {
720
738
  try {
721
739
  const planContent = await loadFile(planFile);
722
740
  if (planContent) {
723
741
  const diskPlan = parsePlan(planContent);
724
742
  if (diskPlan.tasks.length > 0) {
743
+ const dbTaskIds = new Set(tasks.map(t => t.id));
744
+ let inserted = 0;
725
745
  for (let i = 0; i < diskPlan.tasks.length; i++) {
726
746
  const t = diskPlan.tasks[i];
747
+ if (dbTaskIds.has(t.id)) continue;
727
748
  try {
728
749
  insertTask({
729
750
  id: t.id,
@@ -733,12 +754,15 @@ async function reconcileSliceTasks(
733
754
  status: t.done ? 'complete' : 'pending',
734
755
  sequence: i + 1,
735
756
  });
757
+ inserted++;
736
758
  } catch (insertErr) {
737
759
  logWarning("reconcile", `failed to insert task ${t.id} from plan file: ${insertErr instanceof Error ? insertErr.message : String(insertErr)}`);
738
760
  }
739
761
  }
740
- tasks = getSliceTasks(milestoneId, sliceId);
741
- logWarning("reconcile", `imported ${tasks.length} tasks from plan file for ${milestoneId}/${sliceId} — DB was empty (#3600)`, { mid: milestoneId, sid: sliceId });
762
+ if (inserted > 0) {
763
+ tasks = getSliceTasks(milestoneId, sliceId);
764
+ logWarning("reconcile", `imported ${inserted} missing task(s) from plan file for ${milestoneId}/${sliceId}`, { mid: milestoneId, sid: sliceId });
765
+ }
742
766
  }
743
767
  }
744
768
  } catch (err) {
@@ -1569,31 +1593,12 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
1569
1593
  };
1570
1594
  }
1571
1595
  } else {
1572
- let bestFallbackLegacy: { id: string; title: string; depends: string[] } | null = null;
1573
- let bestFallbackLegacySatisfied = -1;
1574
-
1575
1596
  for (const s of activeRoadmap.slices) {
1576
1597
  if (s.done) continue;
1577
1598
  if (s.depends.every(dep => doneSliceIds.has(dep))) {
1578
1599
  activeSlice = { id: s.id, title: s.title };
1579
1600
  break;
1580
1601
  }
1581
- const satisfied = s.depends.filter(dep => doneSliceIds.has(dep)).length;
1582
- if (satisfied > bestFallbackLegacySatisfied) {
1583
- bestFallbackLegacy = s;
1584
- bestFallbackLegacySatisfied = satisfied;
1585
- }
1586
- }
1587
-
1588
- if (!activeSlice && bestFallbackLegacy) {
1589
- const unmet = bestFallbackLegacy.depends.filter(dep => !doneSliceIds.has(dep));
1590
- logWarning(
1591
- "state",
1592
- `No slice has all deps satisfied — falling back to ${bestFallbackLegacy.id} ` +
1593
- `(${bestFallbackLegacySatisfied}/${bestFallbackLegacy.depends.length} deps met, ` +
1594
- `unmet: ${unmet.join(", ")})`,
1595
- );
1596
- activeSlice = { id: bestFallbackLegacy.id, title: bestFallbackLegacy.title };
1597
1602
  }
1598
1603
  }
1599
1604