gsd-pi 2.66.1-dev.ed243f2 → 2.67.0-dev.43b0159

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 (287) hide show
  1. package/dist/claude-cli-check.d.ts +8 -0
  2. package/dist/claude-cli-check.js +36 -0
  3. package/dist/cli.js +40 -0
  4. package/dist/onboarding.js +19 -2
  5. package/dist/resources/extensions/ask-user-questions.js +79 -11
  6. package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
  7. package/dist/resources/extensions/claude-code-cli/readiness.js +63 -12
  8. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
  9. package/dist/resources/extensions/gsd/auto/loop.js +13 -1
  10. package/dist/resources/extensions/gsd/auto/phases.js +22 -3
  11. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
  12. package/dist/resources/extensions/gsd/auto/session.js +1 -1
  13. package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
  14. package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
  15. package/dist/resources/extensions/gsd/auto-model-selection.js +12 -3
  16. package/dist/resources/extensions/gsd/auto-prompts.js +173 -25
  17. package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
  18. package/dist/resources/extensions/gsd/auto.js +13 -1
  19. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +32 -1
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
  21. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
  23. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
  24. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
  25. package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
  26. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  27. package/dist/resources/extensions/gsd/commands/dispatcher.js +1 -1
  28. package/dist/resources/extensions/gsd/commands/handlers/core.js +94 -4
  29. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +49 -9
  30. package/dist/resources/extensions/gsd/context-store.js +134 -2
  31. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
  32. package/dist/resources/extensions/gsd/detection.js +6 -0
  33. package/dist/resources/extensions/gsd/files.js +19 -2
  34. package/dist/resources/extensions/gsd/guided-flow.js +12 -8
  35. package/dist/resources/extensions/gsd/index.js +1 -1
  36. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
  37. package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
  38. package/dist/resources/extensions/gsd/preferences.js +6 -1
  39. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  41. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  42. package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
  43. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  44. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  45. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  46. package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
  47. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  48. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  49. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +11 -9
  50. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  51. package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
  52. package/dist/resources/extensions/gsd/state.js +2 -1
  53. package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
  54. package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
  55. package/dist/resources/extensions/remote-questions/manager.js +8 -0
  56. package/dist/resources/extensions/shared/interview-ui.js +10 -0
  57. package/dist/web/standalone/.next/BUILD_ID +1 -1
  58. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  59. package/dist/web/standalone/.next/build-manifest.json +3 -3
  60. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  61. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  63. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/index.html +1 -1
  80. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  87. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  90. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  91. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  92. package/dist/web/standalone/.next/static/chunks/{6502.8874bcae249c02e1.js → 6502.b804e48b7919f55e.js} +3 -3
  93. package/dist/web/standalone/.next/static/chunks/{webpack-9fed74684e1c5bb1.js → webpack-65f0501b197d1c49.js} +1 -1
  94. package/package.json +1 -1
  95. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  96. package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
  97. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  98. package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
  99. package/packages/pi-ai/dist/utils/json-parse.js +11 -1
  100. package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
  101. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  102. package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
  103. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  104. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
  105. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
  106. package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
  107. package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
  108. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
  109. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  110. package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
  111. package/packages/pi-ai/src/utils/json-parse.ts +11 -1
  112. package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
  113. package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
  114. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
  115. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -0
  116. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -0
  118. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +16 -0
  120. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/retry-handler.js +58 -1
  122. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +58 -0
  124. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/sdk.d.ts +3 -0
  126. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/sdk.js +1 -0
  128. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  148. package/packages/pi-coding-agent/package.json +1 -1
  149. package/packages/pi-coding-agent/src/core/agent-session.ts +4 -0
  150. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +69 -0
  151. package/packages/pi-coding-agent/src/core/retry-handler.ts +66 -1
  152. package/packages/pi-coding-agent/src/core/sdk.ts +5 -0
  153. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
  154. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
  155. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
  156. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
  157. package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
  158. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
  159. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
  160. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  161. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
  162. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
  163. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
  164. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
  165. package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
  166. package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
  167. package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
  168. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
  169. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  170. package/packages/pi-tui/dist/autocomplete.js +9 -7
  171. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  172. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
  173. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
  174. package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
  175. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
  176. package/packages/pi-tui/dist/components/editor.d.ts +3 -1
  177. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  178. package/packages/pi-tui/dist/components/editor.js +14 -3
  179. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  180. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  181. package/packages/pi-tui/dist/stdin-buffer.js +6 -0
  182. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  183. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  184. package/packages/pi-tui/dist/tui.js +8 -0
  185. package/packages/pi-tui/dist/tui.js.map +1 -1
  186. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
  187. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
  188. package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
  189. package/packages/pi-tui/src/autocomplete.ts +9 -7
  190. package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
  191. package/packages/pi-tui/src/components/editor.ts +14 -3
  192. package/packages/pi-tui/src/stdin-buffer.ts +7 -0
  193. package/packages/pi-tui/src/tui.ts +9 -0
  194. package/pkg/package.json +1 -1
  195. package/src/resources/extensions/ask-user-questions.ts +103 -11
  196. package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
  197. package/src/resources/extensions/claude-code-cli/readiness.ts +67 -12
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
  199. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
  200. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
  201. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
  202. package/src/resources/extensions/gsd/auto/loop.ts +14 -1
  203. package/src/resources/extensions/gsd/auto/phases.ts +27 -4
  204. package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
  205. package/src/resources/extensions/gsd/auto/session.ts +1 -1
  206. package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
  207. package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
  208. package/src/resources/extensions/gsd/auto-model-selection.ts +12 -3
  209. package/src/resources/extensions/gsd/auto-prompts.ts +195 -25
  210. package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
  211. package/src/resources/extensions/gsd/auto.ts +12 -1
  212. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
  213. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
  214. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -0
  215. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
  216. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
  217. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
  218. package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
  219. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  220. package/src/resources/extensions/gsd/commands/dispatcher.ts +1 -2
  221. package/src/resources/extensions/gsd/commands/handlers/core.ts +113 -8
  222. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +49 -11
  223. package/src/resources/extensions/gsd/context-store.ts +167 -2
  224. package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
  225. package/src/resources/extensions/gsd/detection.ts +6 -0
  226. package/src/resources/extensions/gsd/files.ts +21 -2
  227. package/src/resources/extensions/gsd/guided-flow.ts +15 -8
  228. package/src/resources/extensions/gsd/index.ts +6 -0
  229. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
  230. package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
  231. package/src/resources/extensions/gsd/preferences.ts +6 -1
  232. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  233. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  234. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  235. package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
  236. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  237. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  238. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  239. package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
  240. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  241. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  242. package/src/resources/extensions/gsd/prompts/validate-milestone.md +11 -9
  243. package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  244. package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
  245. package/src/resources/extensions/gsd/state.ts +2 -1
  246. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
  247. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
  248. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +21 -7
  249. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
  250. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  251. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +12 -0
  252. package/src/resources/extensions/gsd/tests/context-store.test.ts +176 -0
  253. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +76 -0
  254. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
  255. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
  256. package/src/resources/extensions/gsd/tests/decision-scope-cascade.test.ts +370 -0
  257. package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
  258. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
  259. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
  260. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
  261. package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
  262. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
  263. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
  264. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +3 -3
  265. package/src/resources/extensions/gsd/tests/measurement.test.ts +531 -0
  266. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
  267. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
  268. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
  269. package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
  270. package/src/resources/extensions/gsd/tests/preferences.test.ts +37 -0
  271. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +26 -4
  272. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +60 -0
  273. package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
  274. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
  275. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
  276. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
  277. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
  278. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
  279. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
  280. package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
  281. package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
  282. package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
  283. package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
  284. package/src/resources/extensions/remote-questions/manager.ts +9 -0
  285. package/src/resources/extensions/shared/interview-ui.ts +13 -0
  286. /package/dist/web/standalone/.next/static/{HAq0VE4k68rhRvJbQL1VW → CrKrzIIxk55witDF1eS0L}/_buildManifest.js +0 -0
  287. /package/dist/web/standalone/.next/static/{HAq0VE4k68rhRvJbQL1VW → CrKrzIIxk55witDF1eS0L}/_ssgManifest.js +0 -0
