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
@@ -0,0 +1,76 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { handleCoreCommand } from "../commands/handlers/core.ts";
5
+
6
+ function makeCtx(customResult: unknown) {
7
+ const notices: Array<{ message: string; type?: string }> = [];
8
+ return {
9
+ hasUI: true,
10
+ ui: {
11
+ custom: async () => customResult,
12
+ notify: (message: string, type?: string) => {
13
+ notices.push({ message, type });
14
+ },
15
+ },
16
+ notices,
17
+ };
18
+ }
19
+
20
+ test("visualize only falls back when ctx.ui.custom() is unavailable", async () => {
21
+ const successCtx = makeCtx(true);
22
+ const success = await handleCoreCommand("visualize", successCtx as any);
23
+ assert.equal(success, true);
24
+ assert.equal(successCtx.notices.length, 0, "successful overlay close does not trigger fallback");
25
+
26
+ const fallbackCtx = makeCtx(undefined);
27
+ const fallback = await handleCoreCommand("visualize", fallbackCtx as any);
28
+ assert.equal(fallback, true);
29
+ assert.equal(fallbackCtx.notices.length, 1, "unavailable overlay triggers fallback warning");
30
+ assert.match(fallbackCtx.notices[0]!.message, /interactive terminal/i);
31
+ });
32
+
33
+ test("show-config only falls back when ctx.ui.custom() is unavailable", async () => {
34
+ const successCtx = makeCtx(true);
35
+ const success = await handleCoreCommand("show-config", successCtx as any);
36
+ assert.equal(success, true);
37
+ assert.equal(successCtx.notices.length, 0, "successful overlay close does not trigger fallback");
38
+
39
+ const fallbackCtx = makeCtx(undefined);
40
+ const fallback = await handleCoreCommand("show-config", fallbackCtx as any);
41
+ assert.equal(fallback, true);
42
+ assert.equal(fallbackCtx.notices.length, 1, "unavailable overlay triggers text fallback");
43
+ assert.match(fallbackCtx.notices[0]!.message, /GSD Configuration/);
44
+ });
45
+
46
+ test("model command resolves and persists exact provider-qualified selection", async () => {
47
+ const selectedModel = { provider: "openai", id: "gpt-5.4" };
48
+ let applied: typeof selectedModel | null = null;
49
+ const ctx = {
50
+ hasUI: true,
51
+ model: { provider: "anthropic", id: "claude-sonnet-4-6" },
52
+ modelRegistry: {
53
+ getAvailable: () => [
54
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
55
+ selectedModel,
56
+ ],
57
+ },
58
+ ui: {
59
+ notify: (message: string, type?: string) => {
60
+ notices.push({ message, type });
61
+ },
62
+ },
63
+ } as any;
64
+ const notices: Array<{ message: string; type?: string }> = [];
65
+ const pi = {
66
+ setModel: async (model: typeof selectedModel) => {
67
+ applied = model;
68
+ return true;
69
+ },
70
+ } as any;
71
+
72
+ const handled = await handleCoreCommand("model openai/gpt-5.4", ctx, pi);
73
+ assert.equal(handled, true);
74
+ assert.deepEqual(applied, selectedModel);
75
+ assert.match(notices[0]!.message, /openai\/gpt-5\.4/);
76
+ });
@@ -178,7 +178,7 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
178
178
  getCurrentBranch: () => "main",
179
179
  autoWorktreeBranch: () => "auto/M001",
180
180
  resolveMilestoneFile: () => null,
181
- reconcileMergeState: () => false,
181
+ reconcileMergeState: () => "clean",
182
182
  getLedger: () => null,
183
183
  getProjectTotals: () => ({ cost: 0 }),
184
184
  formatCost: (c: number) => `$${c.toFixed(2)}`,
