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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/README.md +1 -1
  2. package/dist/claude-cli-check.js +5 -1
  3. package/dist/headless.js +49 -4
  4. package/dist/resource-loader.d.ts +40 -0
  5. package/dist/resource-loader.js +32 -13
  6. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  7. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  8. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  9. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  10. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  11. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
  14. package/dist/resources/extensions/gsd/auto/loop.js +43 -0
  15. package/dist/resources/extensions/gsd/auto/phases.js +15 -21
  16. package/dist/resources/extensions/gsd/auto/session.js +0 -2
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +102 -24
  18. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +71 -64
  20. package/dist/resources/extensions/gsd/auto-prompts.js +329 -102
  21. package/dist/resources/extensions/gsd/auto-recovery.js +195 -23
  22. package/dist/resources/extensions/gsd/auto-start.js +34 -24
  23. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  24. package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
  25. package/dist/resources/extensions/gsd/auto.js +31 -20
  26. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  27. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  28. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
  29. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  30. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  31. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  32. package/dist/resources/extensions/gsd/component-types.js +69 -0
  33. package/dist/resources/extensions/gsd/detection.js +49 -1
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  35. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  36. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  37. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  38. package/dist/resources/extensions/gsd/git-service.js +126 -2
  39. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  40. package/dist/resources/extensions/gsd/guided-flow.js +17 -5
  41. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  42. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  43. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  44. package/dist/resources/extensions/gsd/model-router.js +6 -0
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  46. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  47. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  48. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  49. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  50. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  51. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  52. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  53. package/dist/resources/extensions/gsd/state.js +44 -33
  54. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  55. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  56. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  57. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  58. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  59. package/dist/resources/extensions/gsd/worktree-manager.js +34 -8
  60. package/dist/resources/extensions/mcp-client/index.js +3 -1
  61. package/dist/resources/extensions/ollama/index.js +5 -1
  62. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  63. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  93. package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
  94. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  95. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  97. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  98. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  99. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  100. package/package.json +2 -3
  101. package/packages/daemon/src/logger.ts +4 -3
  102. package/packages/mcp-server/dist/server.d.ts +24 -0
  103. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  104. package/packages/mcp-server/dist/server.js +88 -87
  105. package/packages/mcp-server/dist/server.js.map +1 -1
  106. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  107. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  108. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  109. package/packages/mcp-server/src/server.ts +131 -105
  110. package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
  111. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  112. package/packages/native/package.json +1 -1
  113. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  114. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  115. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  116. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  117. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  118. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  119. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  120. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  121. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  122. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  123. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  124. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  125. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  127. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  128. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  129. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  130. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  131. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  133. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  134. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  135. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  136. package/packages/pi-ai/dist/models.test.js +36 -11
  137. package/packages/pi-ai/dist/models.test.js.map +1 -1
  138. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  139. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  140. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  141. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  142. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  143. package/packages/pi-ai/src/models.test.ts +48 -11
  144. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  145. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  146. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  148. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  150. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  152. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  154. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  156. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  158. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  160. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  163. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  165. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  167. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  168. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  169. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  170. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  171. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  173. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  175. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/index.js +1 -0
  177. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  189. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  191. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  193. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  194. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  195. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  196. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  197. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  198. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  199. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  200. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  201. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  202. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  203. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  204. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  205. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  206. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  207. package/packages/pi-coding-agent/src/index.ts +1 -0
  208. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  209. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  210. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  211. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  212. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  213. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  214. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  215. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  216. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  217. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  218. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  219. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
  220. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  221. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  222. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  223. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  224. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  225. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  226. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  227. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  228. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  229. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  230. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  231. package/packages/pi-tui/dist/components/editor.js +19 -0
  232. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  233. package/packages/pi-tui/dist/components/image.test.js +6 -5
  234. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  235. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  236. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  237. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  238. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  239. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  240. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
  241. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  242. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  243. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  244. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  245. package/packages/pi-tui/src/components/editor.ts +22 -0
  246. package/packages/pi-tui/src/components/image.test.ts +10 -5
  247. package/packages/pi-tui/src/editor-component.ts +3 -0
  248. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  249. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  250. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  251. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  252. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  253. package/scripts/install.js +15 -1
  254. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  255. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  256. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  257. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  258. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  259. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  260. package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
  261. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
  262. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
  263. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  264. package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
  265. package/src/resources/extensions/gsd/auto/loop.ts +47 -0
  266. package/src/resources/extensions/gsd/auto/phases.ts +16 -20
  267. package/src/resources/extensions/gsd/auto/session.ts +0 -2
  268. package/src/resources/extensions/gsd/auto-dispatch.ts +113 -24
  269. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  270. package/src/resources/extensions/gsd/auto-post-unit.ts +82 -73
  271. package/src/resources/extensions/gsd/auto-prompts.ts +330 -90
  272. package/src/resources/extensions/gsd/auto-recovery.ts +225 -24
  273. package/src/resources/extensions/gsd/auto-start.ts +54 -6
  274. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  275. package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
  276. package/src/resources/extensions/gsd/auto.ts +43 -22
  277. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
  278. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  279. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
  280. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  281. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  282. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  283. package/src/resources/extensions/gsd/component-types.ts +362 -0
  284. package/src/resources/extensions/gsd/detection.ts +58 -1
  285. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  286. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  287. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  288. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  289. package/src/resources/extensions/gsd/git-service.ts +133 -2
  290. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  291. package/src/resources/extensions/gsd/guided-flow.ts +20 -5
  292. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  293. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  294. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  295. package/src/resources/extensions/gsd/model-router.ts +6 -0
  296. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  297. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  298. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  299. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  300. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  301. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  302. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  303. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  304. package/src/resources/extensions/gsd/state.ts +49 -44
  305. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  306. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  307. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  308. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  309. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +94 -289
  310. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  311. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  312. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  313. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  314. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -197
  315. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  316. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  317. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  318. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  319. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  320. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  321. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  322. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  323. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  324. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  325. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  326. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  327. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -3
  328. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  329. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  330. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +9 -105
  331. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  332. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  333. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  334. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -57
  335. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  336. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  337. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  338. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  339. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  340. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  341. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  342. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  343. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  344. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -62
  345. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  346. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  347. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  348. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -49
  349. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  350. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -133
  351. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  352. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  353. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
  354. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
  355. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  356. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  357. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  358. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  359. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  360. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  361. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  362. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  363. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  364. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  365. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  366. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  367. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  368. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -5
  369. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  370. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  371. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  372. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +12 -61
  373. package/src/resources/extensions/gsd/tests/test-helpers.ts +21 -8
  374. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  375. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  376. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  377. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  378. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  379. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  380. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
  381. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -81
  382. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  383. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  384. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  385. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  386. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  387. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  388. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  389. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  390. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  391. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  392. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  393. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  394. package/src/resources/extensions/gsd/types.ts +3 -3
  395. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  396. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  397. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  398. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  399. package/src/resources/extensions/gsd/worktree-manager.ts +55 -7
  400. package/src/resources/extensions/mcp-client/index.ts +3 -1
  401. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  402. package/src/resources/extensions/ollama/index.ts +5 -1
  403. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  404. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  405. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  406. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  407. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  408. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  409. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  410. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  411. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  412. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  413. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  414. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -144
  415. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  416. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  417. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  418. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  419. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  420. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  421. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -75
  422. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  423. package/src/resources/extensions/gsd/tests/forensics-worktree-telemetry.test.ts +0 -145
  424. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  425. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  426. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -130
  427. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -43
  428. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
  429. /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { randomUUID } from "node:crypto";