@@ -14,7 +14,7 @@ This is remediation round {{remediationRound}}. If this is round 0, this is the
14
14
 
15
15
  ## Context
16
16
 
17
- All relevant context has been preloaded below — the roadmap, all slice summaries, UAT results, requirements, decisions, and project context are inlined. Start working immediately without re-reading these files.
17
+ All relevant context has been preloaded below — the roadmap, all slice summaries, assessment results, requirements, decisions, and project context are inlined. Start working immediately without re-reading these files.
18
18
 
19
19
  {{inlinedContext}}
20
20
 
@@ -30,8 +30,8 @@ Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directo
30
30
  **Reviewer B — Cross-Slice Integration**
31
31
  Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Read `{{roadmapPath}}` and find the boundary map (produces/consumes contracts). For each boundary, check that the producing slice's SUMMARY confirms it produced the artifact, and the consuming slice's SUMMARY confirms it consumed it. Output a markdown table: Boundary | Producer Summary | Consumer Summary | Status. End with a one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
32
32
 
33
- **Reviewer C — UAT & Acceptance Criteria**
34
- Prompt: "Review milestone {{milestoneId}} UAT and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/{{milestoneId}}/CONTEXT.md` for acceptance criteria. Check for UAT-RESULT files in each slice directory. Verify each acceptance criterion maps to either a passing UAT result or clear SUMMARY evidence. Output a checklist: [ ] Criterion | Evidence. End with a one-line verdict: PASS if all criteria met, NEEDS-ATTENTION if gaps exist."
33
+ **Reviewer C — Assessment & Acceptance Criteria**
34
+ Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/{{milestoneId}}/CONTEXT.md` for acceptance criteria. Check for ASSESSMENT files in each slice directory. Verify each acceptance criterion maps to either a passing assessment result or clear SUMMARY evidence. Output a checklist: [ ] Criterion | Evidence. End with a one-line verdict: PASS if all criteria met, NEEDS-ATTENTION if gaps exist."
35
35
 
36
36
  ### Step 2 — Synthesize Findings
37
37
 
@@ -40,9 +40,9 @@ After all reviewers complete, aggregate their verdicts:
40
40
  - If any reviewer says NEEDS-ATTENTION → overall verdict: `needs-attention`
41
41
  - If any reviewer says FAIL → overall verdict: `needs-remediation`
42
42
 
43
- ### Step 3 — Write VALIDATION File
43
+ ### Step 3 — Persist Validation
44
44
 
45
- Write to `{{validationPath}}`:
45
+ Prepare the validation content you will pass to `gsd_validate_milestone`. Do **not** manually write `{{validationPath}}` — the DB-backed tool is the canonical write path and renders the validation file for you.
46
46
 
47
47
  ```markdown