@@ -311,6 +311,12 @@ describe("Custom engine loop integration", () => {
311
311
  `stopAuto reason should include "Workflow complete", got: ${stopEntry}`,
312
312
  );
313
313
 
314
+ assert.equal(
315
+ deps.callLog.filter((e: string) => e === "deriveState").length,
316
+ 3,
317
+ "custom engine should stop immediately after a milestone-complete reconcile",
318
+ );
319
+
314
320
  // Verify dev path was NOT used (resolveDispatch should not appear)
315
321
  assert.ok(
316
322
  !deps.callLog.includes("resolveDispatch"),
@@ -249,6 +249,37 @@ describe("CustomWorkflowEngine.reconcile", () => {
249
249
  const graph = readGraph(runDir);
250
250
  assert.equal(graph.steps[0].status, "complete");
251
251
  });
252
+
253
+ it("re-reads GRAPH.yaml before reconcile so concurrent edits are preserved", async () => {
254
+ const { engine, runDir } = setupEngine([
255
+ makeStep({ id: "step-1" }),
256
+ makeStep({ id: "step-2", dependsOn: ["step-1"] }),
257
+ ], "wf");
258
+
259
+ const staleState = await engine.deriveState("/unused");
260
+
261
+ // Simulate another process appending a new step after deriveState() ran.
262
+ writeGraph(runDir, makeGraph([
263
+ makeStep({ id: "step-1" }),
264
+ makeStep({ id: "step-2", dependsOn: ["step-1"] }),
265
+ makeStep({ id: "step-3", dependsOn: ["step-2"] }),
266
+ ], "wf"));
267
+
268
+ const result = await engine.reconcile(staleState, {
269
+ unitType: "custom-step",
270
+ unitId: "wf/step-1",
271
+ startedAt: Date.now() - 1000,
272
+ finishedAt: Date.now(),
273
+ });
274
+
275
+ assert.equal(result.outcome, "continue");
276
+
277
+ const graph = readGraph(runDir);
278
+ assert.equal(graph.steps.length, 3, "reconcile should preserve the concurrent graph edit");
279
+ assert.equal(graph.steps[0].status, "complete");
280
+ assert.equal(graph.steps[1].status, "pending");
281
+ assert.equal(graph.steps[2].status, "pending");
282
+ });
252
283
  });
253
284
 
254
285
  // ─── getDisplayMetadata ──────────────────────────────────────────────────