@@ -21,6 +21,7 @@ import {
21
21
  } from "../interrupted-session.ts";
22
22
  import { gsdRoot } from "../paths.ts";
23
23
  import type { GSDState } from "../types.ts";
24
+ import { _synthesizePausedSessionRecoveryForTest } from "../auto.ts";
24
25
 
25
26
  function makeTmpBase(): string {
26
27
  const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
@@ -167,6 +168,54 @@ test("readPausedSessionMetadata reads paused-session metadata when present", ()
167
168
  }
168
169
  });
169
170
 
171
+ test("paused session recovery consumes JSONL without deleting the evidence file", (t) => {
172
+ const base = makeTmpBase();
173
+ t.after(() => cleanup(base));
174
+
175
+ const sessionFile = join(base, "paused-session.jsonl");
176
+ writeFileSync(
177
+ sessionFile,
178
+ [
179
+ JSON.stringify({ type: "session", id: "session-1" }),
180
+ JSON.stringify({
181
+ type: "message",
182
+ message: {
183
+ role: "assistant",
184
+ content: [
185
+ {
186
+ type: "toolCall",
187
+ name: "bash",
188
+ id: "tool-1",
189
+ arguments: { command: "echo paused" },
190
+ },
191
+ ],
192
+ },
193
+ }),
194
+ JSON.stringify({
195
+ type: "message",
196
+ message: {
197
+ role: "toolResult",
198
+ toolCallId: "tool-1",
199
+ toolName: "bash",
200
+ isError: false,
201
+ content: "paused\n",
202
+ },
203
+ }),
204
+ ].join("\n"),
205
+ "utf-8",
206
+ );
207
+
208
+ const recovery = _synthesizePausedSessionRecoveryForTest(
209
+ base,
210
+ "execute-task",
211
+ "M001/S01/T01",
212
+ sessionFile,
213
+ );
214
+
215
+ assert.equal(recovery?.trace.toolCallCount, 1);
216
+ assert.equal(existsSync(sessionFile), true, "paused JSONL must remain available after synthesis");
217
+ });
218
+
170
219
  test("readPausedSessionMetadata preserves unitType and unitId through round-trip", () => {
171
220
  const base = makeTmpBase();
172
221
  try {
@@ -351,9 +351,9 @@ skills_used: []
351
351
  const dbState = await deriveStateFromDb(base);
352
352
 
353
353
  assertStatesEqual(dbState, fileState, 'E-blocked');
354
- // With partial-dep fallback, circular deps no longer block fallback picks first eligible slice
355
- assert.deepStrictEqual(dbState.phase, 'planning', 'E-blocked: phase is planning (fallback picks a slice)');
356
- assert.ok(dbState.activeSlice !== null, 'E-blocked: activeSlice is set via fallback');
354
+ assert.deepStrictEqual(dbState.phase, 'blocked', 'E-blocked: phase is blocked when no slice deps are satisfied');
355
+ assert.deepStrictEqual(dbState.activeSlice, null, 'E-blocked: no activeSlice is selected through unmet deps');
356
+ assert.ok(dbState.blockers.some(b => b.includes('No slice eligible')), 'E-blocked: blocker explains no eligible slice');
357
357
 
358
358
  closeDatabase();
359
359
  } finally {
@@ -112,6 +112,46 @@ async function main(): Promise<void> {
112
112
  cleanup(base);
113
113
  }
114
114
 
115
+ console.log("\n=== #4974: summary-only disk milestones keep parsed title ===");
116
+
117
+ {
118
+ const summaryOnlyBase = createFixtureBase();
119
+ const summaryOnlyDbPath = join(summaryOnlyBase, ".gsd", "gsd.db");
120
+
121
+ try {
122
+ openDatabase(summaryOnlyDbPath);
123
+ insertMilestone({ id: "M001", title: "M001: Existing DB Milestone", status: "complete", depends_on: [] });
124
+
125
+ writeFile(
126
+ summaryOnlyBase,
127
+ "milestones/M002/SUMMARY.md",
128
+ `---\nid: M002\nstatus: complete\n---\n\n# M002: Summary-Only Milestone\n\nComplete.`,
129
+ );
130
+
131
+ invalidateStateCache();
132
+ const state = await deriveStateFromDb(summaryOnlyBase);
133
+ const m002Entry = state.registry.find((m) => m.id === "M002");
134
+
135
+ assertTrue(
136
+ m002Entry !== undefined,
137
+ "M002 summary-only disk milestone should appear in state.registry (#4974)",
138
+ );
139
+ assertEq(
140
+ m002Entry?.title,
141
+ "Summary-Only Milestone",
142
+ "M002 summary-only disk milestone should use parsed SUMMARY title (#4974)",
143
+ );
144
+ assertEq(
145
+ m002Entry?.status,
146
+ "complete",
147
+ "M002 summary-only disk milestone should reconcile as complete (#4974)",
148
+ );
149
+ } finally {
150
+ closeDatabase();
151
+ cleanup(summaryOnlyBase);
152
+ }
153
+ }
154
+
115
155
  report();
116
156
  }
117
157
 
@@ -14,6 +14,7 @@ import {
14
14
  getAllMilestones,
15
15
  insertSlice,
16
16
  insertTask,
17
+ getSliceTasks,
17
18
  updateTaskStatus,
18
19
  } from '../gsd-db.ts';
19
20
  // ─── Fixture Helpers ───────────────────────────────────────────────────────
@@ -270,6 +271,93 @@ describe('derive-state-db', async () => {
270
271
  }
271
272
  });
272
273
 
274
+ test('derive-state-db: partial task rows reconcile missing plan tasks before summarizing', async (t) => {
275
+ const base = createFixtureBase();
276
+ t.after(() => {
277
+ closeDatabase();
278
+ cleanup(base);
279
+ });
280
+
281
+ const partialTaskPlan = `# S01: First Slice
282
+
283
+ **Goal:** Test partial task DB reconciliation.
284
+ **Demo:** Tests pass.
285
+
286
+ ## Tasks
287
+
288
+ - [x] **T01: Existing Complete** \`est:10m\`
289
+ Already complete in DB.
290
+
291
+ - [ ] **T02: Missing Pending** \`est:10m\`
292
+ Missing from DB but present in the plan.
293
+ `;
294
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
295
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', partialTaskPlan);
296
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
297
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
298
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T02-PLAN.md', '# T02 Plan');
299
+
300
+ openDatabase(':memory:');
301
+ insertMilestone({ id: 'M001', title: 'Test Milestone', status: 'active' });
302
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First Slice', status: 'active', risk: 'low', depends: [] });
303
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Existing Complete', status: 'complete' });
304
+
305
+ invalidateStateCache();
306
+ const state = await deriveState(base);
307
+
308
+ const dbTasks = getSliceTasks('M001', 'S01');
309
+ assert.deepStrictEqual(dbTasks.length, 2, 'partial-task-db: missing T02 imported from plan');
310
+ assert.deepStrictEqual(dbTasks.find(t => t.id === 'T01')?.status, 'complete', 'partial-task-db: existing complete T01 preserved');
311
+ assert.deepStrictEqual(dbTasks.find(t => t.id === 'T02')?.status, 'pending', 'partial-task-db: missing T02 inserted pending');
312
+ assert.deepStrictEqual(state.phase, 'executing', 'partial-task-db: phase remains executing, not summarizing');
313
+ assert.deepStrictEqual(state.activeTask?.id, 'T02', 'partial-task-db: activeTask is missing plan task T02');
314
+ assert.deepStrictEqual(state.progress?.tasks, { done: 1, total: 2 }, 'partial-task-db: task progress includes reconciled plan task');
315
+ });
316
+
317
+ test('derive-state-db: empty DB disk import preserves milestone completion and dependencies', async (t) => {
318
+ const base = createFixtureBase();
319
+ t.after(() => {
320
+ closeDatabase();
321
+ cleanup(base);
322
+ });
323
+
324
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', `# M001: Foundation
325
+
326
+ **Vision:** Foundation.
327
+
328
+ ## Slices
329
+
330
+ - [x] **S01: Done** \`risk:low\` \`depends:[]\`
331
+ > After this: Done.
332
+ `);
333
+ writeFile(base, 'milestones/M001/M001-VALIDATION.md', '---\nverdict: pass\nremediation_round: 0\n---\n\nPassed.');
334
+ writeFile(base, 'milestones/M001/M001-SUMMARY.md', '# M001 Summary\n\nDone.');
335
+ writeFile(base, 'milestones/M002/M002-CONTEXT.md', '---\ndepends_on:\n - M001\n---\n\n# M002: Dependent\n');
336
+ writeFile(base, 'milestones/M002/M002-ROADMAP.md', `# M002: Dependent
337
+
338
+ **Vision:** Active work.
339
+
340
+ ## Slices
341
+
342
+ - [ ] **S01: Work** \`risk:low\` \`depends:[]\`
343
+ > After this: Done.
344
+ `);
345
+ writeFile(base, 'milestones/M003/M003-CONTEXT.md', '---\ndepends_on:\n - M002\n---\n\n# M003: Blocked\n');
346
+
347
+ openDatabase(':memory:');
348
+
349
+ invalidateStateCache();
350
+ const state = await deriveState(base);
351
+
352
+ const milestones = getAllMilestones();
353
+ assert.deepStrictEqual(milestones.find(m => m.id === 'M001')?.status, 'complete', 'disk-import: terminal summary imports M001 as complete');
354
+ assert.deepStrictEqual(milestones.find(m => m.id === 'M002')?.depends_on, ['M001'], 'disk-import: M002 depends_on imported from CONTEXT');
355
+ assert.deepStrictEqual(milestones.find(m => m.id === 'M003')?.depends_on, ['M002'], 'disk-import: M003 depends_on imported from CONTEXT');
356
+ assert.deepStrictEqual(state.activeMilestone?.id, 'M002', 'disk-import: M002 is active because M001 completion satisfies dependency');
357
+ assert.deepStrictEqual(state.registry.find(e => e.id === 'M001')?.status, 'complete', 'disk-import: M001 registry status is complete');
358
+ assert.deepStrictEqual(state.registry.find(e => e.id === 'M003')?.status, 'pending', 'disk-import: M003 stays pending on unmet M002 dependency');
359
+ });
360
+
273
361
  // ─── Test 5: Requirements counting from disk (DB no longer used for content) ─