48
48
  ---
@@ -59,7 +59,7 @@ reviewers: 3
59
59
  ## Reviewer B — Cross-Slice Integration
60
60
  <paste Reviewer B output>
61
61
 
62
- ## Reviewer C — UAT & Acceptance Criteria
62
+ ## Reviewer C — Assessment & Acceptance Criteria
63
63
  <paste Reviewer C output>
64
64
 
65
65
  ## Synthesis
@@ -69,13 +69,15 @@ reviewers: 3
69
69
  <if verdict is not pass: specific actions required>
70
70
  ```
71
71
 
72
+ Call `gsd_validate_milestone` with the camelCase fields `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verdictRationale`, and `remediationPlan` when needed. If you include verification-class analysis, pass it in `verificationClasses`.
73
+
72
74
  **DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — the engine owns the WAL connection. Use `gsd_milestone_status` to read milestone and slice state. All data you need is already inlined in the context above or accessible via the `gsd_*` tools. Direct DB access corrupts the WAL and bypasses tool-level validation.
73
75
 
74
76
  If verdict is `needs-remediation`:
75
- - Add new slices to `{{roadmapPath}}` with unchecked `[ ]` status
76
- - These slices will be planned and executed before validation re-runs
77
+ - Use `gsd_reassess_roadmap` to add the remediation slices instead of editing `{{roadmapPath}}` manually
78
+ - Those slices will be planned and executed before validation re-runs
77
79
 
78
- **You MUST write `{{validationPath}}` before finishing.**
80
+ **You MUST call `gsd_validate_milestone` before finishing. Do not manually write `{{validationPath}}`.**
79
81
 
80
82
  **File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
81
83
 
@@ -90,9 +90,11 @@ Present a merge plan to the user:
90
90
 
91
91
  Ask the user to confirm the merge plan before proceeding.
92
92
 
93
+ **CRITICAL — Non-bypassable gate:** Do NOT execute any merge commands until the user explicitly approves the merge plan. If `ask_user_questions` fails, errors, returns no response, or the user's response is ambiguous, you MUST re-ask — never rationalize past the block. "No response, I'll proceed with the clean merges," "the plan looks safe, merging," or any other self-authorization is **forbidden**. The gate exists to protect the user's branches; treat a block as an instruction to wait, not an obstacle to work around.
94
+
93
95
  ### Step 4: Execute Merge
94
96
 
95
- Once confirmed, run all commands from `{{mainTreePath}}` (your CWD):
97
+ Once the user has explicitly confirmed, run all commands from `{{mainTreePath}}` (your CWD):
96
98
 
97
99
  1. Ensure you are on the target branch: `git checkout {{mainBranch}}`
98
100
  2. If there are conflicts requiring manual reconciliation, apply the reconciled versions first
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import { execFileSync } from "node:child_process";
13
+ import { normalizePlannedFileReference } from "../files.js";
13
14
  import { logWarning } from "../workflow-logger.js";
14
15
 
15
16
  // ─── Types ──────────────────────────────────────────────────────────────────
@@ -57,7 +58,9 @@ export function validateFileChanges(
57
58
 
58
59
  // Normalize expected paths (strip leading ./ or /)
59
60
  const normalizedExpected = new Set(
60
- [...allExpected].map(f => f.replace(/^\.\//, "").replace(/^\//, "")),
61
+ [...allExpected].map((f) =>
62
+ normalizePlannedFileReference(f).replace(/^\.\//, "").replace(/^\//, ""),
63
+ ),
61
64
  );
62
65
 
63
66
  // Compute symmetric difference
@@ -1317,7 +1317,8 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
1317
1317
  ? `All milestones complete. ${activeReqs} active requirement${activeReqs === 1 ? '' : 's'} in REQUIREMENTS.md ${activeReqs === 1 ? 'has' : 'have'} not been mapped to a milestone.`
1318
1318
  : 'All milestones complete.';
1319
1319
  return {
1320
- activeMilestone: lastEntry ? { id: lastEntry.id, title: lastEntry.title } : null,
1320
+ activeMilestone: null,
1321
+ lastCompletedMilestone: lastEntry ? { id: lastEntry.id, title: lastEntry.title } : null,
1321
1322
  activeSlice: null,
1322
1323
  activeTask: null,
1323
1324
  phase: 'complete',
@@ -1,7 +1,8 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
3
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
5
6
 
6
7
  import {
7
8
  unitVerb,
@@ -11,11 +12,29 @@ import {
11
12
  formatWidgetTokens,
12
13
  estimateTimeRemaining,
13
14
  extractUatSliceId,
15
+ getWidgetMode,
16
+ cycleWidgetMode,
17
+ _resetWidgetModeForTests,
14
18
  } from "../auto-dashboard.ts";
15
19
 
16
20
  const autoSource = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "auto.ts"), "utf-8");
17
21
  const dashboardSource = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "auto-dashboard.ts"), "utf-8");
18
22
 
23
+ function makeTempDir(prefix: string): string {
24
+ return join(
25
+ tmpdir(),
26
+ `gsd-auto-dashboard-test-${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
27
+ );
28
+ }
29
+
30
+ function cleanup(dir: string): void {
31
+ try {
32
+ rmSync(dir, { recursive: true, force: true });
33
+ } catch {
34
+ // best-effort
35
+ }
36
+ }
37
+
19
38
  // ─── unitVerb ─────────────────────────────────────────────────────────────