@@ -0,0 +1,370 @@
1
+ // decision-scope-cascade: Tests for R005 fallback cascade and scope derivation
2
+ //
3
+ // Validates:
4
+ // (a) inlineDecisionsFromDb cascade: milestone + scope → milestone only → null
5
+ // (b) deriveSliceScope extracts meaningful scope keywords from slice titles
6
+ // (c) deriveSliceScope returns undefined for generic titles
7
+
8
+ import { describe, test, afterEach, beforeEach } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import {
11
+ openDatabase,
12
+ closeDatabase,
13
+ isDbAvailable,
14
+ insertDecision,
15
+ } from '../gsd-db.ts';
16
+ import {
17
+ queryDecisions,
18
+ formatDecisionsForPrompt,
19
+ } from '../context-store.ts';
20
+ import { deriveSliceScope } from '../auto-prompts.ts';
21
+
22
+ // ═══════════════════════════════════════════════════════════════════════════
23
+ // deriveSliceScope: Extract meaningful scope from slice titles
24
+ // ═══════════════════════════════════════════════════════════════════════════
25
+
26
+ describe("deriveSliceScope: keyword extraction", () => {
27
+ test("extracts first meaningful noun from title", () => {
28
+ // "Auth Middleware & Protected Route" → "auth"
29
+ assert.strictEqual(
30
+ deriveSliceScope("Auth Middleware & Protected Route"),
31
+ "auth",
32
+ "extracts 'auth' from auth-related title",
33
+ );
34
+
35
+ // "Database & User Model Setup" → "database" (not "setup" which is generic)
36
+ const dbScope = deriveSliceScope("Database & User Model Setup");
37
+ assert.ok(
38
+ dbScope === "database" || dbScope === "user",
39
+ `expected 'database' or 'user', got '${dbScope}'`,
40
+ );
41
+
42
+ // "API Rate Limiting" → "api"
43
+ assert.strictEqual(
44
+ deriveSliceScope("API Rate Limiting"),
45
+ "api",
46
+ "extracts 'api' from API-related title",
47
+ );
48
+
49
+ // "Stripe Payment Integration" → "stripe"
50
+ assert.strictEqual(
51
+ deriveSliceScope("Stripe Payment Integration"),
52
+ "stripe",
53
+ "extracts 'stripe' from payment-related title",
54
+ );
55
+ });
56
+
57
+ test("returns undefined for generic titles", () => {
58
+ // "Integration Testing" → undefined (both words are generic)
59
+ assert.strictEqual(
60
+ deriveSliceScope("Integration Testing"),
61
+ undefined,
62
+ "returns undefined for generic 'Integration Testing'",
63
+ );
64
+
65
+ // "Setup & Configuration" → undefined (all generic)
66
+ assert.strictEqual(
67
+ deriveSliceScope("Setup & Configuration"),
68
+ undefined,
69
+ "returns undefined for generic 'Setup & Configuration'",
70
+ );
71
+
72
+ // "Final Review" → undefined
73
+ assert.strictEqual(
74
+ deriveSliceScope("Final Review"),
75
+ undefined,
76
+ "returns undefined for generic 'Final Review'",
77
+ );
78
+
79
+ // "Basic Implementation" → undefined
80
+ assert.strictEqual(
81
+ deriveSliceScope("Basic Implementation"),
82
+ undefined,
83
+ "returns undefined for generic 'Basic Implementation'",
84
+ );
85
+ });
86
+
87
+ test("handles description as additional context", () => {
88
+ // Generic title but specific description
89
+ const scope = deriveSliceScope(
90
+ "Initial Setup",
91
+ "Configure PostgreSQL database connection",
92
+ );
93
+ assert.ok(
94
+ scope === "postgresql" || scope === "database" || scope === "configure",
95
+ `expected meaningful scope from description, got '${scope}'`,
96
+ );
97
+ });
98
+
99
+ test("handles edge cases", () => {
100
+ // Empty title
101
+ assert.strictEqual(
102
+ deriveSliceScope(""),
103
+ undefined,
104
+ "returns undefined for empty title",
105
+ );
106
+
107
+ // Short words only
108
+ assert.strictEqual(
109
+ deriveSliceScope("A B C"),
110
+ undefined,
111
+ "returns undefined for very short words",
112
+ );
113
+
114
+ // Mixed case and punctuation
115
+ assert.strictEqual(
116
+ deriveSliceScope("OAuth2 + JWT Authentication"),
117
+ "oauth2",
118
+ "handles mixed case and punctuation",
119
+ );
120
+ });
121
+
122
+ test("filters unit IDs (S01, M001, T03)", () => {
123
+ // "S01: Infrastructure" → undefined (S01 is a unit ID, infrastructure is generic)
124
+ assert.strictEqual(
125
+ deriveSliceScope("S01: Infrastructure"),
126
+ undefined,
127
+ "skips S01 ID and returns undefined for generic 'Infrastructure'",
128
+ );
129
+
130
+ // "M001 Setup" → undefined (M001 is a unit ID, setup is generic)
131
+ assert.strictEqual(
132
+ deriveSliceScope("M001 Setup"),
133
+ undefined,
134
+ "skips M001 ID and returns undefined for generic 'Setup'",
135
+ );
136
+
137
+ // "T03: Database Migration" → "database" (skips T03, returns meaningful word)
138
+ assert.strictEqual(
139
+ deriveSliceScope("T03: Database Migration"),
140
+ "database",
141
+ "skips T03 ID and returns 'database'",
142
+ );
143
+
144
+ // "S02 Auth Flow" → "auth" (skips S02, returns meaningful word)
145
+ assert.strictEqual(
146
+ deriveSliceScope("S02 Auth Flow"),
147
+ "auth",
148
+ "skips S02 ID and returns 'auth'",
149
+ );
150
+ });
151
+
152
+ test("filters process/activity words", () => {
153
+ // "Integration Testing + Hardening" → undefined (all generic/process words)
154
+ assert.strictEqual(
155
+ deriveSliceScope("Integration Testing + Hardening"),
156
+ undefined,
157
+ "returns undefined for 'Integration Testing + Hardening'",
158
+ );
159
+
160
+ // "Validation & Verification" → undefined (both are process words)
161
+ assert.strictEqual(
162
+ deriveSliceScope("Validation & Verification"),
163
+ undefined,
164
+ "returns undefined for 'Validation & Verification'",
165
+ );
166
+
167
+ // "Performance Optimization" → "performance" (optimization is generic, performance is domain)
168
+ assert.strictEqual(
169
+ deriveSliceScope("Performance Optimization"),
170
+ "performance",
171
+ "extracts 'performance' before generic 'optimization'",
172
+ );
173
+
174
+ // "Security Enhancement" → "security" (enhancement is generic, security is domain)
175
+ assert.strictEqual(
176
+ deriveSliceScope("Security Enhancement"),
177
+ "security",
178
+ "extracts 'security' before generic 'enhancement'",
179
+ );
180
+
181
+ // "WebSocket Delivery Pipeline" → "websocket"
182
+ assert.strictEqual(
183
+ deriveSliceScope("WebSocket Delivery Pipeline"),
184
+ "websocket",
185
+ "extracts 'websocket' from delivery pipeline title",
186
+ );
187
+
188
+ // "Prisma Schema + Migration" → "prisma"
189
+ assert.strictEqual(
190
+ deriveSliceScope("Prisma Schema + Migration"),
191
+ "prisma",
192
+ "extracts 'prisma' from schema migration title",
193
+ );
194
+ });
195
+ });
196
+
197
+ // ═══════════════════════════════════════════════════════════════════════════
198
+ // inlineDecisionsFromDb cascade: R005 implementation
199
+ // ═══════════════════════════════════════════════════════════════════════════
200
+
201
+ describe("inlineDecisionsFromDb: cascade fallback (R005)", () => {
202
+ beforeEach(() => {
203
+ openDatabase(':memory:');
204
+ });
205
+
206
+ afterEach(() => {
207
+ closeDatabase();
208
+ });
209
+
210
+ test("cascade: scoped query returns scoped decisions when they exist", () => {
211
+ // Insert decisions with different scopes
212
+ insertDecision({
213
+ id: 'D001', when_context: 'M001/S01', scope: 'auth',
214
+ decision: 'use JWT', choice: 'JWT', rationale: 'standard',
215
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
216
+ });
217
+ insertDecision({
218
+ id: 'D002', when_context: 'M001/S02', scope: 'database',
219
+ decision: 'use PostgreSQL', choice: 'PostgreSQL', rationale: 'relational',
220
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
221
+ });
222
+ insertDecision({
223
+ id: 'D003', when_context: 'M001/S01', scope: 'architecture',
224
+ decision: 'use microservices', choice: 'microservices', rationale: 'scalable',
225
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
226
+ });
227
+
228
+ // Query with scope 'auth' should return D001 only
229
+ const authDecisions = queryDecisions({ milestoneId: 'M001', scope: 'auth' });
230
+ assert.strictEqual(authDecisions.length, 1, 'scoped query returns 1 decision');
231
+ assert.strictEqual(authDecisions[0]?.id, 'D001', 'returns D001 for auth scope');
232
+
233
+ // Query with scope 'database' should return D002 only
234
+ const dbDecisions = queryDecisions({ milestoneId: 'M001', scope: 'database' });
235
+ assert.strictEqual(dbDecisions.length, 1, 'scoped query returns 1 decision');
236
+ assert.strictEqual(dbDecisions[0]?.id, 'D002', 'returns D002 for database scope');
237
+ });
238
+
239
+ test("cascade: milestone-only fallback when scoped query returns empty", () => {
240
+ // Insert decisions for M001 with generic scope (e.g. 'architecture')
241
+ insertDecision({
242
+ id: 'D001', when_context: 'M001/S01', scope: 'architecture',
243
+ decision: 'use microservices', choice: 'microservices', rationale: 'scalable',
244
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
245
+ });
246
+ insertDecision({
247
+ id: 'D002', when_context: 'M001/S02', scope: 'performance',
248
+ decision: 'use caching', choice: 'Redis', rationale: 'fast',
249
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
250
+ });
251
+
252
+ // Query with scope 'auth' (no decisions with this scope) should return empty
253
+ const authDecisions = queryDecisions({ milestoneId: 'M001', scope: 'auth' });
254
+ assert.strictEqual(authDecisions.length, 0, 'scoped query for auth returns empty');
255
+
256
+ // Simulate cascade: fallback to milestone-only query
257
+ const milestoneDecisions = queryDecisions({ milestoneId: 'M001' });
258
+ assert.strictEqual(milestoneDecisions.length, 2, 'milestone-only query returns 2 decisions');
259
+ const ids = milestoneDecisions.map(d => d.id).sort();
260
+ assert.deepStrictEqual(ids, ['D001', 'D002'], 'milestone fallback returns all M001 decisions');
261
+ });
262
+
263
+ test("cascade: returns null when both scoped and milestone queries are empty", () => {
264
+ // Insert decisions only for M002
265
+ insertDecision({
266
+ id: 'D001', when_context: 'M002/S01', scope: 'auth',
267
+ decision: 'use OAuth', choice: 'OAuth2', rationale: 'standard',
268
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
269
+ });
270
+
271
+ // Query M001 with scope should return empty (no M001 decisions at all)
272
+ const scopedDecisions = queryDecisions({ milestoneId: 'M001', scope: 'auth' });
273
+ assert.strictEqual(scopedDecisions.length, 0, 'scoped query returns empty');
274
+
275
+ // Fallback to milestone-only should also return empty (no M001 decisions)
276
+ const milestoneDecisions = queryDecisions({ milestoneId: 'M001' });
277
+ assert.strictEqual(milestoneDecisions.length, 0, 'milestone-only query returns empty');
278
+
279
+ // This scenario would result in null from inlineDecisionsFromDb
280
+ // (we can't directly test inlineDecisionsFromDb here without mocking fs)
281
+ });
282
+
283
+ test("cascade: demonstrates the full cascade behavior", () => {
284
+ // This test demonstrates the cascade logic that inlineDecisionsFromDb implements:
285
+ // 1. First try { milestoneId: 'M001', scope: 'payment' } → empty
286
+ // 2. Then try { milestoneId: 'M001' } → gets D001, D002
287
+ // 3. Return the milestone-level decisions
288
+
289
+ // Setup: decisions exist at milestone level but not for 'payment' scope
290
+ insertDecision({
291
+ id: 'D001', when_context: 'M001/S01', scope: 'architecture',
292
+ decision: 'use REST', choice: 'REST API', rationale: 'standard',
293
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
294
+ });
295
+ insertDecision({
296
+ id: 'D002', when_context: 'M001/S02', scope: 'security',
297
+ decision: 'use HTTPS', choice: 'TLS 1.3', rationale: 'secure',
298
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
299
+ });
300
+
301
+ // Step 1: Query with scope 'payment' (no matches)
302
+ const paymentDecisions = queryDecisions({ milestoneId: 'M001', scope: 'payment' });
303
+ assert.strictEqual(paymentDecisions.length, 0, 'payment scope query returns empty');
304
+
305
+ // Step 2: Since scope was provided but returned empty, cascade to milestone-only
306
+ const milestoneDecisions = queryDecisions({ milestoneId: 'M001' });
307
+ assert.strictEqual(milestoneDecisions.length, 2, 'milestone fallback returns 2 decisions');
308
+
309
+ // Step 3: Format and verify content
310
+ const formatted = formatDecisionsForPrompt(milestoneDecisions);
311
+ assert.match(formatted, /D001/, 'formatted output includes D001');
312
+ assert.match(formatted, /D002/, 'formatted output includes D002');
313
+ assert.match(formatted, /architecture/, 'formatted output includes architecture scope');
314
+ assert.match(formatted, /security/, 'formatted output includes security scope');
315
+ });
316
+ });
317
+
318
+ // ═══════════════════════════════════════════════════════════════════════════
319
+ // Integration: scope derivation feeds into cascade
320
+ // ═══════════════════════════════════════════════════════════════════════════
321
+
322
+ describe("integration: scope derivation with cascade", () => {
323
+ beforeEach(() => {
324
+ openDatabase(':memory:');
325
+ });
326
+
327
+ afterEach(() => {
328
+ closeDatabase();
329
+ });
330
+
331
+ test("derived scope finds matching decisions when they exist", () => {
332
+ // Insert decisions with 'auth' scope
333
+ insertDecision({
334
+ id: 'D001', when_context: 'M001/S01', scope: 'auth',
335
+ decision: 'use JWT', choice: 'JWT tokens', rationale: 'stateless',
336
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
337
+ });
338
+
339
+ // Derive scope from slice title
340
+ const derivedScope = deriveSliceScope("Auth Middleware & Protected Routes");
341
+ assert.strictEqual(derivedScope, 'auth', 'derives auth scope from title');
342
+
343
+ // Query with derived scope should find the decision
344
+ const decisions = queryDecisions({ milestoneId: 'M001', scope: derivedScope });
345
+ assert.strictEqual(decisions.length, 1, 'scoped query finds matching decision');
346
+ assert.strictEqual(decisions[0]?.id, 'D001', 'finds the auth decision');
347
+ });
348
+
349
+ test("generic title triggers milestone-level fallback", () => {
350
+ // Insert decisions with various scopes
351
+ insertDecision({
352
+ id: 'D001', when_context: 'M001/S01', scope: 'architecture',
353
+ decision: 'use monolith', choice: 'monolith', rationale: 'simple',
354
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
355
+ });
356
+ insertDecision({
357
+ id: 'D002', when_context: 'M001/S02', scope: 'tooling',
358
+ decision: 'use TypeScript', choice: 'TypeScript', rationale: 'type safety',
359
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
360
+ });
361
+
362
+ // Derive scope from generic slice title
363
+ const derivedScope = deriveSliceScope("Integration Testing");
364
+ assert.strictEqual(derivedScope, undefined, 'generic title returns undefined scope');
365
+
366
+ // Without a scope, query returns all milestone decisions
367
+ const decisions = queryDecisions({ milestoneId: 'M001', scope: derivedScope });
368
+ assert.strictEqual(decisions.length, 2, 'no scope filter returns all decisions');
369
+ });
370
+ });
@@ -17,6 +17,7 @@ import {
17
17
  detectProjectState,
18
18
  detectV1Planning,
19
19
  detectProjectSignals,
20
+ scanProjectFiles,
20
21
  } from "../detection.ts";