274
362
  test('derive-state-db: requirements from disk content', async () => {
275
363
  const base = createFixtureBase();
@@ -616,10 +704,10 @@ describe('derive-state-db', async () => {
616
704
  invalidateStateCache();
617
705
  const dbState = await deriveStateFromDb(base);
618
706
 
619
- // With partial-dep fallback, circular deps no longer block fallback picks first eligible slice
620
- assert.deepStrictEqual(dbState.phase, 'planning', 'blocked-db: phase is planning (fallback picks a slice)');
707
+ assert.deepStrictEqual(dbState.phase, 'blocked', 'blocked-db: phase is blocked when no slice deps are satisfied');
621
708
  assert.deepStrictEqual(dbState.phase, fileState.phase, 'blocked-db: phase matches filesystem');
622
- assert.ok(dbState.activeSlice !== null, 'blocked-db: activeSlice is set via fallback');
709
+ assert.deepStrictEqual(dbState.activeSlice, null, 'blocked-db: no activeSlice is selected through unmet deps');
710
+ assert.ok(dbState.blockers.some(b => b.includes('No slice eligible')), 'blocked-db: blocker explains no eligible slice');
623
711
 
624
712
  closeDatabase();
625
713
  } finally {
@@ -431,7 +431,7 @@ Continue from step 2.
431
431
  cleanup(base1);
432
432
  }
433
433
 
434
- // Case B: S01 depends on nonexistent S99 -> fallback picks best available slice
434
+ // Case B: S01 depends on nonexistent S99 -> no slice is eligible
435
435
  const base2 = createFixtureBase();
436
436
  try {
437
437
  writeRoadmap(base2, 'M001', `# M001: Test Milestone
@@ -446,8 +446,9 @@ Continue from step 2.
446
446
 
447
447
  const state2 = await deriveState(base2);
448
448
 
449
- assert.deepStrictEqual(state2.phase, 'planning', 'blocked-B: phase is planning (fallback picks S01)');
450
- assert.deepStrictEqual(state2.activeSlice?.id, 'S01', 'blocked-B: activeSlice is S01 via fallback');
449
+ assert.deepStrictEqual(state2.phase, 'blocked', 'blocked-B: phase is blocked when dependency is unsatisfied');
450
+ assert.deepStrictEqual(state2.activeSlice, null, 'blocked-B: no activeSlice selected through unmet deps');
451
+ assert.ok(state2.blockers.some(b => b.includes('No slice eligible')), 'blocked-B: blocker explains no eligible slice');
451
452
  } finally {
452
453
  cleanup(base2);
453
454
  }
@@ -4,7 +4,7 @@
4
4
  * Verify that state.ts contains the disk-to-DB task reconciliation logic
5
5
  * that prevents the dispatcher from getting stuck in an infinite planning
6
6
  * loop when the planner writes a PLAN.md but never calls the persistence
7
- * tool, leaving the DB with zero task rows.
7
+ * tool, leaving the DB with zero or partial task rows.
8
8
  */
9
9
 
10
10
  import { describe, test } from "node:test";
@@ -24,7 +24,8 @@ describe("dispatcher stuck-planning reconciliation (#3656)", () => {
24
24
  });
25
25
 
26
26
  test("contains plan-file task reconciliation block", () => {
27
- assert.match(source, /tasks\.length\s*===\s*0\s*&&\s*planFile/);
27
+ assert.match(source, /if\s*\(\s*planFile\s*\)/);
28
+ assert.match(source, /dbTaskIds\.has\(t\.id\)/);
28
29
  });
29
30
 
30
31
  test("calls insertTask for each disk plan task", () => {
@@ -1,154 +1,164 @@
1
- // Structural contracts for GSD extension bootstrap isolation.
1
+ // Behavioural contract for GSD extension bootstrap isolation (#4168, #4172).
2
2
  //
3
- // The /gsd command must survive failures in the full extension bootstrap
4
- // (register-extension.ts). This guards against the regression where a
5
- // Windows-specific import failure in register-shortcuts.ts silently
6
- // prevented /gsd from being registered at all (#4168, #4172).
3
+ // Guarantee: the `/gsd` slash command must be registered on pi even if the
4
+ // full bootstrap (shortcuts, tools, hooks, ecosystem) throws during import or
5
+ // execution. Prior regressions: a Windows-specific failure in register-
6
+ // shortcuts.ts silently prevented /gsd from being registered at all because
7
+ // registerGSDCommand was called inside the same try that loaded shortcuts.
8
+ //
9
+ // These tests exercise the real default export of index.ts (which calls
10
+ // registerGSDCommand via dynamic import, then attempts the full bootstrap)
11
+ // with a minimal mock ExtensionAPI and verify the observable behaviour
12
+ // directly: /gsd is registered in both the happy path and the degraded path.
13
+ //
14
+ // Anti-regression proof (documented in commit):
15
+ // neuter index.ts to register /gsd inside the same try{} as
16
+ // register-extension → the degraded-path test fails (no /gsd command
17
+ // registered when register-extension throws). Restore → passes.
7
18
 
8
19
  import { describe, test } from "node:test";
9
20
  import assert from "node:assert/strict";
10
- import { readFileSync } from "node:fs";
11
- import { join, dirname } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const indexSrc = readFileSync(join(__dirname, "../index.ts"), "utf-8");
16
- const registerExtSrc = readFileSync(
17
- join(__dirname, "../bootstrap/register-extension.ts"),
18
- "utf-8",
19
- );
20
-
21
- // ─── index.ts: core /gsd command must be registered before full bootstrap ─────
22
-
23
- describe("index.ts bootstrap isolation", () => {
24
- test("imports registerGSDCommand from commands/index.js separately", () => {
25
- assert.ok(
26
- indexSrc.includes('./commands/index.js"') || indexSrc.includes("./commands/index.js'"),
27
- "index.ts must import registerGSDCommand from ./commands/index.js",
28
- );
29
- });
30
-
31
- test("calls registerGSDCommand before importing register-extension.js", () => {
32
- const gsdCommandCallPos = indexSrc.indexOf("registerGSDCommand(pi)");
33
- const bootstrapImportPos = indexSrc.indexOf(
34
- './bootstrap/register-extension.js"',
35
- );
36
21
 
37
- assert.ok(gsdCommandCallPos >= 0, "must call registerGSDCommand(pi)");
38
- assert.ok(bootstrapImportPos >= 0, "must import register-extension.js");
22
+ import registerExtension from "../index.ts";
23
+
24
+ type RegisterFn = (name: string, def: unknown) => void;
25
+
26
+ function makePi(overrides: Partial<Record<string, unknown>> = {}) {
27
+ const registered: Array<[string, unknown]> = [];
28
+ const registerCommand: RegisterFn = (name, def) => {
29
+ registered.push([name, def]);
30
+ };
31
+ const events = {
32
+ on: () => {},
33
+ off: () => {},
34
+ emit: () => {},
35
+ };
36
+ const pi = {
37
+ registerCommand,
38
+ registerTool: () => {},
39
+ registerHook: () => {},
40
+ registerShortcut: () => {},
41
+ events,
42
+ ...overrides,
43
+ };
44
+ return { pi, registered };
45
+ }
46
+
47
+ describe("extension bootstrap isolation (#4168, #4172)", () => {
48
+ test("happy path: /gsd command is registered", async () => {
49
+ const { pi, registered } = makePi();
50
+ await registerExtension(pi as any);
51
+ const names = registered.map(([n]) => n);
39
52
  assert.ok(
40
- gsdCommandCallPos < bootstrapImportPos,
41
- "registerGSDCommand(pi) must be called BEFORE importing register-extension.js",
53
+ names.includes("gsd"),
54
+ `expected 'gsd' in registered commands, got ${JSON.stringify(names)}`,
42
55
  );
43
56
  });
44
57
 
45
- test("wraps register-extension.js import in try-catch", () => {
46
- // The dynamic import of register-extension.js must be inside a try block
47
- const tryPos = indexSrc.indexOf("try {");
48
- const bootstrapImportPos = indexSrc.indexOf(
49
- './bootstrap/register-extension.js"',
50
- );
51
- const catchPos = indexSrc.indexOf("catch (err)");
52
-
53
- assert.ok(tryPos >= 0, "must have try block");
54
- assert.ok(catchPos >= 0, "must have catch block");
58
+ test("degraded path: /gsd still registered when registerCommand throws for non-core commands", async () => {
59
+ // Simulate the Windows-style failure: pi.registerCommand throws for a
60
+ // specific non-core command ('kill' is a simple target registered by
61
+ // the full bootstrap) — the full bootstrap must fail but /gsd must
62
+ // already be registered before the failure occurs.
63
+ const registered: Array<[string, unknown]> = [];
64
+ const pi = {
65
+ registerCommand: (name: string, def: unknown) => {
66
+ if (name !== "gsd" && name !== "worktree" && name !== "exit") {
67
+ // Let /gsd, /worktree, /exit succeed (they precede the non-core
68
+ // loop); throw when the first non-core registration fires.
69
+ }
70
+ if (name === "kill") throw new Error("simulated windows failure");
71
+ registered.push([name, def]);
72
+ },
73
+ registerTool: () => {},
74
+ registerHook: () => {},
75
+ registerShortcut: () => {},
76
+ events: { on: () => {}, off: () => {}, emit: () => {} },
77
+ };
78
+
79
+ // registerExtension must not throw — the outer try/catch in index.ts
80
+ // swallows bootstrap failures after /gsd is already registered.
81
+ await registerExtension(pi as any);
82
+
83
+ const names = registered.map(([n]) => n);
55
84
  assert.ok(
56
- tryPos < bootstrapImportPos && bootstrapImportPos < catchPos,
57
- "register-extension.js import must be wrapped in try-catch",
85
+ names.includes("gsd"),
86
+ "expected 'gsd' to be registered even when a later command registration throws",
58
87
  );
59
88
  });
60
89
 
61
- test("logs warning on bootstrap failure via workflow-logger", () => {
62
- assert.ok(
63
- indexSrc.includes("logWarning"),
64
- "must use logWarning when bootstrap fails",
65
- );
66
- assert.ok(
67
- indexSrc.includes("Extension setup partially failed"),
68
- "warning message must indicate partial failure with /gsd still available",
90
+ test("degraded path: /gsd registered BEFORE any non-core command", async () => {
91
+ // Ordering guard: the first registerCommand call must be for 'gsd',
92
+ // because index.ts awaits registerGSDCommand(pi) before importing
93
+ // register-extension. Regression scenario: if a future refactor moves
94
+ // registerGSDCommand into the try block or after other registrations,
95
+ // a failure in those earlier registrations would take /gsd down too.
96
+ const calls: string[] = [];
97
+ const pi = {
98
+ registerCommand: (name: string) => {
99
+ calls.push(name);
100
+ },
101
+ registerTool: () => {},
102
+ registerHook: () => {},
103
+ registerShortcut: () => {},
104
+ events: { on: () => {}, off: () => {}, emit: () => {} },
105
+ };
106
+ await registerExtension(pi as any);
107
+ assert.ok(calls.length > 0, "expected at least one registerCommand call");
108
+ assert.equal(
109
+ calls[0],
110
+ "gsd",
111
+ `expected 'gsd' to be the first command registered, got ${JSON.stringify(calls)}`,
69
112
  );
70
113
  });
71
114
  });
72
115
 
73
- // ─── register-extension.ts: no double-registration + defensive wrapping ───────
74
-
75
- describe("register-extension.ts defensive registration", () => {
76
- test("does NOT import or call registerGSDCommand (avoids double-registration)", () => {
77
- // registerGSDCommand is now called by index.ts, not register-extension.ts
78
- assert.ok(
79
- !registerExtSrc.includes("import { registerGSDCommand }"),
80
- "register-extension.ts must NOT import registerGSDCommand",
81
- );
82
-
83
- // Check the function body of registerGsdExtension doesn't call it
84
- const funcBodyStart = registerExtSrc.indexOf(
85
- "export function registerGsdExtension",
86
- );
87
- const funcBody = registerExtSrc.slice(funcBodyStart);
88
- assert.ok(
89
- !funcBody.includes("registerGSDCommand(pi)"),
90
- "registerGsdExtension must NOT call registerGSDCommand(pi)",
91
- );
92
- });
93
-
94
- test("still registers worktree, exit, and kill commands", () => {
95
- const funcBodyStart = registerExtSrc.indexOf(
96
- "export function registerGsdExtension",
97
- );
98
- const funcBody = registerExtSrc.slice(funcBodyStart);
99
-
100
- assert.ok(
101
- funcBody.includes("registerWorktreeCommand(pi)"),
102
- "must register worktree command",
103
- );
104
- assert.ok(
105
- funcBody.includes("registerExitCommand(pi)"),
106
- "must register exit command",
107
- );
108
- assert.ok(
109
- funcBody.includes('"kill"'),
110
- "must register kill command",
111
- );
112
- });
113
-
114
- test("wraps non-critical registrations in individual try-catch blocks", () => {
115
- const funcBodyStart = registerExtSrc.indexOf(
116
- "export function registerGsdExtension",
117
- );
118
- const funcBody = registerExtSrc.slice(funcBodyStart);
119
-
120
- // Each non-critical registration should be wrapped with error handling
121
- const registrationNames = [
122
- "dynamic-tools",
123
- "db-tools",
124
- "journal-tools",
125
- "query-tools",
126
- "shortcuts",
127
- "hooks",
128
- ];
129
-
130
- for (const name of registrationNames) {
131
- assert.ok(
132
- funcBody.includes(`"${name}"`),
133
- `non-critical registration "${name}" must be present`,
134
- );
135
- }
136
-
137
- // Must have try-catch inside the registration loop
116
+ // Behavioural contract for registerGsdExtension itself: each non-core
117
+ // registration is wrapped in its own try/catch so one failure does not
118
+ // prevent siblings from loading.
119
+
120
+ import { registerGsdExtension } from "../bootstrap/register-extension.ts";
121
+
122
+ describe("registerGsdExtension defensive registration", () => {
123
+ test("a failing shortcut registration does not prevent kill command registration", async () => {
124
+ // `shortcuts` is registered via a non-critical slot that is wrapped in
125
+ // its own try/catch. `kill` is registered before the non-critical loop
126
+ // as a critical command. Simulate: registerShortcut throws. Expect:
127
+ // 'kill' is still registered, registerGsdExtension does not throw.
128
+ const registered: string[] = [];
129
+ const pi = {
130
+ registerCommand: (name: string) => {
131
+ registered.push(name);
132
+ },
133
+ registerTool: () => {},
134
+ registerHook: () => {},
135
+ registerShortcut: () => {
136
+ throw new Error("simulated platform-specific shortcut failure");
137
+ },
138
+ events: { on: () => {}, off: () => {}, emit: () => {} },
139
+ };
140
+ assert.doesNotThrow(() => registerGsdExtension(pi as any));
138
141
  assert.ok(
139
- funcBody.includes("try {") && funcBody.includes("catch (err)"),
140
- "must have try-catch for non-critical registrations",
142
+ registered.includes("kill"),
143
+ `expected 'kill' to be registered despite shortcut failure, got ${JSON.stringify(registered)}`,
141
144
  );
142
145
  });
143
146
 
144
- test("logs warning when a non-critical registration fails", () => {
145
- assert.ok(
146
- registerExtSrc.includes("Failed to register"),
147
- "must log descriptive warning for individual registration failures",
148
- );
147
+ test("does NOT register /gsd (caller's responsibility, avoids double-registration)", () => {
148
+ const registered: string[] = [];
149
+ const pi = {
150
+ registerCommand: (name: string) => {
151
+ registered.push(name);
152
+ },
153
+ registerTool: () => {},
154
+ registerHook: () => {},
155
+ registerShortcut: () => {},
156
+ events: { on: () => {}, off: () => {}, emit: () => {} },
157
+ };
158
+ registerGsdExtension(pi as any);
149
159
  assert.ok(
150
- registerExtSrc.includes("logWarning"),
151
- "must use logWarning from workflow-logger",
160
+ !registered.includes("gsd"),
161
+ `registerGsdExtension must NOT register 'gsd' (it is registered separately by index.ts), got ${JSON.stringify(registered)}`,
152
162
  );
153
163
  });
154
164
  });