gsd-pi 2.58.0 → 2.59.0-dev.023bd39

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 (843) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +60 -35
  3. package/dist/headless-ui.d.ts +17 -0
  4. package/dist/headless-ui.js +97 -3
  5. package/dist/headless.js +67 -6
  6. package/dist/help-text.js +1 -0
  7. package/dist/onboarding.js +44 -0
  8. package/dist/resource-loader.js +16 -1
  9. package/dist/resources/agents/researcher.md +1 -1
  10. package/dist/resources/extensions/ask-user-questions.js +16 -3
  11. package/dist/resources/extensions/async-jobs/extension-manifest.json +1 -1
  12. package/dist/resources/extensions/bg-shell/extension-manifest.json +1 -1
  13. package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
  14. package/dist/resources/extensions/claude-code-cli/partial-builder.js +14 -6
  15. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +59 -36
  16. package/dist/resources/extensions/context7/extension-manifest.json +1 -1
  17. package/dist/resources/extensions/get-secrets-from-user.js +8 -5
  18. package/dist/resources/extensions/google-search/extension-manifest.json +1 -1
  19. package/dist/resources/extensions/google-search/index.js +2 -1
  20. package/dist/resources/extensions/gsd/auto/phases.js +25 -21
  21. package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
  22. package/dist/resources/extensions/gsd/auto-dashboard.js +37 -20
  23. package/dist/resources/extensions/gsd/auto-dispatch.js +17 -2
  24. package/dist/resources/extensions/gsd/auto-model-selection.js +26 -3
  25. package/dist/resources/extensions/gsd/auto-post-unit.js +16 -4
  26. package/dist/resources/extensions/gsd/auto-prompts.js +1 -1
  27. package/dist/resources/extensions/gsd/auto-recovery.js +13 -5
  28. package/dist/resources/extensions/gsd/auto-start.js +35 -22
  29. package/dist/resources/extensions/gsd/auto-worktree.js +199 -12
  30. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +32 -0
  31. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +80 -8
  32. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +32 -1
  33. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +42 -34
  34. package/dist/resources/extensions/gsd/bootstrap/system-context.js +66 -12
  35. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -0
  36. package/dist/resources/extensions/gsd/captures.js +56 -4
  37. package/dist/resources/extensions/gsd/codebase-generator.js +279 -0
  38. package/dist/resources/extensions/gsd/commands/catalog.js +10 -1
  39. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  40. package/dist/resources/extensions/gsd/commands-codebase.js +115 -0
  41. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +41 -4
  42. package/dist/resources/extensions/gsd/complexity-classifier.js +8 -6
  43. package/dist/resources/extensions/gsd/db-writer.js +116 -8
  44. package/dist/resources/extensions/gsd/doctor-git-checks.js +76 -1
  45. package/dist/resources/extensions/gsd/doctor-proactive.js +34 -1
  46. package/dist/resources/extensions/gsd/doctor-providers.js +2 -1
  47. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +5 -4
  48. package/dist/resources/extensions/gsd/doctor.js +3 -1
  49. package/dist/resources/extensions/gsd/error-classifier.js +12 -10
  50. package/dist/resources/extensions/gsd/extension-manifest.json +16 -1
  51. package/dist/resources/extensions/gsd/forensics.js +123 -20
  52. package/dist/resources/extensions/gsd/git-service.js +105 -2
  53. package/dist/resources/extensions/gsd/gitignore.js +33 -0
  54. package/dist/resources/extensions/gsd/gsd-db.js +36 -9
  55. package/dist/resources/extensions/gsd/guided-flow.js +106 -44
  56. package/dist/resources/extensions/gsd/health-widget-core.js +31 -0
  57. package/dist/resources/extensions/gsd/health-widget.js +17 -0
  58. package/dist/resources/extensions/gsd/index.js +1 -1
  59. package/dist/resources/extensions/gsd/memory-extractor.js +7 -0
  60. package/dist/resources/extensions/gsd/migrate-external.js +8 -1
  61. package/dist/resources/extensions/gsd/milestone-validation-gates.js +45 -0
  62. package/dist/resources/extensions/gsd/model-cost-table.js +18 -0
  63. package/dist/resources/extensions/gsd/model-router.js +35 -1
  64. package/dist/resources/extensions/gsd/native-git-bridge.js +39 -0
  65. package/dist/resources/extensions/gsd/notifications.js +16 -1
  66. package/dist/resources/extensions/gsd/parallel-eligibility.js +13 -2
  67. package/dist/resources/extensions/gsd/parallel-merge.js +78 -5
  68. package/dist/resources/extensions/gsd/parsers-legacy.js +20 -3
  69. package/dist/resources/extensions/gsd/paths.js +45 -0
  70. package/dist/resources/extensions/gsd/preferences-models.js +14 -1
  71. package/dist/resources/extensions/gsd/preferences-types.js +3 -1
  72. package/dist/resources/extensions/gsd/preferences.js +13 -16
  73. package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
  74. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  75. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -2
  76. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +1 -1
  77. package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
  78. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -1
  79. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -2
  80. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  81. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  82. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -0
  83. package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
  84. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -0
  85. package/dist/resources/extensions/gsd/repo-identity.js +205 -11
  86. package/dist/resources/extensions/gsd/rethink.js +5 -0
  87. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -4
  88. package/dist/resources/extensions/gsd/state.js +85 -27
  89. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +20 -1
  90. package/dist/resources/extensions/gsd/tools/complete-task.js +34 -71
  91. package/dist/resources/extensions/gsd/tools/plan-milestone.js +12 -2
  92. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +29 -1
  93. package/dist/resources/extensions/gsd/tools/validate-milestone.js +14 -3
  94. package/dist/resources/extensions/gsd/triage-resolution.js +22 -7
  95. package/dist/resources/extensions/gsd/undo.js +2 -2
  96. package/dist/resources/extensions/gsd/unit-ownership.js +164 -33
  97. package/dist/resources/extensions/gsd/verdict-parser.js +20 -8
  98. package/dist/resources/extensions/gsd/watch/header-renderer.js +241 -0
  99. package/dist/resources/extensions/gsd/workflow-manifest.js +24 -5
  100. package/dist/resources/extensions/gsd/workflow-projections.js +95 -63
  101. package/dist/resources/extensions/gsd/workflow-reconcile.js +35 -5
  102. package/dist/resources/extensions/gsd/workspace-index.js +24 -0
  103. package/dist/resources/extensions/gsd/worktree-manager.js +105 -1
  104. package/dist/resources/extensions/gsd/worktree-resolver.js +20 -3
  105. package/dist/resources/extensions/mcp-client/index.js +11 -7
  106. package/dist/resources/extensions/ollama/index.js +112 -0
  107. package/dist/resources/extensions/ollama/model-capabilities.js +115 -0
  108. package/dist/resources/extensions/ollama/ollama-client.js +168 -0
  109. package/dist/resources/extensions/ollama/ollama-commands.js +194 -0
  110. package/dist/resources/extensions/ollama/ollama-discovery.js +69 -0
  111. package/dist/resources/extensions/ollama/ollama-tool.js +184 -0
  112. package/dist/resources/extensions/ollama/types.js +2 -0
  113. package/dist/resources/extensions/search-the-web/extension-manifest.json +1 -1
  114. package/dist/resources/extensions/search-the-web/url-utils.js +17 -0
  115. package/dist/resources/extensions/shared/interview-ui.js +11 -1
  116. package/dist/resources/skills/btw/SKILL.md +42 -0
  117. package/dist/resources/skills/create-gsd-extension/SKILL.md +5 -3
  118. package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +5 -4
  119. package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +2 -2
  120. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +4 -4
  121. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +5 -3
  122. package/dist/security-overrides.d.ts +11 -0
  123. package/dist/security-overrides.js +41 -0
  124. package/dist/startup-model-validation.d.ts +39 -0
  125. package/dist/startup-model-validation.js +50 -0
  126. package/dist/web/standalone/.next/BUILD_ID +1 -1
  127. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  128. package/dist/web/standalone/.next/build-manifest.json +4 -4
  129. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  130. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  131. package/dist/web/standalone/.next/required-server-files.json +4 -4
  132. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  133. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  135. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  143. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  145. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
  146. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  147. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  148. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  149. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  152. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  159. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  171. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  199. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  202. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  203. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  204. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  205. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  206. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  207. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  209. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  210. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  211. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  212. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  213. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  214. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  215. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  216. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  217. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  218. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  219. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  220. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  221. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  222. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  223. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  224. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  225. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  226. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  227. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  228. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  229. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  230. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  231. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  232. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  233. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  234. package/dist/web/standalone/.next/server/app/index.html +1 -1
  235. package/dist/web/standalone/.next/server/app/index.rsc +5 -5
  236. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  237. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
  238. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  239. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
  240. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  241. package/dist/web/standalone/.next/server/app/page.js +2 -2
  242. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  243. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  244. package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
  245. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  246. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  247. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  248. package/dist/web/standalone/.next/server/middleware.js +2 -2
  249. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  250. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  251. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  252. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  253. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  254. package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +9 -0
  255. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  256. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  257. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  258. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  259. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  260. package/dist/web/standalone/.next/static/chunks/{webpack-61d3afac6d0f0ce7.js → webpack-a1c1e452c6b32d04.js} +1 -1
  261. package/dist/web/standalone/.next/static/css/f6e8833d46e738d8.css +1 -0
  262. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  263. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  264. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  265. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  266. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  267. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  268. package/dist/web/standalone/server.js +1 -1
  269. package/dist/web-mode.js +2 -1
  270. package/dist/welcome-screen.d.ts +1 -0
  271. package/dist/welcome-screen.js +32 -6
  272. package/package.json +2 -2
  273. package/packages/daemon/src/daemon.ts +1 -1
  274. package/packages/daemon/src/discord-bot.ts +11 -0
  275. package/packages/daemon/src/event-bridge.ts +15 -9
  276. package/packages/daemon/src/event-formatter.ts +30 -2
  277. package/packages/daemon/src/message-batcher.test.ts +2 -2
  278. package/packages/daemon/src/message-batcher.ts +9 -3
  279. package/packages/daemon/src/orchestrator.test.ts +1 -0
  280. package/packages/daemon/src/orchestrator.ts +106 -2
  281. package/packages/native/dist/ast/index.js +9 -5
  282. package/packages/native/dist/ast/types.js +2 -1
  283. package/packages/native/dist/clipboard/index.js +12 -7
  284. package/packages/native/dist/clipboard/types.js +2 -1
  285. package/packages/native/dist/diff/index.js +12 -7
  286. package/packages/native/dist/diff/types.js +2 -1
  287. package/packages/native/dist/fd/index.js +6 -3
  288. package/packages/native/dist/fd/types.js +2 -1
  289. package/packages/native/dist/glob/index.js +9 -5
  290. package/packages/native/dist/glob/types.js +2 -1
  291. package/packages/native/dist/grep/index.js +9 -5
  292. package/packages/native/dist/grep/types.js +2 -1
  293. package/packages/native/dist/gsd-parser/index.js +18 -11
  294. package/packages/native/dist/gsd-parser/types.js +2 -1
  295. package/packages/native/dist/highlight/index.js +12 -7
  296. package/packages/native/dist/highlight/types.js +2 -1
  297. package/packages/native/dist/html/index.js +6 -3
  298. package/packages/native/dist/html/types.js +2 -1
  299. package/packages/native/dist/image/index.js +10 -5
  300. package/packages/native/dist/image/types.js +7 -4
  301. package/packages/native/dist/index.js +70 -17
  302. package/packages/native/dist/json-parse/index.js +13 -8
  303. package/packages/native/dist/native.js +47 -10
  304. package/packages/native/dist/ps/index.js +15 -9
  305. package/packages/native/dist/ps/types.js +2 -1
  306. package/packages/native/dist/stream-process/index.js +12 -7
  307. package/packages/native/dist/text/index.js +24 -14
  308. package/packages/native/dist/text/types.js +5 -2
  309. package/packages/native/dist/truncate/index.js +12 -7
  310. package/packages/native/dist/ttsr/index.js +12 -7
  311. package/packages/native/dist/ttsr/types.js +2 -1
  312. package/packages/native/dist/xxhash/index.js +9 -5
  313. package/packages/native/package.json +19 -19
  314. package/packages/native/src/__tests__/module-compat.test.mjs +91 -0
  315. package/packages/native/src/native.ts +9 -8
  316. package/packages/pi-agent-core/dist/agent-loop.js +3 -2
  317. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  318. package/packages/pi-agent-core/dist/proxy.d.ts +1 -1
  319. package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
  320. package/packages/pi-agent-core/dist/proxy.js.map +1 -1
  321. package/packages/pi-agent-core/src/agent-loop.test.ts +45 -0
  322. package/packages/pi-agent-core/src/agent-loop.ts +3 -2
  323. package/packages/pi-agent-core/src/proxy.ts +1 -1
  324. package/packages/pi-ai/dist/env-api-keys.js +1 -0
  325. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  326. package/packages/pi-ai/dist/index.d.ts +1 -0
  327. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  328. package/packages/pi-ai/dist/index.js +1 -0
  329. package/packages/pi-ai/dist/index.js.map +1 -1
  330. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  331. package/packages/pi-ai/dist/providers/anthropic-shared.js +19 -2
  332. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  333. package/packages/pi-ai/dist/providers/anthropic-shared.test.d.ts +2 -0
  334. package/packages/pi-ai/dist/providers/anthropic-shared.test.d.ts.map +1 -0
  335. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +25 -0
  336. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -0
  337. package/packages/pi-ai/dist/types.d.ts +3 -3
  338. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  339. package/packages/pi-ai/dist/types.js.map +1 -1
  340. package/packages/pi-ai/dist/utils/json-parse.d.ts +3 -0
  341. package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
  342. package/packages/pi-ai/dist/utils/json-parse.js +24 -1
  343. package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
  344. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts +37 -0
  345. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -0
  346. package/packages/pi-ai/dist/utils/repair-tool-json.js +75 -0
  347. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -0
  348. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.d.ts +2 -0
  349. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.d.ts.map +1 -0
  350. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +73 -0
  351. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -0
  352. package/packages/pi-ai/src/env-api-keys.ts +1 -0
  353. package/packages/pi-ai/src/index.ts +1 -0
  354. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +29 -0
  355. package/packages/pi-ai/src/providers/anthropic-shared.ts +17 -2
  356. package/packages/pi-ai/src/types.ts +3 -2
  357. package/packages/pi-ai/src/utils/json-parse.ts +28 -1
  358. package/packages/pi-ai/src/utils/repair-tool-json.ts +88 -0
  359. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +102 -0
  360. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +4 -0
  361. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  362. package/packages/pi-coding-agent/dist/core/agent-session.js +31 -0
  363. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  364. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +17 -1
  365. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  366. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +62 -2
  367. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  368. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.d.ts +6 -0
  369. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.d.ts.map +1 -0
  370. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js +176 -0
  371. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js.map +1 -0
  372. package/packages/pi-coding-agent/dist/core/exec.d.ts.map +1 -1
  373. package/packages/pi-coding-agent/dist/core/exec.js +3 -1
  374. package/packages/pi-coding-agent/dist/core/exec.js.map +1 -1
  375. package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.d.ts +28 -0
  376. package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.d.ts.map +1 -0
  377. package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.js +37 -0
  378. package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.js.map +1 -0
  379. package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.test.d.ts +2 -0
  380. package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.test.d.ts.map +1 -0
  381. package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.test.js +63 -0
  382. package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.test.js.map +1 -0
  383. package/packages/pi-coding-agent/dist/core/extensions/extension-sort.d.ts +19 -0
  384. package/packages/pi-coding-agent/dist/core/extensions/extension-sort.d.ts.map +1 -0
  385. package/packages/pi-coding-agent/dist/core/extensions/extension-sort.js +115 -0
  386. package/packages/pi-coding-agent/dist/core/extensions/extension-sort.js.map +1 -0
  387. package/packages/pi-coding-agent/dist/core/extensions/extension-sort.test.d.ts +2 -0
  388. package/packages/pi-coding-agent/dist/core/extensions/extension-sort.test.d.ts.map +1 -0
  389. package/packages/pi-coding-agent/dist/core/extensions/extension-sort.test.js +109 -0
  390. package/packages/pi-coding-agent/dist/core/extensions/extension-sort.test.js.map +1 -0
  391. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +4 -0
  392. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  393. package/packages/pi-coding-agent/dist/core/extensions/index.js +2 -0
  394. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  395. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  396. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  397. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  398. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  399. package/packages/pi-coding-agent/dist/core/image-overflow-recovery.d.ts +44 -0
  400. package/packages/pi-coding-agent/dist/core/image-overflow-recovery.d.ts.map +1 -0
  401. package/packages/pi-coding-agent/dist/core/image-overflow-recovery.js +97 -0
  402. package/packages/pi-coding-agent/dist/core/image-overflow-recovery.js.map +1 -0
  403. package/packages/pi-coding-agent/dist/core/image-overflow-recovery.test.d.ts +2 -0
  404. package/packages/pi-coding-agent/dist/core/image-overflow-recovery.test.d.ts.map +1 -0
  405. package/packages/pi-coding-agent/dist/core/image-overflow-recovery.test.js +181 -0
  406. package/packages/pi-coding-agent/dist/core/image-overflow-recovery.test.js.map +1 -0
  407. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -1
  408. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  409. package/packages/pi-coding-agent/dist/core/index.js +1 -1
  410. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  411. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  412. package/packages/pi-coding-agent/dist/core/lsp/index.js +3 -0
  413. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  414. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  415. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +3 -0
  416. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  417. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  418. package/packages/pi-coding-agent/dist/core/messages.js +31 -2
  419. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  420. package/packages/pi-coding-agent/dist/core/messages.test.d.ts +9 -0
  421. package/packages/pi-coding-agent/dist/core/messages.test.d.ts.map +1 -0
  422. package/packages/pi-coding-agent/dist/core/messages.test.js +86 -0
  423. package/packages/pi-coding-agent/dist/core/messages.test.js.map +1 -0
  424. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  425. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  426. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  427. package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts +8 -0
  428. package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts.map +1 -1
  429. package/packages/pi-coding-agent/dist/core/resolve-config-value.js +23 -2
  430. package/packages/pi-coding-agent/dist/core/resolve-config-value.js.map +1 -1
  431. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +89 -2
  432. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  433. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +10 -0
  434. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  435. package/packages/pi-coding-agent/dist/core/resource-loader.js +12 -1
  436. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  437. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +6 -0
  438. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  439. package/packages/pi-coding-agent/dist/core/retry-handler.js +48 -1
  440. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  441. package/packages/pi-coding-agent/dist/core/retry-handler.test.d.ts +9 -0
  442. package/packages/pi-coding-agent/dist/core/retry-handler.test.d.ts.map +1 -0
  443. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +193 -0
  444. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -0
  445. package/packages/pi-coding-agent/dist/core/settings-manager-security.test.d.ts +2 -0
  446. package/packages/pi-coding-agent/dist/core/settings-manager-security.test.d.ts.map +1 -0
  447. package/packages/pi-coding-agent/dist/core/settings-manager-security.test.js +83 -0
  448. package/packages/pi-coding-agent/dist/core/settings-manager-security.test.js.map +1 -0
  449. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +14 -0
  450. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  451. package/packages/pi-coding-agent/dist/core/settings-manager.js +36 -3
  452. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  453. package/packages/pi-coding-agent/dist/core/tools/hashline-read.d.ts.map +1 -1
  454. package/packages/pi-coding-agent/dist/core/tools/hashline-read.js +10 -3
  455. package/packages/pi-coding-agent/dist/core/tools/hashline-read.js.map +1 -1
  456. package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  457. package/packages/pi-coding-agent/dist/core/tools/read.js +13 -4
  458. package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  459. package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.d.ts +16 -0
  460. package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.d.ts.map +1 -0
  461. package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js +80 -0
  462. package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js.map +1 -0
  463. package/packages/pi-coding-agent/dist/index.d.ts +3 -2
  464. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  465. package/packages/pi-coding-agent/dist/index.js +2 -1
  466. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  467. package/packages/pi-coding-agent/dist/modes/interactive/components/armin.d.ts +1 -1
  468. package/packages/pi-coding-agent/dist/modes/interactive/components/armin.d.ts.map +1 -1
  469. package/packages/pi-coding-agent/dist/modes/interactive/components/armin.js +9 -8
  470. package/packages/pi-coding-agent/dist/modes/interactive/components/armin.js.map +1 -1
  471. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  472. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +0 -3
  473. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  474. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -0
  475. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  476. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +2 -1
  477. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  478. package/packages/pi-coding-agent/dist/modes/interactive/components/bordered-loader.js +1 -1
  479. package/packages/pi-coding-agent/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  480. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -1
  481. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  482. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  483. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  484. package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  485. package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.js +5 -2
  486. package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.js.map +1 -1
  487. package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.d.ts +1 -0
  488. package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  489. package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.js +4 -0
  490. package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  491. package/packages/pi-coding-agent/dist/modes/interactive/components/custom-message.js +1 -1
  492. package/packages/pi-coding-agent/dist/modes/interactive/components/custom-message.js.map +1 -1
  493. package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.d.ts +1 -1
  494. package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
  495. package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.js +4 -2
  496. package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.js.map +1 -1
  497. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +2 -2
  498. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  499. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  500. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +8 -1
  501. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  502. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  503. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js +2 -0
  504. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js.map +1 -1
  505. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  506. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js +4 -0
  507. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js.map +1 -1
  508. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  509. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +26 -12
  510. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  511. package/packages/pi-coding-agent/dist/modes/interactive/components/oauth-selector.js +4 -4
  512. package/packages/pi-coding-agent/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  513. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +3 -0
  514. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  515. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +46 -14
  516. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  517. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  518. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -8
  519. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  520. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +4 -4
  521. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
  522. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -2
  523. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  524. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  525. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +8 -3
  526. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  527. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  528. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.js +3 -2
  529. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  530. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  531. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -1
  532. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  533. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +1 -0
  534. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  535. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +22 -1
  536. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  537. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts +2 -0
  538. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts.map +1 -0
  539. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +122 -0
  540. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -0
  541. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
  542. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  543. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +57 -4
  544. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  545. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +1 -1
  546. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  547. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +1 -1
  548. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  549. package/packages/pi-coding-agent/dist/modes/rpc/remote-terminal.d.ts +1 -0
  550. package/packages/pi-coding-agent/dist/modes/rpc/remote-terminal.d.ts.map +1 -1
  551. package/packages/pi-coding-agent/dist/modes/rpc/remote-terminal.js +5 -0
  552. package/packages/pi-coding-agent/dist/modes/rpc/remote-terminal.js.map +1 -1
  553. package/packages/pi-coding-agent/package.json +1 -1
  554. package/packages/pi-coding-agent/src/core/agent-session.ts +38 -1
  555. package/packages/pi-coding-agent/src/core/compaction/compaction.test.ts +236 -0
  556. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +94 -1
  557. package/packages/pi-coding-agent/src/core/exec.ts +3 -1
  558. package/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts +77 -0
  559. package/packages/pi-coding-agent/src/core/extensions/extension-manifest.ts +62 -0
  560. package/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts +134 -0
  561. package/packages/pi-coding-agent/src/core/extensions/extension-sort.ts +137 -0
  562. package/packages/pi-coding-agent/src/core/extensions/index.ts +4 -0
  563. package/packages/pi-coding-agent/src/core/extensions/loader.ts +5 -0
  564. package/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts +228 -0
  565. package/packages/pi-coding-agent/src/core/image-overflow-recovery.ts +118 -0
  566. package/packages/pi-coding-agent/src/core/index.ts +6 -0
  567. package/packages/pi-coding-agent/src/core/lsp/index.ts +3 -0
  568. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +3 -0
  569. package/packages/pi-coding-agent/src/core/messages.test.ts +114 -0
  570. package/packages/pi-coding-agent/src/core/messages.ts +29 -2
  571. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  572. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +111 -1
  573. package/packages/pi-coding-agent/src/core/resolve-config-value.ts +26 -2
  574. package/packages/pi-coding-agent/src/core/resource-loader.ts +20 -1
  575. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +255 -0
  576. package/packages/pi-coding-agent/src/core/retry-handler.ts +52 -1
  577. package/packages/pi-coding-agent/src/core/settings-manager-security.test.ts +102 -0
  578. package/packages/pi-coding-agent/src/core/settings-manager.ts +44 -3
  579. package/packages/pi-coding-agent/src/core/tools/hashline-read.ts +11 -3
  580. package/packages/pi-coding-agent/src/core/tools/read.ts +14 -4
  581. package/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts +92 -0
  582. package/packages/pi-coding-agent/src/index.ts +11 -0
  583. package/packages/pi-coding-agent/src/modes/interactive/components/armin.ts +9 -9
  584. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +0 -2
  585. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +3 -1
  586. package/packages/pi-coding-agent/src/modes/interactive/components/bordered-loader.ts +1 -1
  587. package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -1
  588. package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -1
  589. package/packages/pi-coding-agent/src/modes/interactive/components/config-selector.ts +7 -2
  590. package/packages/pi-coding-agent/src/modes/interactive/components/countdown-timer.ts +3 -0
  591. package/packages/pi-coding-agent/src/modes/interactive/components/custom-message.ts +1 -1
  592. package/packages/pi-coding-agent/src/modes/interactive/components/daxnuts.ts +4 -3
  593. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +2 -2
  594. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +3 -1
  595. package/packages/pi-coding-agent/src/modes/interactive/components/extension-input.ts +1 -0
  596. package/packages/pi-coding-agent/src/modes/interactive/components/extension-selector.ts +4 -0
  597. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +27 -13
  598. package/packages/pi-coding-agent/src/modes/interactive/components/oauth-selector.ts +4 -4
  599. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +45 -14
  600. package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -7
  601. package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +4 -4
  602. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -2
  603. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +8 -3
  604. package/packages/pi-coding-agent/src/modes/interactive/components/user-message-selector.ts +3 -2
  605. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +24 -1
  606. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +156 -0
  607. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +21 -1
  608. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +73 -3
  609. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +1 -1
  610. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +1 -1
  611. package/packages/pi-coding-agent/src/modes/rpc/remote-terminal.ts +6 -0
  612. package/packages/pi-tui/dist/terminal.d.ts +2 -0
  613. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  614. package/packages/pi-tui/dist/terminal.js +9 -0
  615. package/packages/pi-tui/dist/terminal.js.map +1 -1
  616. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  617. package/packages/pi-tui/dist/tui.js +9 -0
  618. package/packages/pi-tui/dist/tui.js.map +1 -1
  619. package/packages/pi-tui/src/terminal.ts +14 -0
  620. package/packages/pi-tui/src/tui.ts +8 -0
  621. package/pkg/dist/modes/interactive/theme/themes.js +1 -1
  622. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  623. package/pkg/package.json +1 -1
  624. package/scripts/ensure-workspace-builds.cjs +45 -14
  625. package/src/resources/agents/researcher.md +1 -1
  626. package/src/resources/extensions/ask-user-questions.ts +21 -3
  627. package/src/resources/extensions/async-jobs/extension-manifest.json +1 -1
  628. package/src/resources/extensions/bg-shell/extension-manifest.json +1 -1
  629. package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
  630. package/src/resources/extensions/claude-code-cli/partial-builder.ts +13 -6
  631. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +63 -35
  632. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +28 -0
  633. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +108 -1
  634. package/src/resources/extensions/context7/extension-manifest.json +1 -1
  635. package/src/resources/extensions/get-secrets-from-user.ts +8 -5
  636. package/src/resources/extensions/google-search/extension-manifest.json +1 -1
  637. package/src/resources/extensions/google-search/index.ts +2 -1
  638. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  639. package/src/resources/extensions/gsd/auto/phases.ts +43 -34
  640. package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
  641. package/src/resources/extensions/gsd/auto-dashboard.ts +37 -19
  642. package/src/resources/extensions/gsd/auto-dispatch.ts +18 -2
  643. package/src/resources/extensions/gsd/auto-model-selection.ts +26 -5
  644. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
  645. package/src/resources/extensions/gsd/auto-prompts.ts +1 -1
  646. package/src/resources/extensions/gsd/auto-recovery.ts +12 -5
  647. package/src/resources/extensions/gsd/auto-start.ts +35 -26
  648. package/src/resources/extensions/gsd/auto-worktree.ts +193 -9
  649. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +31 -0
  650. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +85 -8
  651. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +38 -1
  652. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +41 -35
  653. package/src/resources/extensions/gsd/bootstrap/system-context.ts +72 -12
  654. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +75 -0
  655. package/src/resources/extensions/gsd/captures.ts +63 -3
  656. package/src/resources/extensions/gsd/codebase-generator.ts +351 -0
  657. package/src/resources/extensions/gsd/commands/catalog.ts +10 -1
  658. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  659. package/src/resources/extensions/gsd/commands-codebase.ts +164 -0
  660. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +46 -4
  661. package/src/resources/extensions/gsd/complexity-classifier.ts +8 -6
  662. package/src/resources/extensions/gsd/db-writer.ts +140 -7
  663. package/src/resources/extensions/gsd/doctor-git-checks.ts +75 -1
  664. package/src/resources/extensions/gsd/doctor-proactive.ts +35 -1
  665. package/src/resources/extensions/gsd/doctor-providers.ts +2 -1
  666. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +5 -4
  667. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  668. package/src/resources/extensions/gsd/doctor.ts +3 -1
  669. package/src/resources/extensions/gsd/error-classifier.ts +13 -11
  670. package/src/resources/extensions/gsd/extension-manifest.json +16 -1
  671. package/src/resources/extensions/gsd/forensics.ts +144 -20
  672. package/src/resources/extensions/gsd/git-service.ts +119 -3
  673. package/src/resources/extensions/gsd/gitignore.ts +33 -0
  674. package/src/resources/extensions/gsd/gsd-db.ts +43 -7
  675. package/src/resources/extensions/gsd/guided-flow.ts +114 -45
  676. package/src/resources/extensions/gsd/health-widget-core.ts +34 -0
  677. package/src/resources/extensions/gsd/health-widget.ts +17 -0
  678. package/src/resources/extensions/gsd/index.ts +1 -0
  679. package/src/resources/extensions/gsd/memory-extractor.ts +8 -0
  680. package/src/resources/extensions/gsd/migrate-external.ts +9 -1
  681. package/src/resources/extensions/gsd/milestone-validation-gates.ts +56 -0
  682. package/src/resources/extensions/gsd/model-cost-table.ts +19 -0
  683. package/src/resources/extensions/gsd/model-router.ts +35 -1
  684. package/src/resources/extensions/gsd/native-git-bridge.ts +41 -0
  685. package/src/resources/extensions/gsd/notifications.ts +16 -0
  686. package/src/resources/extensions/gsd/parallel-eligibility.ts +15 -2
  687. package/src/resources/extensions/gsd/parallel-merge.ts +87 -4
  688. package/src/resources/extensions/gsd/parsers-legacy.ts +22 -3
  689. package/src/resources/extensions/gsd/paths.ts +44 -0
  690. package/src/resources/extensions/gsd/preferences-models.ts +14 -1
  691. package/src/resources/extensions/gsd/preferences-types.ts +10 -1
  692. package/src/resources/extensions/gsd/preferences.ts +13 -15
  693. package/src/resources/extensions/gsd/prompt-loader.ts +4 -1
  694. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  695. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -2
  696. package/src/resources/extensions/gsd/prompts/discuss-headless.md +1 -1
  697. package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
  698. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -1
  699. package/src/resources/extensions/gsd/prompts/forensics.md +2 -2
  700. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  701. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  702. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -0
  703. package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
  704. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -0
  705. package/src/resources/extensions/gsd/repo-identity.ts +186 -11
  706. package/src/resources/extensions/gsd/rethink.ts +6 -0
  707. package/src/resources/extensions/gsd/roadmap-slices.ts +5 -4
  708. package/src/resources/extensions/gsd/state.ts +84 -32
  709. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +29 -0
  710. package/src/resources/extensions/gsd/tests/auto-mode-interactive-guard.test.ts +71 -0
  711. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +71 -1
  712. package/src/resources/extensions/gsd/tests/captures.test.ts +103 -0
  713. package/src/resources/extensions/gsd/tests/cli-provider-rate-limit.test.ts +47 -0
  714. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +488 -0
  715. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +27 -0
  716. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +21 -0
  717. package/src/resources/extensions/gsd/tests/completion-hierarchy-guards.test.ts +192 -0
  718. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +4 -4
  719. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +131 -0
  720. package/src/resources/extensions/gsd/tests/db-writer.test.ts +7 -12
  721. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +78 -5
  722. package/src/resources/extensions/gsd/tests/derive-state.test.ts +29 -0
  723. package/src/resources/extensions/gsd/tests/discord-invite-links.test.ts +47 -0
  724. package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +127 -0
  725. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +40 -0
  726. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +20 -1
  727. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +117 -0
  728. package/src/resources/extensions/gsd/tests/dynamic-routing-default.test.ts +20 -0
  729. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +74 -0
  730. package/src/resources/extensions/gsd/tests/event-replay-idempotency.test.ts +140 -0
  731. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +129 -0
  732. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +96 -0
  733. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +31 -0
  734. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +125 -12
  735. package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +164 -0
  736. package/src/resources/extensions/gsd/tests/guided-flow-dynamic-routing.test.ts +135 -0
  737. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +97 -0
  738. package/src/resources/extensions/gsd/tests/health-widget.test.ts +67 -0
  739. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +107 -0
  740. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +111 -1
  741. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +134 -0
  742. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +59 -0
  743. package/src/resources/extensions/gsd/tests/integration/doctor-false-positives.test.ts +243 -0
  744. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +72 -0
  745. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +68 -0
  746. package/src/resources/extensions/gsd/tests/integration/gitignore-staging-2570.test.ts +150 -0
  747. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +110 -0
  748. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +1 -1
  749. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +959 -0
  750. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +85 -2
  751. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +105 -0
  752. package/src/resources/extensions/gsd/tests/milestone-status-authoritative.test.ts +116 -0
  753. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +34 -0
  754. package/src/resources/extensions/gsd/tests/model-router.test.ts +68 -3
  755. package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +28 -0
  756. package/src/resources/extensions/gsd/tests/notifications.test.ts +45 -0
  757. package/src/resources/extensions/gsd/tests/parallel-commit-scope.test.ts +159 -0
  758. package/src/resources/extensions/gsd/tests/parallel-eligibility-ghost.test.ts +150 -0
  759. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +70 -0
  760. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +33 -1
  761. package/src/resources/extensions/gsd/tests/project-relocation-recovery.test.ts +297 -0
  762. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +29 -0
  763. package/src/resources/extensions/gsd/tests/prompt-loader-replacement.test.ts +178 -0
  764. package/src/resources/extensions/gsd/tests/prompt-tool-names.test.ts +69 -0
  765. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +38 -0
  766. package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +157 -0
  767. package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +90 -0
  768. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +117 -0
  769. package/src/resources/extensions/gsd/tests/reconciliation-edge-cases.test.ts +162 -0
  770. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +97 -0
  771. package/src/resources/extensions/gsd/tests/secure-env-collect.test.ts +134 -0
  772. package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +233 -0
  773. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +305 -0
  774. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +405 -0
  775. package/src/resources/extensions/gsd/tests/state-derivation-parity.test.ts +257 -0
  776. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +1628 -0
  777. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +106 -0
  778. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +174 -0
  779. package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +221 -0
  780. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +44 -0
  781. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +2 -1
  782. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +8 -0
  783. package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +289 -0
  784. package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +100 -17
  785. package/src/resources/extensions/gsd/tests/vacuum-recovery.test.ts +154 -0
  786. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +4 -1
  787. package/src/resources/extensions/gsd/tests/verdict-parser.test.ts +156 -0
  788. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +82 -0
  789. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +48 -0
  790. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +92 -0
  791. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -2
  792. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +140 -0
  793. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +101 -0
  794. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +48 -1
  795. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +29 -5
  796. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +95 -0
  797. package/src/resources/extensions/gsd/tools/complete-task.ts +36 -74
  798. package/src/resources/extensions/gsd/tools/plan-milestone.ts +13 -1
  799. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +36 -0
  800. package/src/resources/extensions/gsd/tools/validate-milestone.ts +20 -2
  801. package/src/resources/extensions/gsd/triage-resolution.ts +23 -6
  802. package/src/resources/extensions/gsd/types.ts +4 -2
  803. package/src/resources/extensions/gsd/undo.ts +2 -2
  804. package/src/resources/extensions/gsd/unit-ownership.ts +206 -35
  805. package/src/resources/extensions/gsd/verdict-parser.ts +21 -6
  806. package/src/resources/extensions/gsd/watch/header-renderer.ts +275 -0
  807. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  808. package/src/resources/extensions/gsd/workflow-manifest.ts +22 -5
  809. package/src/resources/extensions/gsd/workflow-projections.ts +97 -64
  810. package/src/resources/extensions/gsd/workflow-reconcile.ts +39 -10
  811. package/src/resources/extensions/gsd/workspace-index.ts +30 -0
  812. package/src/resources/extensions/gsd/worktree-manager.ts +120 -1
  813. package/src/resources/extensions/gsd/worktree-resolver.ts +22 -3
  814. package/src/resources/extensions/mcp-client/index.ts +13 -7
  815. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +55 -0
  816. package/src/resources/extensions/ollama/index.ts +130 -0
  817. package/src/resources/extensions/ollama/model-capabilities.ts +145 -0
  818. package/src/resources/extensions/ollama/ollama-client.ts +196 -0
  819. package/src/resources/extensions/ollama/ollama-commands.ts +248 -0
  820. package/src/resources/extensions/ollama/ollama-discovery.ts +106 -0
  821. package/src/resources/extensions/ollama/ollama-tool.ts +218 -0
  822. package/src/resources/extensions/ollama/tests/model-capabilities.test.ts +162 -0
  823. package/src/resources/extensions/ollama/tests/ollama-client.test.ts +38 -0
  824. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +28 -0
  825. package/src/resources/extensions/ollama/types.ts +130 -0
  826. package/src/resources/extensions/search-the-web/extension-manifest.json +1 -1
  827. package/src/resources/extensions/search-the-web/url-utils.ts +19 -0
  828. package/src/resources/extensions/shared/interview-ui.ts +12 -1
  829. package/src/resources/extensions/shared/tests/ask-user-freetext.test.ts +156 -0
  830. package/src/resources/skills/btw/SKILL.md +42 -0
  831. package/src/resources/skills/create-gsd-extension/SKILL.md +5 -3
  832. package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +5 -4
  833. package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +2 -2
  834. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +4 -4
  835. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +5 -3
  836. package/dist/web/standalone/.next/static/chunks/6502.8b732f67a11b11b4.js +0 -9
  837. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  838. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  839. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  840. package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +0 -1
  841. package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +0 -79
  842. /package/dist/web/standalone/.next/static/{IoheXIe-5DH7ieX8AUo8U → QlWL-8CXgQpzV3ehkNMzh}/_buildManifest.js +0 -0
  843. /package/dist/web/standalone/.next/static/{IoheXIe-5DH7ieX8AUo8U → QlWL-8CXgQpzV3ehkNMzh}/_ssgManifest.js +0 -0