21
22
 
22
23
  function makeTempDir(prefix: string): string {
@@ -1188,3 +1189,39 @@ test("detectProjectSignals: Spring Boot settings-defined catalog accessor emits
1188
1189
  cleanup(dir);
1189
1190
  }
1190
1191
  });
1192
+
1193
+ // ─── scanProjectFiles: RECURSIVE_SCAN_IGNORED_DIRS ──────────────────────
1194
+
1195
+ test("scanProjectFiles: excludes .claude, .gsd, .planning, .plans, .cursor, .vscode directories", () => {
1196
+ const dir = makeTempDir("scan-ignore-dotdirs");
1197
+ try {
1198
+ // Create project files that should be included
1199
+ mkdirSync(join(dir, "src"), { recursive: true });
1200
+ writeFileSync(join(dir, "src", "main.ts"), "// main\n", "utf-8");
1201
+ writeFileSync(join(dir, "README.md"), "# Project\n", "utf-8");
1202
+
1203
+ // Create tool directories that should be excluded
1204
+ const excludedDirs = [".claude", ".gsd", ".planning", ".plans", ".cursor", ".vscode"];
1205
+ for (const d of excludedDirs) {
1206
+ mkdirSync(join(dir, d), { recursive: true });
1207
+ writeFileSync(join(dir, d, "config.json"), "{}\n", "utf-8");
1208
+ }
1209
+ // Nested .claude directory
1210
+ mkdirSync(join(dir, ".claude", "memory"), { recursive: true });
1211
+ writeFileSync(join(dir, ".claude", "memory", "user.md"), "# Memory\n", "utf-8");
1212
+
1213
+ const files = scanProjectFiles(dir);
1214
+
1215
+ // Should include project files
1216
+ assert.ok(files.includes("src/main.ts"), "should include src/main.ts");
1217
+ assert.ok(files.includes("README.md"), "should include README.md");
1218
+
1219
+ // Should exclude all tool directories
1220
+ for (const d of excludedDirs) {
1221
+ const hasExcluded = files.some((f) => f.startsWith(`${d}/`));
1222
+ assert.ok(!hasExcluded, `should exclude ${d}/ directory but found: ${files.filter((f) => f.startsWith(`${d}/`)).join(", ")}`);
1223
+ }
1224
+ } finally {
1225
+ cleanup(dir);
1226
+ }
1227
+ });
@@ -0,0 +1,50 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFileSync } from "node:child_process";
4
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+
8
+ import { validateFileChanges } from "../safety/file-change-validator.ts";
9
+
10
+ function git(cwd: string, ...args: string[]): string {
11
+ return execFileSync("git", args, {
12
+ cwd,
13
+ stdio: ["ignore", "pipe", "pipe"],
14
+ encoding: "utf-8",
15
+ }).trim();
16
+ }
17
+
18
+ test("validateFileChanges ignores inline descriptions in expected output paths", (t) => {
19
+ const base = mkdtempSync(join(tmpdir(), "gsd-file-change-validator-"));
20
+ t.after(() => rmSync(base, { recursive: true, force: true }));
21
+
22
+ mkdirSync(join(base, "definitions"), { recursive: true });
23
+ git(base, "init");
24
+ git(base, "config", "user.email", "test@example.com");
25
+ git(base, "config", "user.name", "Test User");
26
+
27
+ const target = join(base, "definitions", "ac-audit.md");
28
+ writeFileSync(target, "initial\n");
29
+ git(base, "add", ".");
30
+ git(base, "commit", "-m", "initial");
31
+
32
+ writeFileSync(target, "updated\n");
33
+ git(base, "add", ".");
34
+ git(base, "commit", "-m", "update");
35
+
36
+ const audit = validateFileChanges(
37
+ base,
38
+ ["definitions/ac-audit.md — current state of AC CRM, tags, pipelines, automations"],
39
+ [],
40
+ );
41
+
42
+ assert.ok(audit, "audit should be produced when expected output exists");
43
+ assert.deepEqual(audit.unexpectedFiles, []);
44
+ assert.deepEqual(audit.missingFiles, []);
45
+ assert.equal(
46
+ audit.violations.some((v) => v.severity === "warning"),
47
+ false,
48
+ "described expected output should not trigger unexpected-file warnings",
49
+ );
50
+ });
@@ -245,6 +245,41 @@ describe('gsd-tools', () => {
245
245
  }
246
246
  });