20
39
 
21
40
  test("unitVerb maps known unit types to verbs", () => {
@@ -209,3 +228,35 @@ test("extractUatSliceId returns null for invalid formats", () => {
209
228
  assert.equal(extractUatSliceId(""), null);
210
229
  assert.equal(extractUatSliceId("M001/T01"), null);
211
230
  });
231
+
232
+ test("widget mode respects project preference precedence and persists there", (t) => {
233
+ const homeDir = makeTempDir("home");
234
+ const projectDir = makeTempDir("project");
235
+ const globalPrefsPath = join(homeDir, ".gsd", "preferences.md");
236
+ const projectPrefsPath = join(projectDir, ".gsd", "preferences.md");
237
+
238
+ mkdirSync(join(homeDir, ".gsd"), { recursive: true });
239
+ mkdirSync(join(projectDir, ".gsd"), { recursive: true });
240
+ writeFileSync(globalPrefsPath, "---\nversion: 1\nwidget_mode: off\n---\n", "utf-8");
241
+ writeFileSync(projectPrefsPath, "---\nversion: 1\nwidget_mode: small\n---\n", "utf-8");
242
+
243
+ t.after(() => {
244
+ cleanup(homeDir);
245
+ cleanup(projectDir);
246
+ _resetWidgetModeForTests();
247
+ });
248
+
249
+ _resetWidgetModeForTests();
250
+
251
+ assert.equal(getWidgetMode(projectPrefsPath, globalPrefsPath), "small", "project widget_mode overrides global");
252
+ assert.equal(
253
+ cycleWidgetMode(projectPrefsPath, globalPrefsPath),
254
+ "min",
255
+ "cycling advances from the project-owned mode",
256
+ );
257
+
258
+ const projectPrefs = readFileSync(projectPrefsPath, "utf-8");
259
+ const globalPrefs = readFileSync(globalPrefsPath, "utf-8");
260
+ assert.match(projectPrefs, /widget_mode:\s*min/);
261
+ assert.match(globalPrefs, /widget_mode:\s*off/);
262
+ });
@@ -1,4 +1,4 @@
1
- import test from "node:test";
1
+ import test, { mock } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
@@ -191,6 +191,54 @@ test("runUnit returns cancelled when session creation times out", async () => {
191
191
  assert.equal(pi.calls.length, 0);
192
192
  });
193
193
 
194
+ test("runUnit keeps the session-switch guard across a late newSession settlement", async () => {
195
+ _resetPendingResolve();
196
+ mock.timers.enable();
197
+
198
+ try {
199
+ const ctx = makeMockCtx();
200
+ const pi = makeMockPi();
201
+ // Use delays longer than NEW_SESSION_TIMEOUT_MS (120s) so the timeout fires
202
+ const firstSession = makeMockSession({ newSessionDelayMs: 200_000 });
203
+ const secondSession = makeMockSession({ newSessionDelayMs: 200_000 });
204
+
205
+ const firstRun = runUnit(ctx, pi, firstSession, "task", "T01", "prompt");
206
+
207
+ // Tick past the 120s session timeout
208
+ mock.timers.tick(121_000);
209
+ await Promise.resolve();
210
+
211
+ const firstResult = await firstRun;
212
+ assert.equal(firstResult.status, "cancelled");
213
+ assert.equal(isSessionSwitchInFlight(), true, "guard should remain set after the timed-out session");
214
+
215
+ mock.timers.tick(1);
216
+ const secondRun = runUnit(ctx, pi, secondSession, "task", "T02", "prompt");
217
+
218
+ mock.timers.tick(100_000);
219
+ await Promise.resolve();
220
+ assert.equal(
221
+ isSessionSwitchInFlight(),
222
+ true,
223
+ "late settlement from the first session must not clear the newer session guard",
224
+ );
225
+
226
+ // Tick past the second session's timeout (121s total > 120s NEW_SESSION_TIMEOUT_MS)
227
+ mock.timers.tick(21_001);
228
+ await Promise.resolve();
229
+
230
+ const secondResult = await secondRun;
231
+ assert.equal(secondResult.status, "cancelled");
232
+
233
+ // Tick past the second session's delayed promise (200s) so .finally() fires
234
+ mock.timers.tick(80_000);
235
+ await Promise.resolve();
236
+ assert.equal(isSessionSwitchInFlight(), false, "guard should clear after the newer session settles");
237
+ } finally {
238
+ mock.timers.reset();
239
+ }
240
+ });
241
+
194
242
  test("runUnit returns cancelled when s.active is false before sendMessage", async () => {
195
243
  _resetPendingResolve();
196
244
 
@@ -412,7 +460,7 @@ function makeMockDeps(
412
460
  getCurrentBranch: () => "main",
413
461
  autoWorktreeBranch: () => "auto/M001",
414
462
  resolveMilestoneFile: () => null,
415
- reconcileMergeState: () => false,
463
+ reconcileMergeState: () => "clean",
416
464
  getLedger: () => null,
417
465
  getProjectTotals: () => ({ cost: 0 }),
418
466
  formatCost: (c: number) => `$${c.toFixed(2)}`,
@@ -143,17 +143,17 @@ test("resolvePreferredModelConfig keeps explicit phase models as the ceiling", (
143
143
 
144
144
  // ─── resolveModelId tests ─────────────────────────────────────────────────
145
145
 
146
- test("resolveModelId: bare ID resolves to anthropic over claude-code when session is claude-code (#2905)", () => {
146
+ test("resolveModelId: bare ID resolves to claude-code when session is claude-code (#3772)", () => {
147
147
  const availableModels = [
148
148
  { id: "claude-sonnet-4-6", provider: "anthropic" },
149
149
  { id: "claude-sonnet-4-6", provider: "claude-code" },
150
150
  ];
151
151
 
152
- // Bug: when currentProvider is "claude-code", bare ID "claude-sonnet-4-6"
153
- // resolves to claude-code/claude-sonnet-4-6 instead of anthropic/claude-sonnet-4-6
152
+ // When currentProvider is "claude-code" (set by startup migration for subscription
153
+ // users), bare IDs must resolve to claude-code to avoid the third-party block (#3772).
154
154
  const result = resolveModelId("claude-sonnet-4-6", availableModels, "claude-code");
155
155
  assert.ok(result, "should resolve a model");
156
- assert.equal(result.provider, "anthropic", "bare ID must resolve to anthropic, not claude-code");
156
+ assert.equal(result.provider, "claude-code", "bare ID must resolve to claude-code when session provider is claude-code");
157
157
  });
158
158
 
159
159
  test("resolveModelId: bare ID still prefers current provider when it is a first-class API provider", () => {
@@ -227,14 +227,28 @@ test("model change notify in selectAndApplyModel is gated behind verbose flag",
227
227
  );
228
228
  });
229
229
 
230
- test("resolveModelId: anthropic wins over claude-code regardless of list order", () => {
230
+ test("resolveModelId: anthropic wins over claude-code when session provider is not claude-code", () => {
231
231
  const availableModels = [
232
232
  { id: "claude-sonnet-4-6", provider: "claude-code" },
233
233
  { id: "claude-sonnet-4-6", provider: "anthropic" },
234
234
  ];
235
235
 
236
- // Even when claude-code appears first in the list, anthropic should win
236
+ // When the session is NOT on claude-code, bare IDs should resolve to
237
+ // the canonical anthropic provider (original #2905 behavior preserved).
238
+ const result = resolveModelId("claude-sonnet-4-6", availableModels, undefined);
239
+ assert.ok(result, "should resolve a model");
240
+ assert.equal(result.provider, "anthropic", "anthropic must win when session is not claude-code");
241
+ });
242
+
243
+ test("resolveModelId: claude-code wins when session is claude-code regardless of list order", () => {
244
+ const availableModels = [
245
+ { id: "claude-sonnet-4-6", provider: "claude-code" },
246
+ { id: "claude-sonnet-4-6", provider: "anthropic" },
247
+ ];
248
+
249
+ // When session provider is claude-code (subscription user migration), it must
250
+ // win regardless of candidate ordering to avoid the third-party block (#3772).
237
251
  const result = resolveModelId("claude-sonnet-4-6", availableModels, "claude-code");
238
252
  assert.ok(result, "should resolve a model");
239
- assert.equal(result.provider, "anthropic", "anthropic must win over claude-code regardless of list order");
253
+ assert.equal(result.provider, "claude-code", "claude-code must win when it is the session provider");
240
254
  });
@@ -0,0 +1,48 @@
1
+ import test, { afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { verifyExpectedArtifact } from "../auto-recovery.ts";
8
+ import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow } from "../gsd-db.ts";
9
+
10
+ const tmpDirs: string[] = [];
11
+
12
+ function makeTmpProject(): string {
13
+ const dir = mkdtempSync(join(tmpdir(), "auto-recovery-"));
14
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
15
+ openDatabase(join(dir, ".gsd", "gsd.db"));
16
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
17
+ insertSlice({
18
+ milestoneId: "M001",
19
+ id: "S01",
20
+ title: "Test Slice",
21
+ status: "pending",
22
+ risk: "low",
23
+ depends: [],
24
+ });
25
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
26
+ tmpDirs.push(dir);
27
+ return dir;
28
+ }
29
+
30
+ afterEach(() => {
31
+ closeDatabase();
32
+ for (const dir of tmpDirs) {
33
+ try {
34
+ rmSync(dir, { recursive: true, force: true });
35
+ } catch {
36
+ // Best-effort cleanup only.
37
+ }
38
+ }
39
+ tmpDirs.length = 0;
40
+ });
41
+
42
+ test("verifyExpectedArtifact checks pending gate-evaluate artifacts without ESM require failures", () => {
43
+ const base = makeTmpProject();
44
+
45
+ const verified = verifyExpectedArtifact("gate-evaluate", "M001/S01/gates+Q3", base);
46
+
47
+ assert.equal(verified, false, "pending gates should keep gate-evaluate unverified");
48
+ });
@@ -138,6 +138,28 @@ test("generateCodebaseMap: excludes .gsd/ files", () => {
138
138
  }
139
139
  });
140
140
 
141
+ test("generateCodebaseMap: excludes .claude/ and other tool directories", () => {
142
+ const base = makeTmpRepo();
143
+ try {
144
+ addFile(base, "src/main.ts");
145
+ addFile(base, ".claude/CLAUDE.md");
146
+ addFile(base, ".claude/memory/user.md");
147
+ addFile(base, ".plans/plan.md");
148
+ addFile(base, ".cursor/settings.json");
149
+ addFile(base, ".vscode/settings.json");
150
+
151
+ const result = generateCodebaseMap(base);
152
+ assert.ok(result.content.includes("`src/main.ts`"), "should include src/main.ts");
153
+ assert.ok(!result.content.includes("CLAUDE.md"), "should exclude .claude/ files");
154
+ assert.ok(!result.content.includes("user.md"), "should exclude .claude/memory/ files");
155
+ assert.ok(!result.content.includes(".plans"), "should exclude .plans/ files");
156
+ assert.ok(!result.content.includes(".cursor"), "should exclude .cursor/ files");
157
+ assert.ok(!result.content.includes(".vscode"), "should exclude .vscode/ files");
158
+ } finally {
159
+ cleanup(base);
160
+ }
161
+ });
162
+
141
163
  test("generateCodebaseMap: excludes binary and lock files", () => {
142
164
  const base = makeTmpRepo();
143
165
  try {
@@ -100,6 +100,18 @@ steps: []
100
100
  // ─── Catalog Registration ────────────────────────────────────────────────
101
101
 
102
102
  describe("workflow catalog registration", () => {
103
+ it("model appears in TOP_LEVEL_SUBCOMMANDS", () => {
104
+ const entry = TOP_LEVEL_SUBCOMMANDS.find((c) => c.cmd === "model");
105
+ assert.ok(entry, "model should be in TOP_LEVEL_SUBCOMMANDS");
106
+ assert.match(entry!.desc, /session model/i);
107
+ });
108
+
109
+ it("getGsdArgumentCompletions('m') includes model", () => {
110
+ const completions = getGsdArgumentCompletions("m");
111
+ const labels = completions.map((c: any) => c.label);
112
+ assert.ok(labels.includes("model"), "should include model completion");
113
+ });
114
+
103
115
  it("workflow appears in TOP_LEVEL_SUBCOMMANDS", () => {
104
116
  const entry = TOP_LEVEL_SUBCOMMANDS.find((c) => c.cmd === "workflow");
105
117
  assert.ok(entry, "workflow should be in TOP_LEVEL_SUBCOMMANDS");
@@ -15,6 +15,8 @@ import {
15
15
  formatRequirementsForPrompt,
16
16
  queryArtifact,
17
17
  queryProject,
18
+ formatRoadmapExcerpt,
19
+ queryKnowledge,
18
20
  } from '../context-store.ts';
19
21
 
20
22
  // ═══════════════════════════════════════════════════════════════════════════
@@ -452,3 +454,177 @@ describe("context-store: queryProject", () => {
452
454
  assert.strictEqual(content, null, 'queryProject returns null when DB closed');
453
455
  });
454
456
  });
457
+
458
+ // ═══════════════════════════════════════════════════════════════════════════
459
+ // context-store: formatRoadmapExcerpt
460
+ // ═══════════════════════════════════════════════════════════════════════════
461
+
462
+ describe("context-store: formatRoadmapExcerpt", () => {
463
+ // Sample roadmap content matching actual M005-ROADMAP.md format
464
+ const sampleRoadmap = `# M005: Tiered Context Injection
465
+
466
+ ## Vision
467
+ Refactor prompt builders to inject relevance-scoped context.
468
+
469
+ ## Slice Overview
470
+ | ID | Slice | Risk | Depends | Done | After this |
471
+ |----|-------|------|---------|------|------------|
472
+ | S01 | Scope existing queries | low | — | ✅ | planSlice prompt scoped. |
473
+ | S02 | KNOWLEDGE scoping | medium | S01 | ⬜ | KNOWLEDGE sections filtered. |
474
+ | S03 | Measurement test | low | S02 | ⬜ | 40% reduction confirmed. |
475
+ `;
476
+
477
+ test("S02 with S01 predecessor includes both rows", () => {
478
+ const result = formatRoadmapExcerpt(sampleRoadmap, 'S02', '.gsd/milestones/M005/M005-ROADMAP.md');
479
+
480
+ // Should have header
481
+ assert.match(result, /\| ID \| Slice \| Risk \| Depends \| Done \| After this \|/, 'has header row');
482
+ // Should have separator
483
+ assert.match(result, /\|----\|/, 'has separator row');
484
+ // Should have S01 predecessor
485
+ assert.match(result, /\| S01 \|/, 'has predecessor S01 row');
486
+ // Should have S02 target
487
+ assert.match(result, /\| S02 \|/, 'has target S02 row');
488
+ // Should have reference directive
489
+ assert.match(result, /See full roadmap:.*M005-ROADMAP\.md/, 'has reference directive');
490
+ // Should NOT have S03 (not relevant)
491
+ assert.ok(!result.includes('| S03 |'), 'does not include unrelated S03');
492
+ });
493
+
494
+ test("S01 with no predecessor includes only target row", () => {
495
+ const result = formatRoadmapExcerpt(sampleRoadmap, 'S01');
496
+
497
+ // Should have header + separator + S01 only
498
+ assert.match(result, /\| ID \| Slice \|/, 'has header row');
499
+ assert.match(result, /\| S01 \|/, 'has target S01 row');
500
+ // Should NOT have S02 or S03
501
+ assert.ok(!result.includes('| S02 |'), 'does not include S02');
502
+ assert.ok(!result.includes('| S03 |'), 'does not include S03');
503
+ // Should have reference
504
+ assert.match(result, /See full roadmap:/, 'has reference directive');
505
+
506
+ // Count rows: header + separator + S01 + blank + directive = 5 lines
507
+ const lines = result.split('\n');
508
+ assert.strictEqual(lines.length, 5, 'correct number of lines (no predecessor)');
509
+ });
510
+
511
+ test("missing slice returns empty string", () => {
512
+ const result = formatRoadmapExcerpt(sampleRoadmap, 'S99');
513
+
514
+ assert.strictEqual(result, '', 'missing slice returns empty string');
515
+ });
516
+
517
+ test("empty input returns empty string", () => {
518
+ assert.strictEqual(formatRoadmapExcerpt('', 'S01'), '', 'empty content returns empty');
519
+ assert.strictEqual(formatRoadmapExcerpt(sampleRoadmap, ''), '', 'empty sliceId returns empty');
520
+ });
521
+
522
+ test("handles table with various column formats", () => {
523
+ // Table with different spacing and content
524
+ const variantRoadmap = `# Milestone
525
+
526
+ | ID | Slice | Risk | Depends | Done | After this |
527
+ |:---|:------|:-----|:--------|:-----|:-----------|
528
+ | S01 | First slice title | low | — | ✅ | First complete. |
529
+ | S02 | Second longer slice title here | medium | S01 | ⬜ | Second working. |
530
+ `;
531
+
532
+ const result = formatRoadmapExcerpt(variantRoadmap, 'S02');
533
+
534
+ assert.match(result, /\| S01 \|/, 'has predecessor with different spacing');
535
+ assert.match(result, /\| S02 \|/, 'has target with different spacing');
536
+ assert.match(result, /Second longer slice title/, 'preserves full slice title');
537
+ });
538
+
539
+ test("handles multiple dependencies by using first one", () => {
540
+ const multiDepRoadmap = `| ID | Slice | Risk | Depends | Done | After this |
541
+ |----|-------|------|---------|------|------------|
542
+ | S01 | First | low | — | ✅ | Done. |
543
+ | S02 | Second | low | — | ✅ | Done. |
544
+ | S03 | Third | medium | S01, S02 | ⬜ | Working. |
545
+ `;
546
+
547
+ const result = formatRoadmapExcerpt(multiDepRoadmap, 'S03');
548
+
549
+ // Should include S01 (first dependency) and S03
550
+ assert.match(result, /\| S01 \|/, 'has first dependency S01');
551
+ assert.match(result, /\| S03 \|/, 'has target S03');
552
+ // S02 is also a dependency but we only include the first one
553
+ // (This is intentional to keep excerpts minimal)
554
+ });
555
+ });
556
+
557
+ // ═══════════════════════════════════════════════════════════════════════════
558
+ // context-store: queryKnowledge
559
+ // ═══════════════════════════════════════════════════════════════════════════
560
+
561
+ describe("context-store: queryKnowledge", () => {
562
+ // Sample KNOWLEDGE.md content
563
+ const sampleKnowledge = `# Project Knowledge
564
+
565
+ ## Database Patterns
566
+ SQLite is used with WAL mode for concurrent reads.
567
+ Always use prepared statements.
568
+
569
+ More database details here.
570
+
571
+ ## API Design
572
+ REST endpoints follow OpenAPI spec.
573
+ Use versioned paths like /v1/resource.
574
+
575
+ ## Testing Guidelines
576
+ Unit tests use node:test.
577
+ Integration tests mock external services.
578
+ `;
579
+
580
+ test("single keyword matches header", async () => {
581
+ const result = await queryKnowledge(sampleKnowledge, ['database']);
582
+
583
+ assert.match(result, /## Database Patterns/, 'includes matching section header');
584
+ assert.match(result, /SQLite is used with WAL mode/, 'includes section content');
585
+ // Should NOT include other sections
586
+ assert.ok(!result.includes('## API Design'), 'does not include non-matching API section');
587
+ assert.ok(!result.includes('## Testing Guidelines'), 'does not include non-matching Testing section');
588
+ });
589
+
590
+ test("multiple keywords match multiple sections", async () => {
591
+ const result = await queryKnowledge(sampleKnowledge, ['database', 'testing']);
592
+
593
+ assert.match(result, /## Database Patterns/, 'includes Database section');
594
+ assert.match(result, /## Testing Guidelines/, 'includes Testing section');
595
+ assert.ok(!result.includes('## API Design'), 'does not include API section');
596
+ });
597
+
598
+ test("no matches returns empty string", async () => {
599
+ const result = await queryKnowledge(sampleKnowledge, ['nonexistent']);
600
+
601
+ assert.strictEqual(result, '', 'no matches returns empty string per D020');
602
+ });
603
+
604
+ test("keyword in first paragraph matches", async () => {
605
+ const result = await queryKnowledge(sampleKnowledge, ['sqlite']);
606
+
607
+ // 'sqlite' appears in first paragraph of Database Patterns
608
+ assert.match(result, /## Database Patterns/, 'matches keyword in first paragraph');
609
+ assert.match(result, /SQLite is used/, 'includes the section with matching paragraph');
610
+ });
611
+
612
+ test("case-insensitive matching", async () => {
613
+ const result = await queryKnowledge(sampleKnowledge, ['DATABASE', 'API']);
614
+
615
+ assert.match(result, /## Database Patterns/, 'case-insensitive header match');
616
+ assert.match(result, /## API Design/, 'case-insensitive header match for API');
617
+ });
618
+
619
+ test("empty keywords returns empty string", async () => {
620
+ const result = await queryKnowledge(sampleKnowledge, []);
621
+
622
+ assert.strictEqual(result, '', 'empty keywords returns empty string');
623
+ });
624
+
625
+ test("empty content returns empty string", async () => {
626
+ const result = await queryKnowledge('', ['database']);
627
+
628
+ assert.strictEqual(result, '', 'empty content returns empty string');
629
+ });
630
+ });