@@ -0,0 +1,1628 @@
1
+ // GSD State Machine — Comprehensive Phase-by-Phase Walkthrough Tests
2
+ // Verifies all 16 phases, reconciliation, edge cases, and cross-validation.
3
+
4
+ import { describe, test, afterEach } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+
10
+ import {
11
+ deriveState,
12
+ deriveStateFromDb,
13
+ isValidationTerminal,
14
+ isGhostMilestone,
15
+ invalidateStateCache,
16
+ } from "../state.ts";
17
+ import {
18
+ openDatabase,
19
+ closeDatabase,
20
+ insertMilestone,
21
+ insertSlice,
22
+ insertTask,
23
+ updateTaskStatus,
24
+ getAllMilestones,
25
+ insertGateRow,
26
+ getPendingSliceGateCount,
27
+ } from "../gsd-db.ts";
28
+ import { isClosedStatus } from "../status-guards.ts";
29
+ import { clearPathCache } from "../paths.ts";
30
+
31
+ // ─── Fixture Helpers ─────────────────────────────────────────────────────────
32
+
33
+ const tempDirs: string[] = [];
34
+
35
+ function createFixtureBase(): string {
36
+ const base = mkdtempSync(join(tmpdir(), "gsd-walkthrough-"));
37
+ mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
38
+ tempDirs.push(base);
39
+ return base;
40
+ }
41
+
42
+ afterEach(() => {
43
+ for (const dir of tempDirs.splice(0)) {
44
+ try {
45
+ rmSync(dir, { recursive: true, force: true });
46
+ } catch { /* best effort */ }
47
+ }
48
+ try { closeDatabase(); } catch { /* may not be open */ }
49
+ });
50
+
51
+ function writeContext(base: string, mid: string, content: string): void {
52
+ const dir = join(base, ".gsd", "milestones", mid);
53
+ mkdirSync(dir, { recursive: true });
54
+ writeFileSync(join(dir, `${mid}-CONTEXT.md`), content);
55
+ }
56
+
57
+ function writeContextDraft(base: string, mid: string, content: string): void {
58
+ const dir = join(base, ".gsd", "milestones", mid);
59
+ mkdirSync(dir, { recursive: true });
60
+ writeFileSync(join(dir, `${mid}-CONTEXT-DRAFT.md`), content);
61
+ }
62
+
63
+ function writeRoadmap(base: string, mid: string, content: string): void {
64
+ const dir = join(base, ".gsd", "milestones", mid);
65
+ mkdirSync(dir, { recursive: true });
66
+ writeFileSync(join(dir, `${mid}-ROADMAP.md`), content);
67
+ }
68
+
69
+ function writePlan(base: string, mid: string, sid: string, content: string): void {
70
+ const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
71
+ const tasksDir = join(dir, "tasks");
72
+ mkdirSync(tasksDir, { recursive: true });
73
+ writeFileSync(join(dir, `${sid}-PLAN.md`), content);
74
+ // Create stub task plan files so deriveState doesn't fall back to planning
75
+ const taskMatches = content.matchAll(/\*\*(T\d+):/g);
76
+ for (const m of taskMatches) {
77
+ const tid = m[1];
78
+ writeFileSync(join(tasksDir, `${tid}-PLAN.md`), `# ${tid} Plan\n\nStub.\n`);
79
+ }
80
+ }
81
+
82
+ function writeTaskSummary(base: string, mid: string, sid: string, tid: string): void {
83
+ const tasksDir = join(base, ".gsd", "milestones", mid, "slices", sid, "tasks");
84
+ mkdirSync(tasksDir, { recursive: true });
85
+ writeFileSync(join(tasksDir, `${tid}-SUMMARY.md`), [
86
+ `# ${tid} Summary`,
87
+ "",
88
+ "Task completed successfully.",
89
+ ].join("\n"));
90
+ }
91
+
92
+ function writeTaskSummaryWithBlocker(base: string, mid: string, sid: string, tid: string): void {
93
+ const tasksDir = join(base, ".gsd", "milestones", mid, "slices", sid, "tasks");
94
+ mkdirSync(tasksDir, { recursive: true });
95
+ writeFileSync(join(tasksDir, `${tid}-SUMMARY.md`), [
96
+ "---",
97
+ "blocker_discovered: true",
98
+ "---",
99
+ "",
100
+ `# ${tid} Summary`,
101
+ "",
102
+ "Blocker found during execution.",
103
+ ].join("\n"));
104
+ }
105
+
106
+ function writeSliceSummary(base: string, mid: string, sid: string): void {
107
+ const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
108
+ mkdirSync(dir, { recursive: true });
109
+ writeFileSync(join(dir, `${sid}-SUMMARY.md`), `# ${sid} Summary\n\nSlice done.\n`);
110
+ }
111
+
112
+ function writeMilestoneSummary(base: string, mid: string): void {
113
+ const dir = join(base, ".gsd", "milestones", mid);
114
+ mkdirSync(dir, { recursive: true });
115
+ writeFileSync(join(dir, `${mid}-SUMMARY.md`), `# ${mid} Summary\n\nMilestone complete.\n`);
116
+ }
117
+
118
+ function writeMilestoneValidation(base: string, mid: string, verdict: string = "pass"): void {
119
+ const dir = join(base, ".gsd", "milestones", mid);
120
+ mkdirSync(dir, { recursive: true });
121
+ writeFileSync(join(dir, `${mid}-VALIDATION.md`), [
122
+ "---",
123
+ `verdict: ${verdict}`,
124
+ "remediation_round: 0",
125
+ "---",
126
+ "",
127
+ "# Validation",
128
+ "Validated.",
129
+ ].join("\n"));
130
+ }
131
+
132
+ function writeReplanTrigger(base: string, mid: string, sid: string): void {
133
+ const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
134
+ mkdirSync(dir, { recursive: true });
135
+ writeFileSync(join(dir, `${sid}-REPLAN-TRIGGER.md`), "Triage replan triggered.\n");
136
+ }
137
+
138
+ function writeReplan(base: string, mid: string, sid: string): void {
139
+ const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
140
+ mkdirSync(dir, { recursive: true });
141
+ writeFileSync(join(dir, `${sid}-REPLAN.md`), "# Replan\n\nReplan completed.\n");
142
+ }
143
+
144
+ function writeContinue(base: string, mid: string, sid: string): void {
145
+ const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
146
+ mkdirSync(dir, { recursive: true });
147
+ writeFileSync(join(dir, `${sid}-CONTINUE.md`), [
148
+ "---",
149
+ "milestone: " + mid,
150
+ "slice: " + sid,
151
+ "task: T01",
152
+ "status: interrupted",
153
+ "---",
154
+ "",
155
+ "# Continue",
156
+ "Resume from step 2.",
157
+ ].join("\n"));
158
+ }
159
+
160
+ /** Standard roadmap with one incomplete slice */
161
+ function standardRoadmap(): string {
162
+ return [
163
+ "# M001: Test Milestone",
164
+ "",
165
+ "**Vision:** Test state machine.",
166
+ "",
167
+ "## Slices",
168
+ "",
169
+ "- [ ] **S01: First Slice** `risk:low` `depends:[]`",
170
+ " > After this: slice done.",
171
+ ].join("\n");
172
+ }
173
+
174
+ /** Roadmap with one done slice */
175
+ function doneSliceRoadmap(): string {
176
+ return [
177
+ "# M001: Test Milestone",
178
+ "",
179
+ "**Vision:** Test state machine.",
180
+ "",
181
+ "## Slices",
182
+ "",
183
+ "- [x] **S01: Done Slice** `risk:low` `depends:[]`",
184
+ " > After this: slice done.",
185
+ ].join("\n");
186
+ }
187
+
188
+ /** Standard plan with two incomplete tasks */
189
+ function standardPlan(): string {
190
+ return [
191
+ "# S01: First Slice",
192
+ "",
193
+ "**Goal:** Test.",
194
+ "**Demo:** Tests pass.",
195
+ "",
196
+ "## Tasks",
197
+ "",
198
+ "- [ ] **T01: First Task** `est:10m`",
199
+ " First task description.",
200
+ "",
201
+ "- [ ] **T02: Second Task** `est:10m`",
202
+ " Second task description.",
203
+ ].join("\n");
204
+ }
205
+
206
+ /** Plan with all tasks done */
207
+ function allDonePlan(): string {
208
+ return [
209
+ "# S01: First Slice",
210
+ "",
211
+ "**Goal:** Test.",
212
+ "**Demo:** Tests pass.",
213
+ "",
214
+ "## Tasks",
215
+ "",
216
+ "- [x] **T01: First Task** `est:10m`",
217
+ " First task done.",
218
+ "",
219
+ "- [x] **T02: Second Task** `est:10m`",
220
+ " Second task done.",
221
+ ].join("\n");
222
+ }
223
+
224
+ /** Plan with one done, one incomplete task */
225
+ function partialDonePlan(): string {
226
+ return [
227
+ "# S01: First Slice",
228
+ "",
229
+ "**Goal:** Test.",
230
+ "**Demo:** Tests pass.",
231
+ "",
232
+ "## Tasks",
233
+ "",
234
+ "- [x] **T01: First Task** `est:10m`",
235
+ " First task done.",
236
+ "",
237
+ "- [ ] **T02: Second Task** `est:10m`",
238
+ " Second task pending.",
239
+ ].join("\n");
240
+ }
241
+
242
+ // ═══════════════════════════════════════════════════════════════════════════════
243
+ // PHASE 1: pre-planning
244
+ // ═══════════════════════════════════════════════════════════════════════════════
245
+
246
+ describe("state-machine-full-walkthrough", () => {
247
+
248
+ describe("Phase 1: pre-planning", () => {
249
+ test("empty milestones dir → pre-planning", async () => {
250
+ const base = createFixtureBase();
251
+ invalidateStateCache();
252
+ const state = await deriveState(base);
253
+
254
+ assert.equal(state.phase, "pre-planning");
255
+ assert.equal(state.activeMilestone, null);
256
+ assert.equal(state.activeSlice, null);
257
+ assert.equal(state.activeTask, null);
258
+ assert.deepStrictEqual(state.registry, []);
259
+ });
260
+
261
+ test("milestone with CONTEXT but no ROADMAP → pre-planning", async () => {
262
+ const base = createFixtureBase();
263
+ writeContext(base, "M001", "# M001: Test\n\nSome context.");
264
+ invalidateStateCache();
265
+ const state = await deriveState(base);
266
+
267
+ assert.equal(state.phase, "pre-planning");
268
+ assert.ok(state.activeMilestone !== null, "activeMilestone should be set");
269
+ assert.equal(state.activeMilestone?.id, "M001");
270
+ });
271
+
272
+ test("roadmap with zero slices → pre-planning (not validating-milestone)", async () => {
273
+ const base = createFixtureBase();
274
+ writeContext(base, "M001", "# M001: Test\n\nContext.");
275
+ // Roadmap exists but has no slice entries
276
+ writeRoadmap(base, "M001", [
277
+ "# M001: Test Milestone",
278
+ "",
279
+ "**Vision:** Test.",
280
+ "",
281
+ "## Slices",
282
+ "",
283
+ "No slices defined yet.",
284
+ ].join("\n"));
285
+ invalidateStateCache();
286
+ const state = await deriveState(base);
287
+
288
+ assert.equal(state.phase, "pre-planning", "zero slices must NOT trigger validating-milestone (#2667)");
289
+ });
290
+ });
291
+
292
+ // ═══════════════════════════════════════════════════════════════════════════
293
+ // PHASE 2: needs-discussion
294
+ // ═══════════════════════════════════════════════════════════════════════════
295
+
296
+ describe("Phase 2: needs-discussion", () => {
297
+ test("CONTEXT-DRAFT exists, no CONTEXT → needs-discussion", async () => {
298
+ const base = createFixtureBase();
299
+ writeContextDraft(base, "M001", "# M001: Draft\n\nDraft context.");
300
+ invalidateStateCache();
301
+ const state = await deriveState(base);
302
+
303
+ assert.equal(state.phase, "needs-discussion");
304
+ assert.ok(state.activeMilestone !== null);
305
+ assert.equal(state.activeMilestone?.id, "M001");
306
+ });
307
+
308
+ test("both CONTEXT-DRAFT and CONTEXT exist → NOT needs-discussion", async () => {
309
+ const base = createFixtureBase();
310
+ writeContext(base, "M001", "# M001: Real\n\nReal context.");
311
+ writeContextDraft(base, "M001", "# M001: Draft\n\nDraft context.");
312
+ invalidateStateCache();
313
+ const state = await deriveState(base);
314
+
315
+ assert.notEqual(state.phase, "needs-discussion", "CONTEXT should win over CONTEXT-DRAFT");
316
+ });
317
+ });
318
+
319
+ // ═══════════════════════════════════════════════════════════════════════════
320
+ // PHASE 3: discussing (auto-mode only)
321
+ // ═══════════════════════════════════════════════════════════════════════════
322
+
323
+ describe("Phase 3: discussing (auto-mode only)", () => {
324
+ test("discussing is NOT reachable from deriveState", async () => {
325
+ // discussing is set only by auto-mode, never by state derivation.
326
+ // Verify that CONTEXT-DRAFT → needs-discussion (not discussing).
327
+ const base = createFixtureBase();
328
+ writeContextDraft(base, "M001", "# M001: Draft\n\nDraft.");
329
+ invalidateStateCache();
330
+ const state = await deriveState(base);
331
+ assert.notEqual(state.phase, "discussing");
332
+ });
333
+ });
334
+
335
+ // ═══════════════════════════════════════════════════════════════════════════
336
+ // PHASE 4: researching (auto-mode only)
337
+ // ═══════════════════════════════════════════════════════════════════════════
338
+
339
+ describe("Phase 4: researching (auto-mode only)", () => {
340
+ test("researching is NOT reachable from deriveState", async () => {
341
+ const base = createFixtureBase();
342
+ writeContext(base, "M001", "# M001: Test\n\nContext.");
343
+ writeRoadmap(base, "M001", standardRoadmap());
344
+ invalidateStateCache();
345
+ const state = await deriveState(base);
346
+ assert.notEqual(state.phase, "researching");
347
+ });
348
+ });
349
+
350
+ // ═══════════════════════════════════════════════════════════════════════════
351
+ // PHASE 5: planning
352
+ // ═══════════════════════════════════════════════════════════════════════════
353
+
354
+ describe("Phase 5: planning", () => {
355
+ test("roadmap with slice, no PLAN file → planning", async () => {
356
+ const base = createFixtureBase();
357
+ writeRoadmap(base, "M001", standardRoadmap());
358
+ invalidateStateCache();
359
+ const state = await deriveState(base);
360
+
361
+ assert.equal(state.phase, "planning");
362
+ assert.ok(state.activeSlice !== null);
363
+ assert.equal(state.activeSlice?.id, "S01");
364
+ });
365
+
366
+ test("PLAN exists but zero tasks → planning", async () => {
367
+ const base = createFixtureBase();
368
+ writeRoadmap(base, "M001", standardRoadmap());
369
+ // Plan file with no task entries
370
+ const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
371
+ mkdirSync(dir, { recursive: true });
372
+ writeFileSync(join(dir, "S01-PLAN.md"), [
373
+ "# S01: First Slice",
374
+ "",
375
+ "**Goal:** Test.",
376
+ "**Demo:** Tests pass.",
377
+ "",
378
+ "## Tasks",
379
+ "",
380
+ "No tasks defined yet.",
381
+ ].join("\n"));
382
+ invalidateStateCache();
383
+ const state = await deriveState(base);
384
+
385
+ assert.equal(state.phase, "planning", "plan with zero tasks should remain in planning");
386
+ });
387
+
388
+ test("PLAN with tasks but missing T##-PLAN.md files → planning", async () => {
389
+ const base = createFixtureBase();
390
+ writeRoadmap(base, "M001", standardRoadmap());
391
+ // Write plan file WITH tasks but WITHOUT stub T##-PLAN.md files
392
+ const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
393
+ mkdirSync(join(dir, "tasks"), { recursive: true });
394
+ writeFileSync(join(dir, "S01-PLAN.md"), standardPlan());
395
+ // Intentionally do NOT create T01-PLAN.md or T02-PLAN.md
396
+ invalidateStateCache();
397
+ const state = await deriveState(base);
398
+
399
+ assert.equal(state.phase, "planning", "missing task plan files should stay in planning");
400
+ });
401
+
402
+ test("PLAN with all task plan files → NOT planning", async () => {
403
+ const base = createFixtureBase();
404
+ writeRoadmap(base, "M001", standardRoadmap());
405
+ writePlan(base, "M001", "S01", standardPlan());
406
+ invalidateStateCache();
407
+ const state = await deriveState(base);
408
+
409
+ assert.notEqual(state.phase, "planning", "complete plan should advance past planning");
410
+ // Should be executing since there are incomplete tasks
411
+ assert.equal(state.phase, "executing");
412
+ });
413
+ });
414
+
415
+ // ═══════════════════════════════════════════════════════════════════════════
416
+ // PHASE 6: evaluating-gates (DB path only)
417
+ // ═══════════════════════════════════════════════════════════════════════════
418
+
419
+ describe("Phase 6: evaluating-gates", () => {
420
+ test("DB path: pending quality gates → evaluating-gates", async () => {
421
+ const base = createFixtureBase();
422
+ const dbPath = join(base, ".gsd", "gsd.db");
423
+ openDatabase(dbPath);
424
+
425
+ // Set up milestone + slice + task in DB
426
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
427
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
428
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
429
+
430
+ // Write plan on disk (needed for state derivation)
431
+ writeRoadmap(base, "M001", standardRoadmap());
432
+ writePlan(base, "M001", "S01", standardPlan());
433
+
434
+ // Insert a pending quality gate
435
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice", status: "pending" });
436
+
437
+ const pending = getPendingSliceGateCount("M001", "S01");
438
+ assert.ok(pending > 0, "should have pending gates");
439
+
440
+ invalidateStateCache();
441
+ const state = await deriveStateFromDb(base);
442
+
443
+ assert.equal(state.phase, "evaluating-gates");
444
+ });
445
+
446
+ test("DB path: no pending gates → NOT evaluating-gates", async () => {
447
+ const base = createFixtureBase();
448
+ const dbPath = join(base, ".gsd", "gsd.db");
449
+ openDatabase(dbPath);
450
+
451
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
452
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
453
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
454
+
455
+ writeRoadmap(base, "M001", standardRoadmap());
456
+ writePlan(base, "M001", "S01", standardPlan());
457
+
458
+ // No gate rows → getPendingSliceGateCount returns 0
459
+ const pending = getPendingSliceGateCount("M001", "S01");
460
+ assert.equal(pending, 0, "should have no pending gates");
461
+
462
+ invalidateStateCache();
463
+ const state = await deriveStateFromDb(base);
464
+
465
+ assert.notEqual(state.phase, "evaluating-gates");
466
+ });
467
+ });
468
+
469
+ // ═══════════════════════════════════════════════════════════════════════════
470
+ // PHASE 7: executing
471
+ // ═══════════════════════════════════════════════════════════════════════════
472
+
473
+ describe("Phase 7: executing", () => {
474
+ test("active task, no blockers → executing", async () => {
475
+ const base = createFixtureBase();
476
+ writeRoadmap(base, "M001", standardRoadmap());
477
+ writePlan(base, "M001", "S01", standardPlan());
478
+ invalidateStateCache();
479
+ const state = await deriveState(base);
480
+
481
+ assert.equal(state.phase, "executing");
482
+ assert.ok(state.activeTask !== null);
483
+ assert.equal(state.activeTask?.id, "T01");
484
+ });
485
+
486
+ test("active task with CONTINUE.md → executing with resume message", async () => {
487
+ const base = createFixtureBase();
488
+ writeRoadmap(base, "M001", standardRoadmap());
489
+ writePlan(base, "M001", "S01", standardPlan());
490
+ writeContinue(base, "M001", "S01");
491
+ invalidateStateCache();
492
+ const state = await deriveState(base);
493
+
494
+ assert.equal(state.phase, "executing");
495
+ assert.ok(
496
+ state.nextAction.toLowerCase().includes("resume") || state.nextAction.toLowerCase().includes("continue"),
497
+ "nextAction should mention resume/continue",
498
+ );
499
+ });
500
+
501
+ test("one task remaining among completed → executing (not summarizing)", async () => {
502
+ const base = createFixtureBase();
503
+ writeRoadmap(base, "M001", standardRoadmap());
504
+ writePlan(base, "M001", "S01", partialDonePlan());
505
+ invalidateStateCache();
506
+ const state = await deriveState(base);
507
+
508
+ assert.equal(state.phase, "executing", "should be executing while tasks remain");
509
+ assert.equal(state.activeTask?.id, "T02", "active task should be T02");
510
+ assert.equal(state.progress?.tasks?.done, 1);
511
+ assert.equal(state.progress?.tasks?.total, 2);
512
+ });
513
+ });
514
+
515
+ // ═══════════════════════════════════════════════════════════════════════════
516
+ // PHASE 8: verifying (auto-mode only)
517
+ // ═══════════════════════════════════════════════════════════════════════════
518
+
519
+ describe("Phase 8: verifying (auto-mode only)", () => {
520
+ test("verifying is NOT reachable from deriveState", async () => {
521
+ // verifying is set only by auto-mode verification gates.
522
+ const base = createFixtureBase();
523
+ writeRoadmap(base, "M001", standardRoadmap());
524
+ writePlan(base, "M001", "S01", allDonePlan());
525
+ invalidateStateCache();
526
+ const state = await deriveState(base);
527
+ assert.notEqual(state.phase, "verifying");
528
+ });
529
+ });
530
+
531
+ // ═══════════════════════════════════════════════════════════════════════════
532
+ // PHASE 9: summarizing
533
+ // ═══════════════════════════════════════════════════════════════════════════
534
+
535
+ describe("Phase 9: summarizing", () => {
536
+ test("all tasks done, slice not complete → summarizing", async () => {
537
+ const base = createFixtureBase();
538
+ writeRoadmap(base, "M001", standardRoadmap());
539
+ writePlan(base, "M001", "S01", allDonePlan());
540
+ invalidateStateCache();
541
+ const state = await deriveState(base);
542
+
543
+ assert.equal(state.phase, "summarizing");
544
+ assert.ok(state.activeSlice !== null);
545
+ assert.equal(state.activeSlice?.id, "S01");
546
+ assert.equal(state.activeTask, null, "no active task when all done");
547
+ assert.equal(state.progress?.tasks?.done, 2);
548
+ assert.equal(state.progress?.tasks?.total, 2);
549
+ });
550
+
551
+ test("tasks reconciled via SUMMARY on disk → summarizing", async () => {
552
+ const base = createFixtureBase();
553
+ writeRoadmap(base, "M001", standardRoadmap());
554
+ // Plan says tasks incomplete (headings, no checkboxes) ...
555
+ const planContent = [
556
+ "# S01: First Slice",
557
+ "",
558
+ "**Goal:** Test.",
559
+ "**Demo:** Tests pass.",
560
+ "",
561
+ "## Tasks",
562
+ "",
563
+ "### T01: First Task",
564
+ "First task.",
565
+ "",
566
+ "### T02: Second Task",
567
+ "Second task.",
568
+ ].join("\n");
569
+ const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
570
+ const tasksDir = join(dir, "tasks");
571
+ mkdirSync(tasksDir, { recursive: true });
572
+ writeFileSync(join(dir, "S01-PLAN.md"), planContent);
573
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\nStub.\n");
574
+ writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan\nStub.\n");
575
+
576
+ // ... but SUMMARY files exist on disk (reconciliation trigger)
577
+ writeTaskSummary(base, "M001", "S01", "T01");
578
+ writeTaskSummary(base, "M001", "S01", "T02");
579
+
580
+ invalidateStateCache();
581
+ const state = await deriveState(base);
582
+
583
+ // Reconciliation should mark both tasks done → summarizing
584
+ assert.equal(state.phase, "summarizing", "SUMMARY reconciliation should advance to summarizing");
585
+ });
586
+ });
587
+
588
+ // ═══════════════════════════════════════════════════════════════════════════
589
+ // PHASE 10: advancing (auto-mode only)
590
+ // ═══════════════════════════════════════════════════════════════════════════
591
+
592
+ describe("Phase 10: advancing (auto-mode only)", () => {
593
+ test("advancing is NOT reachable from deriveState", async () => {
594
+ // advancing is an internal auto-mode transition marker
595
+ const base = createFixtureBase();
596
+ writeRoadmap(base, "M001", standardRoadmap());
597
+ writePlan(base, "M001", "S01", standardPlan());
598
+ invalidateStateCache();
599
+ const state = await deriveState(base);
600
+ assert.notEqual(state.phase, "advancing");
601
+ });
602
+ });
603
+
604
+ // ═══════════════════════════════════════════════════════════════════════════
605
+ // PHASE 11: validating-milestone
606
+ // ═══════════════════════════════════════════════════════════════════════════
607
+
608
+ describe("Phase 11: validating-milestone", () => {
609
+ test("all slices done, no VALIDATION file → validating-milestone", async () => {
610
+ const base = createFixtureBase();
611
+ writeRoadmap(base, "M001", doneSliceRoadmap());
612
+ invalidateStateCache();
613
+ const state = await deriveState(base);
614
+
615
+ assert.equal(state.phase, "validating-milestone");
616
+ assert.ok(state.activeMilestone !== null);
617
+ });
618
+
619
+ test("all slices done, VALIDATION with unparseable verdict → validating-milestone", async () => {
620
+ const base = createFixtureBase();
621
+ writeRoadmap(base, "M001", doneSliceRoadmap());
622
+ // Write a validation file with no parseable verdict
623
+ const dir = join(base, ".gsd", "milestones", "M001");
624
+ mkdirSync(dir, { recursive: true });
625
+ writeFileSync(join(dir, "M001-VALIDATION.md"), "Just some text with no frontmatter verdict.");
626
+ invalidateStateCache();
627
+ const state = await deriveState(base);
628
+
629
+ assert.equal(state.phase, "validating-milestone", "unparseable verdict should stay in validating");
630
+ });
631
+
632
+ test("all slices done, terminal verdict → NOT validating-milestone", async () => {
633
+ const base = createFixtureBase();
634
+ writeRoadmap(base, "M001", doneSliceRoadmap());
635
+ writeMilestoneValidation(base, "M001", "pass");
636
+ invalidateStateCache();
637
+ const state = await deriveState(base);
638
+
639
+ assert.notEqual(state.phase, "validating-milestone");
640
+ });
641
+ });
642
+
643
+ // ═══════════════════════════════════════════════════════════════════════════
644
+ // PHASE 12: completing-milestone
645
+ // ═══════════════════════════════════════════════════════════════════════════
646
+
647
+ describe("Phase 12: completing-milestone", () => {
648
+ test("all slices done, validation terminal, no SUMMARY → completing-milestone", async () => {
649
+ const base = createFixtureBase();
650
+ writeRoadmap(base, "M001", doneSliceRoadmap());
651
+ writeMilestoneValidation(base, "M001", "pass");
652
+ invalidateStateCache();
653
+ const state = await deriveState(base);
654
+
655
+ assert.equal(state.phase, "completing-milestone");
656
+ assert.ok(state.activeMilestone !== null);
657
+ });
658
+
659
+ test("all slices done, validation terminal, SUMMARY exists → NOT completing-milestone", async () => {
660
+ const base = createFixtureBase();
661
+ writeRoadmap(base, "M001", doneSliceRoadmap());
662
+ writeMilestoneValidation(base, "M001", "pass");
663
+ writeMilestoneSummary(base, "M001");
664
+ invalidateStateCache();
665
+ const state = await deriveState(base);
666
+
667
+ assert.notEqual(state.phase, "completing-milestone", "should be complete, not completing");
668
+ assert.equal(state.phase, "complete");
669
+ });
670
+ });
671
+
672
+ // ═══════════════════════════════════════════════════════════════════════════
673
+ // PHASE 13: replanning-slice
674
+ // ═══════════════════════════════════════════════════════════════════════════
675
+
676
+ describe("Phase 13: replanning-slice", () => {
677
+ test("filesystem: task with blocker_discovered, no REPLAN.md → replanning-slice", async () => {
678
+ const base = createFixtureBase();
679
+ writeRoadmap(base, "M001", standardRoadmap());
680
+ // T01 is done with blocker, T02 is pending
681
+ writePlan(base, "M001", "S01", partialDonePlan());
682
+ writeTaskSummaryWithBlocker(base, "M001", "S01", "T01");
683
+ invalidateStateCache();
684
+ const state = await deriveState(base);
685
+
686
+ assert.equal(state.phase, "replanning-slice");
687
+ assert.ok(state.blockers.length > 0, "should have blocker details");
688
+ });
689
+
690
+ test("filesystem: REPLAN-TRIGGER.md exists, no REPLAN.md → replanning-slice", async () => {
691
+ const base = createFixtureBase();
692
+ writeRoadmap(base, "M001", standardRoadmap());
693
+ writePlan(base, "M001", "S01", standardPlan());
694
+ writeReplanTrigger(base, "M001", "S01");
695
+ invalidateStateCache();
696
+ const state = await deriveState(base);
697
+
698
+ assert.equal(state.phase, "replanning-slice");
699
+ });
700
+
701
+ test("filesystem: REPLAN-TRIGGER + REPLAN.md exists → NOT replanning-slice (loop guard)", async () => {
702
+ const base = createFixtureBase();
703
+ writeRoadmap(base, "M001", standardRoadmap());
704
+ writePlan(base, "M001", "S01", standardPlan());
705
+ writeReplanTrigger(base, "M001", "S01");
706
+ writeReplan(base, "M001", "S01");
707
+ invalidateStateCache();
708
+ const state = await deriveState(base);
709
+
710
+ assert.notEqual(state.phase, "replanning-slice", "REPLAN.md loop guard should prevent re-entering replanning");
711
+ // Should fall through to executing
712
+ assert.equal(state.phase, "executing");
713
+ });
714
+ });
715
+
716
+ // ═══════════════════════════════════════════════════════════════════════════
717
+ // PHASE 14: complete
718
+ // ═══════════════════════════════════════════════════════════════════════════
719
+
720
+ describe("Phase 14: complete", () => {
721
+ test("single milestone with SUMMARY + VALIDATION → complete", async () => {
722
+ const base = createFixtureBase();
723
+ writeRoadmap(base, "M001", doneSliceRoadmap());
724
+ writeMilestoneValidation(base, "M001", "pass");
725
+ writeMilestoneSummary(base, "M001");
726
+ invalidateStateCache();
727
+ const state = await deriveState(base);
728
+
729
+ assert.equal(state.phase, "complete");
730
+ assert.equal(state.registry.length, 1);
731
+ assert.equal(state.registry[0]?.status, "complete");
732
+ });
733
+
734
+ test("all milestones complete → complete", async () => {
735
+ const base = createFixtureBase();
736
+ // M001: complete
737
+ writeRoadmap(base, "M001", doneSliceRoadmap());
738
+ writeMilestoneValidation(base, "M001", "pass");
739
+ writeMilestoneSummary(base, "M001");
740
+
741
+ // M002: also complete
742
+ writeRoadmap(base, "M002", [
743
+ "# M002: Second Milestone",
744
+ "",
745
+ "**Vision:** Test.",
746
+ "",
747
+ "## Slices",
748
+ "",
749
+ "- [x] **S01: Done** `risk:low` `depends:[]`",
750
+ " > After this: done.",
751
+ ].join("\n"));
752
+ writeMilestoneValidation(base, "M002", "pass");
753
+ writeMilestoneSummary(base, "M002");
754
+
755
+ invalidateStateCache();
756
+ const state = await deriveState(base);
757
+
758
+ assert.equal(state.phase, "complete");
759
+ assert.equal(state.registry.length, 2);
760
+ assert.ok(state.registry.every(e => e.status === "complete"), "all registry entries should be complete");
761
+ });
762
+ });
763
+
764
+ // ═══════════════════════════════════════════════════════════════════════════
765
+ // PHASE 15: paused (auto-mode only)
766
+ // ═══════════════════════════════════════════════════════════════════════════
767
+
768
+ describe("Phase 15: paused (auto-mode only)", () => {
769
+ test("paused is NOT reachable from deriveState", async () => {
770
+ const base = createFixtureBase();
771
+ writeRoadmap(base, "M001", standardRoadmap());
772
+ writePlan(base, "M001", "S01", standardPlan());
773
+ invalidateStateCache();
774
+ const state = await deriveState(base);
775
+ assert.notEqual(state.phase, "paused");
776
+ });
777
+ });
778
+
779
+ // ═══════════════════════════════════════════════════════════════════════════
780
+ // PHASE 16: blocked
781
+ // ═══════════════════════════════════════════════════════════════════════════
782
+
783
+ describe("Phase 16: blocked", () => {
784
+ test("milestone with unmet dependency → blocked", async () => {
785
+ const base = createFixtureBase();
786
+ // M001 depends on M000 which doesn't exist — uses YAML frontmatter
787
+ writeContext(base, "M001", [
788
+ "---",
789
+ "depends_on:",
790
+ " - M000",
791
+ "---",
792
+ "",
793
+ "# M001: Test",
794
+ "",
795
+ "Context.",
796
+ ].join("\n"));
797
+ writeRoadmap(base, "M001", [
798
+ "# M001: Test Milestone",
799
+ "",
800
+ "**Vision:** Test blocked.",
801
+ "",
802
+ "## Slices",
803
+ "",
804
+ "- [ ] **S01: Slice** `risk:low` `depends:[]`",
805
+ " > After this: done.",
806
+ ].join("\n"));
807
+ invalidateStateCache();
808
+ const state = await deriveState(base);
809
+
810
+ assert.equal(state.phase, "blocked");
811
+ assert.ok(state.blockers.length > 0, "should have blockers");
812
+ });
813
+
814
+ test("no eligible slice (all deps unmet) → blocked at slice level", async () => {
815
+ const base = createFixtureBase();
816
+ // S01 depends on S00 which doesn't exist
817
+ writeRoadmap(base, "M001", [
818
+ "# M001: Test Milestone",
819
+ "",
820
+ "**Vision:** Test blocked slices.",
821
+ "",
822
+ "## Slices",
823
+ "",
824
+ "- [ ] **S01: First** `risk:low` `depends:[S00]`",
825
+ " > After this: done.",
826
+ ].join("\n"));
827
+ invalidateStateCache();
828
+ const state = await deriveState(base);
829
+
830
+ assert.equal(state.phase, "blocked");
831
+ assert.ok(
832
+ state.blockers.some(b => b.includes("dependency") || b.includes("eligible")),
833
+ "blockers should mention dependency or eligibility",
834
+ );
835
+ });
836
+ });
837
+
838
+ // ═══════════════════════════════════════════════════════════════════════════
839
+ // RECONCILIATION
840
+ // ═══════════════════════════════════════════════════════════════════════════
841
+
842
+ describe("Reconciliation", () => {
843
+ test("DB: task with SUMMARY on disk but DB says pending → reconciliation fixes status (#2514)", async () => {
844
+ const base = createFixtureBase();
845
+ const dbPath = join(base, ".gsd", "gsd.db");
846
+ openDatabase(dbPath);
847
+
848
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
849
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
850
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
851
+ insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Task", status: "pending" });
852
+
853
+ writeRoadmap(base, "M001", standardRoadmap());
854
+ writePlan(base, "M001", "S01", standardPlan());
855
+
856
+ // Write SUMMARY files on disk for both tasks (simulating session disconnect)
857
+ writeTaskSummary(base, "M001", "S01", "T01");
858
+ writeTaskSummary(base, "M001", "S01", "T02");
859
+
860
+ invalidateStateCache();
861
+ const state = await deriveStateFromDb(base);
862
+
863
+ // Reconciliation should detect SUMMARY→DB mismatch and update
864
+ // All tasks done → summarizing (not executing)
865
+ assert.equal(state.phase, "summarizing", "reconciliation should advance past pending tasks");
866
+ });
867
+
868
+ test("empty DB with disk milestones → disk-to-DB sync (#2631)", async () => {
869
+ const base = createFixtureBase();
870
+ writeContext(base, "M001", "# M001: Test\n\nContext.");
871
+
872
+ // Open DB — milestones table starts empty
873
+ openDatabase(":memory:");
874
+ const before = getAllMilestones();
875
+ assert.equal(before.length, 0, "DB should start empty");
876
+
877
+ invalidateStateCache();
878
+ const state = await deriveState(base);
879
+
880
+ // After deriveState, DB should have the disk milestone
881
+ const after = getAllMilestones();
882
+ assert.ok(after.length > 0, "DB should have milestones after reconciliation");
883
+ assert.equal(after[0]!.id, "M001");
884
+ assert.ok(state.activeMilestone !== null);
885
+ });
886
+
887
+ test("ghost milestone (empty dir) → NOT in registry", async () => {
888
+ const base = createFixtureBase();
889
+ // Create empty milestone dir (ghost — no CONTEXT, ROADMAP, SUMMARY)
890
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
891
+ // Create a real milestone too
892
+ writeContext(base, "M002", "# M002: Real\n\nContext.");
893
+ invalidateStateCache();
894
+ const state = await deriveState(base);
895
+
896
+ // M001 (ghost) should not appear in registry
897
+ const m001 = state.registry.find(e => e.id === "M001");
898
+ assert.equal(m001, undefined, "ghost milestone should not appear in registry");
899
+ // M002 should be there
900
+ const m002 = state.registry.find(e => e.id === "M002");
901
+ assert.ok(m002 !== undefined, "real milestone should appear in registry");
902
+ });
903
+
904
+ test("ghost milestone detection helper", () => {
905
+ const base = createFixtureBase();
906
+ // Ghost: empty dir
907
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
908
+ clearPathCache();
909
+ assert.equal(isGhostMilestone(base, "M001"), true, "empty dir is ghost");
910
+
911
+ // Not ghost: has CONTEXT
912
+ writeContext(base, "M002", "# M002\n\nContext.");
913
+ clearPathCache();
914
+ assert.equal(isGhostMilestone(base, "M002"), false, "dir with CONTEXT is not ghost");
915
+ });
916
+ });
917
+
918
+ // ═══════════════════════════════════════════════════════════════════════════
919
+ // CROSS-VALIDATION
920
+ // ═══════════════════════════════════════════════════════════════════════════
921
+
922
+ describe("Cross-validation: DB vs filesystem", () => {
923
+ test("executing scenario produces same phase on both paths", async () => {
924
+ const base = createFixtureBase();
925
+ const dbPath = join(base, ".gsd", "gsd.db");
926
+ openDatabase(dbPath);
927
+
928
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
929
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
930
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: First", status: "pending" });
931
+ insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Second", status: "pending" });
932
+
933
+ writeRoadmap(base, "M001", standardRoadmap());
934
+ writePlan(base, "M001", "S01", standardPlan());
935
+
936
+ invalidateStateCache();
937
+ const dbState = await deriveStateFromDb(base);
938
+
939
+ closeDatabase();
940
+
941
+ invalidateStateCache();
942
+ const fsState = await deriveState(base);
943
+
944
+ assert.equal(dbState.phase, "executing", "DB path should produce executing");
945
+ assert.equal(fsState.phase, "executing", "filesystem path should produce executing");
946
+ assert.equal(dbState.activeTask?.id, fsState.activeTask?.id, "active task should match");
947
+ });
948
+
949
+ test("summarizing scenario produces same phase on both paths", async () => {
950
+ const base = createFixtureBase();
951
+ const dbPath = join(base, ".gsd", "gsd.db");
952
+ openDatabase(dbPath);
953
+
954
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
955
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
956
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: First", status: "complete" });
957
+ insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Second", status: "complete" });
958
+
959
+ writeRoadmap(base, "M001", standardRoadmap());
960
+ writePlan(base, "M001", "S01", allDonePlan());
961
+
962
+ invalidateStateCache();
963
+ const dbState = await deriveStateFromDb(base);
964
+
965
+ closeDatabase();
966
+
967
+ invalidateStateCache();
968
+ const fsState = await deriveState(base);
969
+
970
+ assert.equal(dbState.phase, "summarizing", "DB path should produce summarizing");
971
+ assert.equal(fsState.phase, "summarizing", "filesystem path should produce summarizing");
972
+ });
973
+ });
974
+
975
+ // ═══════════════════════════════════════════════════════════════════════════
976
+ // EDGE CASES
977
+ // ═══════════════════════════════════════════════════════════════════════════
978
+
979
+ describe("Edge cases", () => {
980
+ test("isValidationTerminal: terminal verdicts", () => {
981
+ assert.equal(isValidationTerminal("---\nverdict: pass\n---\n"), true, "pass is terminal");
982
+ assert.equal(isValidationTerminal("---\nverdict: fail\n---\n"), true, "fail is terminal");
983
+ assert.equal(isValidationTerminal("---\nverdict: needs-remediation\n---\n"), true, "needs-remediation is terminal");
984
+ assert.equal(isValidationTerminal("---\nverdict: needs-attention\n---\n"), true, "needs-attention is terminal");
985
+ });
986
+
987
+ test("isValidationTerminal: non-terminal content", () => {
988
+ assert.equal(isValidationTerminal("No frontmatter at all"), false, "no frontmatter is not terminal");
989
+ assert.equal(isValidationTerminal(""), false, "empty string is not terminal");
990
+ assert.equal(isValidationTerminal("---\n---\n"), false, "empty frontmatter is not terminal");
991
+ });
992
+
993
+ test("isClosedStatus boundary", () => {
994
+ assert.equal(isClosedStatus("complete"), true);
995
+ assert.equal(isClosedStatus("done"), true);
996
+ assert.equal(isClosedStatus("pending"), false);
997
+ assert.equal(isClosedStatus("in-progress"), false);
998
+ assert.equal(isClosedStatus("blocked"), false);
999
+ assert.equal(isClosedStatus("active"), false);
1000
+ assert.equal(isClosedStatus(""), false);
1001
+ });
1002
+
1003
+ test("multiple milestones: M001 complete, M002 active → M002 is activeMilestone", async () => {
1004
+ const base = createFixtureBase();
1005
+ // M001: complete
1006
+ writeRoadmap(base, "M001", doneSliceRoadmap());
1007
+ writeMilestoneValidation(base, "M001", "pass");
1008
+ writeMilestoneSummary(base, "M001");
1009
+
1010
+ // M002: active, in planning phase
1011
+ writeContext(base, "M002", "# M002: Next Milestone\n\nContext for M002.");
1012
+ writeRoadmap(base, "M002", [
1013
+ "# M002: Next Milestone",
1014
+ "",
1015
+ "**Vision:** Next phase.",
1016
+ "",
1017
+ "## Slices",
1018
+ "",
1019
+ "- [ ] **S01: New Slice** `risk:low` `depends:[]`",
1020
+ " > After this: done.",
1021
+ ].join("\n"));
1022
+
1023
+ invalidateStateCache();
1024
+ const state = await deriveState(base);
1025
+
1026
+ assert.equal(state.activeMilestone?.id, "M002", "active milestone should be M002");
1027
+ assert.notEqual(state.phase, "complete", "should not be complete while M002 is active");
1028
+ // M001 in registry as complete
1029
+ const m001 = state.registry.find(e => e.id === "M001");
1030
+ assert.ok(m001 !== undefined, "M001 should be in registry");
1031
+ assert.equal(m001?.status, "complete", "M001 should be complete");
1032
+ // M002 in registry as active
1033
+ const m002 = state.registry.find(e => e.id === "M002");
1034
+ assert.ok(m002 !== undefined, "M002 should be in registry");
1035
+ assert.equal(m002?.status, "active", "M002 should be active");
1036
+ });
1037
+ });
1038
+
1039
+ // ═══════════════════════════════════════════════════════════════════════════
1040
+ // FAILURE MODES: What happens when things go wrong
1041
+ // ═══════════════════════════════════════════════════════════════════════════
1042
+
1043
+ describe("Failure: DB has slice but no task rows (partial migration)", () => {
1044
+ test("DB tasks empty but PLAN on disk has tasks → wrong phase (planning)", async () => {
1045
+ const base = createFixtureBase();
1046
+ const dbPath = join(base, ".gsd", "gsd.db");
1047
+ openDatabase(dbPath);
1048
+
1049
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
1050
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
1051
+ // NO insertTask() — simulates partial migration / failed write
1052
+
1053
+ writeRoadmap(base, "M001", standardRoadmap());
1054
+ writePlan(base, "M001", "S01", standardPlan());
1055
+
1056
+ invalidateStateCache();
1057
+ const state = await deriveStateFromDb(base);
1058
+
1059
+ // BUG: Returns "planning" because getSliceTasks() returns []
1060
+ // and line 703 treats empty tasks as "no tasks defined".
1061
+ // PLAN file on disk has T01/T02 but DB doesn't know about them.
1062
+ assert.equal(state.phase, "planning",
1063
+ "KNOWN ISSUE: DB empty tasks → planning even though PLAN has tasks on disk");
1064
+ });
1065
+ });
1066
+
1067
+ describe("Failure: partial SUMMARY reconciliation", () => {
1068
+ test("only one task has SUMMARY, other still pending → executing next task", async () => {
1069
+ const base = createFixtureBase();
1070
+ const dbPath = join(base, ".gsd", "gsd.db");
1071
+ openDatabase(dbPath);
1072
+
1073
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
1074
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
1075
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
1076
+ insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Task", status: "pending" });
1077
+
1078
+ writeRoadmap(base, "M001", standardRoadmap());
1079
+ writePlan(base, "M001", "S01", standardPlan());
1080
+ // Only T01 has SUMMARY, T02 does not
1081
+ writeTaskSummary(base, "M001", "S01", "T01");
1082
+
1083
+ invalidateStateCache();
1084
+ const state = await deriveStateFromDb(base);
1085
+
1086
+ // T01 reconciled to complete, T02 still pending → executing T02
1087
+ assert.equal(state.phase, "executing");
1088
+ assert.equal(state.activeTask?.id, "T02", "should advance to next pending task");
1089
+ });
1090
+ });
1091
+
1092
+ describe("Failure: 0-byte files", () => {
1093
+ test("0-byte SUMMARY file triggers reconciliation (existsSync-only check)", async () => {
1094
+ const base = createFixtureBase();
1095
+ writeRoadmap(base, "M001", standardRoadmap());
1096
+ writePlan(base, "M001", "S01", standardPlan());
1097
+ // Write 0-byte SUMMARY — existsSync returns true for empty files
1098
+ const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
1099
+ mkdirSync(tasksDir, { recursive: true });
1100
+ writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "");
1101
+
1102
+ invalidateStateCache();
1103
+ clearPathCache();
1104
+ const state = await deriveState(base);
1105
+
1106
+ // The reconciler checks existsSync(summaryPath) at line 1328
1107
+ // — it does NOT read content. So 0-byte file counts as "done".
1108
+ // This is a known gap: empty SUMMARY treated as completion.
1109
+ assert.equal(state.phase, "executing",
1110
+ "0-byte SUMMARY marks T01 done via reconciliation, T02 becomes active");
1111
+ assert.equal(state.activeTask?.id, "T02");
1112
+ });
1113
+
1114
+ test("0-byte VALIDATION file → stays in validating-milestone", async () => {
1115
+ const base = createFixtureBase();
1116
+ writeRoadmap(base, "M001", doneSliceRoadmap());
1117
+ const dir = join(base, ".gsd", "milestones", "M001");
1118
+ mkdirSync(dir, { recursive: true });
1119
+ writeFileSync(join(dir, "M001-VALIDATION.md"), "");
1120
+
1121
+ invalidateStateCache();
1122
+ const state = await deriveState(base);
1123
+
1124
+ assert.equal(state.phase, "validating-milestone",
1125
+ "0-byte VALIDATION should not be treated as terminal");
1126
+ });
1127
+
1128
+ test("0-byte PLAN file → planning phase", async () => {
1129
+ const base = createFixtureBase();
1130
+ writeRoadmap(base, "M001", standardRoadmap());
1131
+ const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
1132
+ mkdirSync(dir, { recursive: true });
1133
+ writeFileSync(join(dir, "S01-PLAN.md"), "");
1134
+
1135
+ invalidateStateCache();
1136
+ const state = await deriveState(base);
1137
+
1138
+ assert.equal(state.phase, "planning", "0-byte PLAN should stay in planning");
1139
+ });
1140
+ });
1141
+
1142
+ describe("Failure: DB/filesystem divergence", () => {
1143
+ test("DB says slice complete, no milestone VALIDATION → validating-milestone", async () => {
1144
+ const base = createFixtureBase();
1145
+ const dbPath = join(base, ".gsd", "gsd.db");
1146
+ openDatabase(dbPath);
1147
+
1148
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
1149
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "complete", depends: [] });
1150
+
1151
+ writeRoadmap(base, "M001", doneSliceRoadmap());
1152
+
1153
+ invalidateStateCache();
1154
+ const state = await deriveStateFromDb(base);
1155
+
1156
+ assert.equal(state.phase, "validating-milestone",
1157
+ "DB-complete slice should trigger milestone validation");
1158
+ });
1159
+
1160
+ test("DB says task complete but SUMMARY missing → no crash, advances to next", async () => {
1161
+ const base = createFixtureBase();
1162
+ const dbPath = join(base, ".gsd", "gsd.db");
1163
+ openDatabase(dbPath);
1164
+
1165
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
1166
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
1167
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "complete" });
1168
+ insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Task", status: "pending" });
1169
+
1170
+ writeRoadmap(base, "M001", standardRoadmap());
1171
+ writePlan(base, "M001", "S01", standardPlan());
1172
+
1173
+ invalidateStateCache();
1174
+ const state = await deriveStateFromDb(base);
1175
+
1176
+ assert.equal(state.phase, "executing");
1177
+ assert.equal(state.activeTask?.id, "T02");
1178
+ });
1179
+
1180
+ test("milestone in DB but directory missing from disk → no crash", async () => {
1181
+ const base = createFixtureBase();
1182
+ const dbPath = join(base, ".gsd", "gsd.db");
1183
+ openDatabase(dbPath);
1184
+
1185
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
1186
+
1187
+ invalidateStateCache();
1188
+ const state = await deriveStateFromDb(base);
1189
+
1190
+ assert.ok(state.phase !== undefined, "should produce a valid phase");
1191
+ });
1192
+ });
1193
+
1194
+ describe("Failure: corrupt frontmatter", () => {
1195
+ test("VALIDATION with broken frontmatter → stays in validating", async () => {
1196
+ const base = createFixtureBase();
1197
+ writeRoadmap(base, "M001", doneSliceRoadmap());
1198
+ const dir = join(base, ".gsd", "milestones", "M001");
1199
+ mkdirSync(dir, { recursive: true });
1200
+ writeFileSync(join(dir, "M001-VALIDATION.md"), [
1201
+ "---",
1202
+ "this is not: valid: yaml: {{{}}}",
1203
+ "---",
1204
+ "",
1205
+ "Some content.",
1206
+ ].join("\n"));
1207
+
1208
+ invalidateStateCache();
1209
+ const state = await deriveState(base);
1210
+
1211
+ assert.equal(state.phase, "validating-milestone",
1212
+ "corrupt frontmatter should keep milestone in validating phase");
1213
+ });
1214
+
1215
+ test("CONTEXT with broken depends_on → no crash, deps empty", async () => {
1216
+ const base = createFixtureBase();
1217
+ writeContext(base, "M001", [
1218
+ "---",
1219
+ "depends_on: {{{invalid}}}",
1220
+ "---",
1221
+ "",
1222
+ "# M001: Test",
1223
+ ].join("\n"));
1224
+ writeRoadmap(base, "M001", standardRoadmap());
1225
+
1226
+ invalidateStateCache();
1227
+ const state = await deriveState(base);
1228
+
1229
+ assert.ok(state.phase !== undefined, "should not crash on corrupt depends_on");
1230
+ // With corrupt deps, parseContextDependsOn returns [] → no blocking
1231
+ assert.notEqual(state.phase, "blocked",
1232
+ "corrupt deps should not falsely block milestone");
1233
+ });
1234
+ });
1235
+
1236
+ describe("Failure: missing task plan files in DB path", () => {
1237
+ test("DB has tasks but no T##-PLAN.md files → planning phase", async () => {
1238
+ const base = createFixtureBase();
1239
+ const dbPath = join(base, ".gsd", "gsd.db");
1240
+ openDatabase(dbPath);
1241
+
1242
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
1243
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
1244
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
1245
+
1246
+ writeRoadmap(base, "M001", standardRoadmap());
1247
+ const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
1248
+ mkdirSync(join(dir, "tasks"), { recursive: true });
1249
+ writeFileSync(join(dir, "S01-PLAN.md"), standardPlan());
1250
+ // NO T01-PLAN.md
1251
+
1252
+ invalidateStateCache();
1253
+ const state = await deriveStateFromDb(base);
1254
+
1255
+ assert.equal(state.phase, "planning",
1256
+ "missing T##-PLAN.md files should keep state in planning");
1257
+ });
1258
+ });
1259
+
1260
+ describe("Failure: stale path cache", () => {
1261
+ test("file created after cache populated → must clear path cache", async () => {
1262
+ const base = createFixtureBase();
1263
+ writeRoadmap(base, "M001", standardRoadmap());
1264
+
1265
+ invalidateStateCache();
1266
+ clearPathCache();
1267
+ const state1 = await deriveState(base);
1268
+ assert.equal(state1.phase, "planning");
1269
+
1270
+ // Write PLAN AFTER first derivation cached paths
1271
+ writePlan(base, "M001", "S01", standardPlan());
1272
+
1273
+ // Without clearPathCache, stale cache may miss the new file
1274
+ invalidateStateCache();
1275
+ clearPathCache();
1276
+ const state2 = await deriveState(base);
1277
+
1278
+ assert.equal(state2.phase, "executing",
1279
+ "after cache clear, should see the new PLAN file");
1280
+ });
1281
+ });
1282
+
1283
+ describe("Failure: blocker detection edge cases", () => {
1284
+ test("filesystem: blocker in SUMMARY but task not marked [x] → still detected", async () => {
1285
+ const base = createFixtureBase();
1286
+ writeRoadmap(base, "M001", standardRoadmap());
1287
+ // T01 marked done in plan, T02 pending
1288
+ writePlan(base, "M001", "S01", partialDonePlan());
1289
+ // T01 SUMMARY has blocker_discovered in frontmatter
1290
+ writeTaskSummaryWithBlocker(base, "M001", "S01", "T01");
1291
+
1292
+ invalidateStateCache();
1293
+ clearPathCache();
1294
+ const state = await deriveState(base);
1295
+
1296
+ assert.equal(state.phase, "replanning-slice",
1297
+ "blocker_discovered in SUMMARY frontmatter should trigger replanning");
1298
+ });
1299
+ });
1300
+
1301
+ // ═══════════════════════════════════════════════════════════════════════════
1302
+ // FAILURE AT EVERY PHASE: What breaks mid-transition
1303
+ // ═══════════════════════════════════════════════════════════════════════════
1304
+
1305
+ describe("Failure at pre-planning: CONTEXT file half-written", () => {
1306
+ test("CONTEXT exists but is garbage → still enters pre-planning (no roadmap)", async () => {
1307
+ const base = createFixtureBase();
1308
+ writeContext(base, "M001", "\x00\x00\x00binary garbage\xff\xfe");
1309
+ invalidateStateCache();
1310
+ clearPathCache();
1311
+ const state = await deriveState(base);
1312
+
1313
+ // File exists so milestone is not ghost, but no roadmap → pre-planning
1314
+ assert.equal(state.phase, "pre-planning");
1315
+ assert.ok(state.activeMilestone !== null);
1316
+ });
1317
+ });
1318
+
1319
+ describe("Failure at needs-discussion: CONTEXT-DRAFT is empty", () => {
1320
+ test("0-byte CONTEXT-DRAFT → should still trigger needs-discussion", async () => {
1321
+ const base = createFixtureBase();
1322
+ const dir = join(base, ".gsd", "milestones", "M001");
1323
+ mkdirSync(dir, { recursive: true });
1324
+ writeFileSync(join(dir, "M001-CONTEXT-DRAFT.md"), "");
1325
+ invalidateStateCache();
1326
+ clearPathCache();
1327
+ const state = await deriveState(base);
1328
+
1329
+ // File exists (even empty) → not a ghost, has draft → needs-discussion
1330
+ assert.equal(state.phase, "needs-discussion",
1331
+ "0-byte draft should still trigger discussion phase");
1332
+ });
1333
+ });
1334
+
1335
+ describe("Failure at planning: ROADMAP exists but is unparseable", () => {
1336
+ test("ROADMAP with no slices section → pre-planning (zero slices)", async () => {
1337
+ const base = createFixtureBase();
1338
+ writeRoadmap(base, "M001", "# M001: Test\n\nJust some text, no ## Slices section.");
1339
+ invalidateStateCache();
1340
+ clearPathCache();
1341
+ const state = await deriveState(base);
1342
+
1343
+ // parseRoadmap finds no slices → empty array → pre-planning
1344
+ assert.equal(state.phase, "pre-planning",
1345
+ "unparseable roadmap with no slices should fall to pre-planning");
1346
+ });
1347
+
1348
+ test("ROADMAP with broken slice syntax → treats as zero slices", async () => {
1349
+ const base = createFixtureBase();
1350
+ writeRoadmap(base, "M001", [
1351
+ "# M001: Test",
1352
+ "",
1353
+ "**Vision:** Test.",
1354
+ "",
1355
+ "## Slices",
1356
+ "",
1357
+ "This is not a valid slice entry at all.",
1358
+ "Neither is this.",
1359
+ ].join("\n"));
1360
+ invalidateStateCache();
1361
+ clearPathCache();
1362
+ const state = await deriveState(base);
1363
+
1364
+ // No parseable slice entries → zero slices → pre-planning
1365
+ assert.equal(state.phase, "pre-planning",
1366
+ "broken slice syntax should result in zero slices");
1367
+ });
1368
+ });
1369
+
1370
+ describe("Failure at planning: PLAN file is corrupt", () => {
1371
+ test("PLAN exists but tasks section is garbage → zero tasks → planning", async () => {
1372
+ const base = createFixtureBase();
1373
+ writeRoadmap(base, "M001", standardRoadmap());
1374
+ const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
1375
+ mkdirSync(dir, { recursive: true });
1376
+ writeFileSync(join(dir, "S01-PLAN.md"), [
1377
+ "# S01: Slice",
1378
+ "",
1379
+ "## Tasks",
1380
+ "",
1381
+ "random garbage with no task markers",
1382
+ "more garbage",
1383
+ ].join("\n"));
1384
+ invalidateStateCache();
1385
+ clearPathCache();
1386
+ const state = await deriveState(base);
1387
+
1388
+ assert.equal(state.phase, "planning",
1389
+ "PLAN with unparseable tasks should stay in planning");
1390
+ });
1391
+ });
1392
+
1393
+ describe("Failure at executing: task plan file is empty", () => {
1394
+ test("T01-PLAN.md exists but is 0-byte → still enters executing", async () => {
1395
+ const base = createFixtureBase();
1396
+ writeRoadmap(base, "M001", standardRoadmap());
1397
+ const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
1398
+ const tasksDir = join(dir, "tasks");
1399
+ mkdirSync(tasksDir, { recursive: true });
1400
+ writeFileSync(join(dir, "S01-PLAN.md"), standardPlan());
1401
+ // Create task plan files but make them 0-byte
1402
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "");
1403
+ writeFileSync(join(tasksDir, "T02-PLAN.md"), "");
1404
+ invalidateStateCache();
1405
+ clearPathCache();
1406
+ const state = await deriveState(base);
1407
+
1408
+ // Task plan file existence check at line 718-730 uses readdirSync
1409
+ // to count .md files. 0-byte files still count.
1410
+ assert.equal(state.phase, "executing",
1411
+ "0-byte task plan files still pass the existence check");
1412
+ });
1413
+ });
1414
+
1415
+ describe("Failure at executing: DB has task but wrong status string", () => {
1416
+ test("task with unexpected status string → not treated as closed", async () => {
1417
+ const base = createFixtureBase();
1418
+ const dbPath = join(base, ".gsd", "gsd.db");
1419
+ openDatabase(dbPath);
1420
+
1421
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
1422
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
1423
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
1424
+
1425
+ // Set a garbage status that isn't "complete" or "done"
1426
+ updateTaskStatus("M001", "S01", "T01", "finished");
1427
+
1428
+ writeRoadmap(base, "M001", standardRoadmap());
1429
+ writePlan(base, "M001", "S01", standardPlan());
1430
+
1431
+ invalidateStateCache();
1432
+ const state = await deriveStateFromDb(base);
1433
+
1434
+ // isClosedStatus("finished") → false → task treated as active
1435
+ assert.equal(state.phase, "executing");
1436
+ assert.equal(state.activeTask?.id, "T01",
1437
+ "non-standard status 'finished' is NOT treated as closed");
1438
+ });
1439
+ });
1440
+
1441
+ describe("Failure at summarizing: slice SUMMARY write fails (file missing)", () => {
1442
+ test("all tasks [x] but no slice SUMMARY → stays in summarizing", async () => {
1443
+ const base = createFixtureBase();
1444
+ writeRoadmap(base, "M001", standardRoadmap());
1445
+ writePlan(base, "M001", "S01", allDonePlan());
1446
+ // All tasks done but no S01-SUMMARY.md written
1447
+ invalidateStateCache();
1448
+ clearPathCache();
1449
+ const state = await deriveState(base);
1450
+
1451
+ assert.equal(state.phase, "summarizing");
1452
+ // Next derivation still returns summarizing — no infinite loop
1453
+ invalidateStateCache();
1454
+ const state2 = await deriveState(base);
1455
+ assert.equal(state2.phase, "summarizing", "stays in summarizing until SUMMARY written");
1456
+ });
1457
+ });
1458
+
1459
+ describe("Failure at validating-milestone: VALIDATION write crashes", () => {
1460
+ test("all slices done, validation never written → stuck in validating", async () => {
1461
+ const base = createFixtureBase();
1462
+ writeRoadmap(base, "M001", doneSliceRoadmap());
1463
+ // No VALIDATION file at all
1464
+ invalidateStateCache();
1465
+ clearPathCache();
1466
+ const state = await deriveState(base);
1467
+ assert.equal(state.phase, "validating-milestone");
1468
+
1469
+ // Call again — still validating (idempotent, not looping)
1470
+ invalidateStateCache();
1471
+ const state2 = await deriveState(base);
1472
+ assert.equal(state2.phase, "validating-milestone",
1473
+ "stays in validating until VALIDATION file appears");
1474
+ });
1475
+ });
1476
+
1477
+ describe("Failure at completing-milestone: SUMMARY write fails", () => {
1478
+ test("validation terminal but SUMMARY never written → stuck in completing", async () => {
1479
+ const base = createFixtureBase();
1480
+ writeRoadmap(base, "M001", doneSliceRoadmap());
1481
+ writeMilestoneValidation(base, "M001", "pass");
1482
+ // No milestone SUMMARY
1483
+ invalidateStateCache();
1484
+ clearPathCache();
1485
+ const state = await deriveState(base);
1486
+ assert.equal(state.phase, "completing-milestone");
1487
+
1488
+ // Repeated calls stay in completing
1489
+ invalidateStateCache();
1490
+ const state2 = await deriveState(base);
1491
+ assert.equal(state2.phase, "completing-milestone",
1492
+ "stays in completing until SUMMARY written");
1493
+ });
1494
+ });
1495
+
1496
+ describe("Failure at replanning: REPLAN.md never written (loop risk)", () => {
1497
+ test("blocker detected, replan dispatched but REPLAN.md not created → re-enters replanning", async () => {
1498
+ const base = createFixtureBase();
1499
+ writeRoadmap(base, "M001", standardRoadmap());
1500
+ writePlan(base, "M001", "S01", partialDonePlan());
1501
+ writeTaskSummaryWithBlocker(base, "M001", "S01", "T01");
1502
+ // No REPLAN.md — simulates failed replan execution
1503
+
1504
+ invalidateStateCache();
1505
+ clearPathCache();
1506
+ const state1 = await deriveState(base);
1507
+ assert.equal(state1.phase, "replanning-slice");
1508
+
1509
+ // Call again — same result, stuck in replanning until REPLAN.md appears
1510
+ invalidateStateCache();
1511
+ const state2 = await deriveState(base);
1512
+ assert.equal(state2.phase, "replanning-slice",
1513
+ "without REPLAN.md, state stays in replanning (dispatch will retry)");
1514
+ });
1515
+ });
1516
+
1517
+ describe("Failure at complete: SUMMARY exists but VALIDATION missing", () => {
1518
+ test("milestone SUMMARY without VALIDATION → still complete (SUMMARY is terminal artifact)", async () => {
1519
+ const base = createFixtureBase();
1520
+ writeRoadmap(base, "M001", doneSliceRoadmap());
1521
+ // SUMMARY exists but NO VALIDATION
1522
+ writeMilestoneSummary(base, "M001");
1523
+ invalidateStateCache();
1524
+ clearPathCache();
1525
+ const state = await deriveState(base);
1526
+
1527
+ // Per #864: SUMMARY is the terminal artifact, validation optional
1528
+ assert.equal(state.phase, "complete",
1529
+ "SUMMARY alone should mark milestone complete per #864");
1530
+ });
1531
+ });
1532
+
1533
+ describe("Failure at blocked: dependency milestone partially complete", () => {
1534
+ test("M001 has slices done but no SUMMARY → M002 (depends on M001) is blocked", async () => {
1535
+ const base = createFixtureBase();
1536
+ // M001: all slices done but no SUMMARY/VALIDATION
1537
+ writeRoadmap(base, "M001", doneSliceRoadmap());
1538
+ // M001 has no SUMMARY → it's in validating/completing, NOT complete
1539
+
1540
+ // M002: depends on M001
1541
+ writeContext(base, "M002", [
1542
+ "---",
1543
+ "depends_on:",
1544
+ " - M001",
1545
+ "---",
1546
+ "",
1547
+ "# M002: Dependent",
1548
+ ].join("\n"));
1549
+ writeRoadmap(base, "M002", [
1550
+ "# M002: Dependent",
1551
+ "",
1552
+ "**Vision:** Test.",
1553
+ "",
1554
+ "## Slices",
1555
+ "",
1556
+ "- [ ] **S01: Slice** `risk:low` `depends:[]`",
1557
+ " > After this: done.",
1558
+ ].join("\n"));
1559
+
1560
+ invalidateStateCache();
1561
+ clearPathCache();
1562
+ const state = await deriveState(base);
1563
+
1564
+ // M001 is active (not yet complete), M002 should wait
1565
+ assert.equal(state.activeMilestone?.id, "M001",
1566
+ "M001 should be active (not complete without SUMMARY)");
1567
+ assert.notEqual(state.activeMilestone?.id, "M002",
1568
+ "M002 should not be active while M001 is incomplete");
1569
+ });
1570
+ });
1571
+
1572
+ describe("Failure: multiple reconciliation in single derivation", () => {
1573
+ test("DB has 3 stale tasks, all with SUMMARY on disk → all reconciled in one pass", async () => {
1574
+ const base = createFixtureBase();
1575
+ const dbPath = join(base, ".gsd", "gsd.db");
1576
+ openDatabase(dbPath);
1577
+
1578
+ insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
1579
+ insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
1580
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01", status: "pending" });
1581
+ insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02", status: "in-progress" });
1582
+ insertTask({ id: "T03", sliceId: "S01", milestoneId: "M001", title: "T03", status: "pending" });
1583
+
1584
+ const threeTaskRoadmap = [
1585
+ "# M001: Test",
1586
+ "",
1587
+ "**Vision:** Test.",
1588
+ "",
1589
+ "## Slices",
1590
+ "",
1591
+ "- [ ] **S01: Slice** `risk:low` `depends:[]`",
1592
+ " > After this: done.",
1593
+ ].join("\n");
1594
+ writeRoadmap(base, "M001", threeTaskRoadmap);
1595
+
1596
+ const threeTaskPlan = [
1597
+ "# S01: Slice",
1598
+ "",
1599
+ "**Goal:** Test.",
1600
+ "**Demo:** Tests pass.",
1601
+ "",
1602
+ "## Tasks",
1603
+ "",
1604
+ "- [ ] **T01: First** `est:10m`",
1605
+ " First.",
1606
+ "",
1607
+ "- [ ] **T02: Second** `est:10m`",
1608
+ " Second.",
1609
+ "",
1610
+ "- [ ] **T03: Third** `est:10m`",
1611
+ " Third.",
1612
+ ].join("\n");
1613
+ writePlan(base, "M001", "S01", threeTaskPlan);
1614
+
1615
+ // All 3 tasks have SUMMARY on disk
1616
+ writeTaskSummary(base, "M001", "S01", "T01");
1617
+ writeTaskSummary(base, "M001", "S01", "T02");
1618
+ writeTaskSummary(base, "M001", "S01", "T03");
1619
+
1620
+ invalidateStateCache();
1621
+ const state = await deriveStateFromDb(base);
1622
+
1623
+ // All 3 should be reconciled in one pass → summarizing
1624
+ assert.equal(state.phase, "summarizing",
1625
+ "all 3 stale tasks should be reconciled to complete in one derivation");
1626
+ });
1627
+ });
1628
+ });