247
247
 
248
+ test('gsd_summary_save supports CONTEXT-DRAFT persistence', async () => {
249
+ const tmpDir = makeTmpDir();
250
+ try {
251
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
252
+ openDatabase(dbPath);
253
+
254
+ await saveArtifactToDb(
255
+ {
256
+ path: 'milestones/M001/M001-CONTEXT-DRAFT.md',
257
+ artifact_type: 'CONTEXT-DRAFT',
258
+ content: '# M001 Draft Context\n\nDraft notes.',
259
+ milestone_id: 'M001',
260
+ },
261
+ tmpDir,
262
+ );
263
+
264
+ const draftPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-CONTEXT-DRAFT.md');
265
+ assert.ok(fs.existsSync(draftPath), 'Draft context file should be created');
266
+ const draftContent = fs.readFileSync(draftPath, 'utf-8');
267
+ assert.ok(draftContent.includes('Draft Context'), 'Draft context file should contain draft content');
268
+
269
+ const adapter = _getAdapter();
270
+ assert.ok(adapter !== null, 'Adapter should be available');
271
+ const rows = adapter!.prepare(
272
+ "SELECT * FROM artifacts WHERE path = 'milestones/M001/M001-CONTEXT-DRAFT.md'",
273
+ ).all();
274
+ assert.deepStrictEqual(rows.length, 1, 'Should have 1 draft artifact row');
275
+ assert.deepStrictEqual(rows[0]['artifact_type'] as string, 'CONTEXT-DRAFT', 'Artifact type should be CONTEXT-DRAFT');
276
+
277
+ closeDatabase();
278
+ } finally {
279
+ cleanupDir(tmpDir);
280
+ }
281
+ });
282
+
248
283
  test('DB unavailable error paths', async () => {
249
284
  // (d) All tools return isError when DB unavailable
250
285
  // Close any open DB and don't open a new one