gsd-pi 2.18.0 → 2.20.0

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 (289) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
  9. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  10. package/dist/resources/extensions/gsd/auto.ts +690 -39
  11. package/dist/resources/extensions/gsd/captures.ts +384 -0
  12. package/dist/resources/extensions/gsd/commands.ts +654 -36
  13. package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
  14. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  15. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  16. package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  17. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  18. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  19. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  20. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  21. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  22. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  23. package/dist/resources/extensions/gsd/export.ts +81 -2
  24. package/dist/resources/extensions/gsd/files.ts +39 -9
  25. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  26. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  27. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  28. package/dist/resources/extensions/gsd/history.ts +0 -1
  29. package/dist/resources/extensions/gsd/index.ts +277 -1
  30. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  31. package/dist/resources/extensions/gsd/metrics.ts +84 -0
  32. package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
  33. package/dist/resources/extensions/gsd/model-router.ts +256 -0
  34. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  35. package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  36. package/dist/resources/extensions/gsd/preferences.ts +198 -150
  37. package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  39. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  40. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  41. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  42. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  43. package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  44. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  45. package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  46. package/dist/resources/extensions/gsd/quick.ts +156 -0
  47. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  48. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  49. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  50. package/dist/resources/extensions/gsd/state.ts +30 -0
  51. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  52. package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
  53. package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  54. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  55. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  56. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  57. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  58. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  59. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  60. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  62. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  63. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  64. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  65. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  66. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  67. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  68. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  69. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  70. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  71. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  72. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  73. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  74. package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  75. package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  76. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  77. package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  78. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  79. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  80. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  81. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  82. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  83. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  84. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  85. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  86. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  87. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  88. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  89. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  90. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  91. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  92. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  93. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  94. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  95. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  96. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  97. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  98. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  99. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  100. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  101. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  102. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  103. package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
  104. package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
  105. package/dist/resources/extensions/gsd/types.ts +29 -0
  106. package/dist/resources/extensions/gsd/undo.ts +0 -1
  107. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  108. package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
  109. package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  110. package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
  111. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  112. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  113. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  114. package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  115. package/dist/resources/extensions/remote-questions/format.ts +166 -14
  116. package/dist/resources/extensions/remote-questions/manager.ts +14 -4
  117. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  118. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  119. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  120. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  121. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  122. package/dist/resources/extensions/voice/index.ts +4 -3
  123. package/package.json +1 -1
  124. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  126. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  131. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  137. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  139. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  140. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  142. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  144. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  146. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  148. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  150. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  153. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  156. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  158. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  161. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  162. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  163. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  164. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  165. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  166. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  167. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  168. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  169. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  170. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  171. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  172. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  173. package/src/resources/extensions/google-search/index.ts +164 -47
  174. package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
  175. package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
  176. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  177. package/src/resources/extensions/gsd/auto.ts +690 -39
  178. package/src/resources/extensions/gsd/captures.ts +384 -0
  179. package/src/resources/extensions/gsd/commands.ts +654 -36
  180. package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
  181. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  182. package/src/resources/extensions/gsd/context-store.ts +195 -0
  183. package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  184. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  185. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  186. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  187. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  188. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  189. package/src/resources/extensions/gsd/doctor.ts +283 -2
  190. package/src/resources/extensions/gsd/export.ts +81 -2
  191. package/src/resources/extensions/gsd/files.ts +39 -9
  192. package/src/resources/extensions/gsd/git-service.ts +6 -0
  193. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  194. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  195. package/src/resources/extensions/gsd/history.ts +0 -1
  196. package/src/resources/extensions/gsd/index.ts +277 -1
  197. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  198. package/src/resources/extensions/gsd/metrics.ts +84 -0
  199. package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
  200. package/src/resources/extensions/gsd/model-router.ts +256 -0
  201. package/src/resources/extensions/gsd/notifications.ts +0 -1
  202. package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  203. package/src/resources/extensions/gsd/preferences.ts +198 -150
  204. package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  206. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  207. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  208. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  209. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  210. package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  211. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  212. package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  213. package/src/resources/extensions/gsd/quick.ts +156 -0
  214. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  215. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  216. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  217. package/src/resources/extensions/gsd/state.ts +30 -0
  218. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  219. package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
  220. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  221. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  222. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  223. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  224. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  226. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  227. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  228. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  229. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  230. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  231. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  232. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  233. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  235. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  236. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  237. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  238. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  239. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  240. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  241. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  242. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  243. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  244. package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  245. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  246. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  247. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  248. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  249. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  250. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  251. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  252. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  253. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  254. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  255. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  256. package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  257. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  258. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  259. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  260. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  261. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  262. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  263. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  264. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  265. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  266. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  267. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  268. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  269. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  270. package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
  271. package/src/resources/extensions/gsd/triage-ui.ts +175 -0
  272. package/src/resources/extensions/gsd/types.ts +29 -0
  273. package/src/resources/extensions/gsd/undo.ts +0 -1
  274. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  275. package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
  276. package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  277. package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
  278. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  279. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  280. package/src/resources/extensions/remote-questions/config.ts +4 -2
  281. package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  282. package/src/resources/extensions/remote-questions/format.ts +166 -14
  283. package/src/resources/extensions/remote-questions/manager.ts +14 -4
  284. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  285. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  286. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  287. package/src/resources/extensions/remote-questions/types.ts +2 -1
  288. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  289. package/src/resources/extensions/voice/index.ts +4 -3
@@ -0,0 +1,195 @@
1
+ // GSD Context Store — Query Layer & Formatters
2
+ //
3
+ // Typed query functions for decisions and requirements from the DB views,
4
+ // with optional filtering. Format functions produce prompt-injectable markdown.
5
+ // All functions degrade gracefully: return empty results when DB unavailable, never throw.
6
+
7
+ import { isDbAvailable, _getAdapter } from './gsd-db.js';
8
+ import type { Decision, Requirement } from './types.js';
9
+
10
+ // ─── Query Functions ───────────────────────────────────────────────────────
11
+
12
+ export interface DecisionQueryOpts {
13
+ milestoneId?: string;
14
+ scope?: string;
15
+ }
16
+
17
+ export interface RequirementQueryOpts {
18
+ sliceId?: string;
19
+ status?: string;
20
+ }
21
+
22
+ /**
23
+ * Query active (non-superseded) decisions with optional filters.
24
+ * - milestoneId: filters where when_context LIKE '%milestoneId%'
25
+ * - scope: filters where scope = :scope (exact match)
26
+ *
27
+ * Returns [] if DB is not available. Never throws.
28
+ */
29
+ export function queryDecisions(opts?: DecisionQueryOpts): Decision[] {
30
+ if (!isDbAvailable()) return [];
31
+ const adapter = _getAdapter();
32
+ if (!adapter) return [];
33
+
34
+ try {
35
+ const clauses: string[] = ['superseded_by IS NULL'];
36
+ const params: Record<string, unknown> = {};
37
+
38
+ if (opts?.milestoneId) {
39
+ clauses.push('when_context LIKE :milestone_pattern');
40
+ params[':milestone_pattern'] = `%${opts.milestoneId}%`;
41
+ }
42
+
43
+ if (opts?.scope) {
44
+ clauses.push('scope = :scope');
45
+ params[':scope'] = opts.scope;
46
+ }
47
+
48
+ const sql = `SELECT * FROM decisions WHERE ${clauses.join(' AND ')} ORDER BY seq`;
49
+ const rows = adapter.prepare(sql).all(params);
50
+
51
+ return rows.map(row => ({
52
+ seq: row['seq'] as number,
53
+ id: row['id'] as string,
54
+ when_context: row['when_context'] as string,
55
+ scope: row['scope'] as string,
56
+ decision: row['decision'] as string,
57
+ choice: row['choice'] as string,
58
+ rationale: row['rationale'] as string,
59
+ revisable: row['revisable'] as string,
60
+ superseded_by: null,
61
+ }));
62
+ } catch {
63
+ return [];
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Query active (non-superseded) requirements with optional filters.
69
+ * - sliceId: filters where primary_owner LIKE '%sliceId%' OR supporting_slices LIKE '%sliceId%'
70
+ * - status: filters where status = :status (exact match)
71
+ *
72
+ * Returns [] if DB is not available. Never throws.
73
+ */
74
+ export function queryRequirements(opts?: RequirementQueryOpts): Requirement[] {
75
+ if (!isDbAvailable()) return [];
76
+ const adapter = _getAdapter();
77
+ if (!adapter) return [];
78
+
79
+ try {
80
+ const clauses: string[] = ['superseded_by IS NULL'];
81
+ const params: Record<string, unknown> = {};
82
+
83
+ if (opts?.sliceId) {
84
+ clauses.push('(primary_owner LIKE :slice_pattern OR supporting_slices LIKE :slice_pattern)');
85
+ params[':slice_pattern'] = `%${opts.sliceId}%`;
86
+ }
87
+
88
+ if (opts?.status) {
89
+ clauses.push('status = :status');
90
+ params[':status'] = opts.status;
91
+ }
92
+
93
+ const sql = `SELECT * FROM requirements WHERE ${clauses.join(' AND ')} ORDER BY id`;
94
+ const rows = adapter.prepare(sql).all(params);
95
+
96
+ return rows.map(row => ({
97
+ id: row['id'] as string,
98
+ class: row['class'] as string,
99
+ status: row['status'] as string,
100
+ description: row['description'] as string,
101
+ why: row['why'] as string,
102
+ source: row['source'] as string,
103
+ primary_owner: row['primary_owner'] as string,
104
+ supporting_slices: row['supporting_slices'] as string,
105
+ validation: row['validation'] as string,
106
+ notes: row['notes'] as string,
107
+ full_content: row['full_content'] as string,
108
+ superseded_by: null,
109
+ }));
110
+ } catch {
111
+ return [];
112
+ }
113
+ }
114
+
115
+ // ─── Format Functions ──────────────────────────────────────────────────────
116
+
117
+ /**
118
+ * Format decisions as a markdown table matching DECISIONS.md format.
119
+ * Returns empty string for empty input.
120
+ */
121
+ export function formatDecisionsForPrompt(decisions: Decision[]): string {
122
+ if (decisions.length === 0) return '';
123
+
124
+ const header = '| # | When | Scope | Decision | Choice | Rationale | Revisable? |';
125
+ const separator = '|---|------|-------|----------|--------|-----------|------------|';
126
+ const rows = decisions.map(d =>
127
+ `| ${d.id} | ${d.when_context} | ${d.scope} | ${d.decision} | ${d.choice} | ${d.rationale} | ${d.revisable} |`,
128
+ );
129
+
130
+ return [header, separator, ...rows].join('\n');
131
+ }
132
+
133
+ /**
134
+ * Format requirements as structured H3 sections matching REQUIREMENTS.md format.
135
+ * Returns empty string for empty input.
136
+ */
137
+ export function formatRequirementsForPrompt(requirements: Requirement[]): string {
138
+ if (requirements.length === 0) return '';
139
+
140
+ return requirements.map(r => {
141
+ const lines: string[] = [
142
+ `### ${r.id}: ${r.description}`,
143
+ '',
144
+ `- **Class:** ${r.class}`,
145
+ `- **Status:** ${r.status}`,
146
+ `- **Why:** ${r.why}`,
147
+ `- **Source:** ${r.source}`,
148
+ `- **Primary Owner:** ${r.primary_owner}`,
149
+ ];
150
+
151
+ if (r.supporting_slices) {
152
+ lines.push(`- **Supporting Slices:** ${r.supporting_slices}`);
153
+ }
154
+
155
+ lines.push(`- **Validation:** ${r.validation}`);
156
+
157
+ if (r.notes) {
158
+ lines.push(`- **Notes:** ${r.notes}`);
159
+ }
160
+
161
+ return lines.join('\n');
162
+ }).join('\n\n');
163
+ }
164
+
165
+ // ─── Artifact Query Functions ──────────────────────────────────────────────
166
+
167
+ /**
168
+ * Query a hierarchy artifact by its relative path.
169
+ * Returns the full_content string or null if not found/unavailable.
170
+ * Never throws.
171
+ */
172
+ export function queryArtifact(path: string): string | null {
173
+ if (!isDbAvailable()) return null;
174
+ const adapter = _getAdapter();
175
+ if (!adapter) return null;
176
+
177
+ try {
178
+ const row = adapter.prepare('SELECT full_content FROM artifacts WHERE path = :path').get({ ':path': path });
179
+ if (!row) return null;
180
+ const content = row['full_content'] as string;
181
+ return content || null;
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Query PROJECT.md content from the artifacts table.
189
+ * PROJECT.md is stored with the relative path 'PROJECT.md' by the importer.
190
+ * Returns the content string or null if not found/unavailable.
191
+ * Never throws.
192
+ */
193
+ export function queryProject(): string | null {
194
+ return queryArtifact('PROJECT.md');
195
+ }
@@ -15,6 +15,7 @@ import { getAutoDashboardData, type AutoDashboardData } from "./auto.js";
15
15
  import {
16
16
  getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice,
17
17
  aggregateByModel, formatCost, formatTokenCount, formatCostProjection,
18
+ type UnitMetrics,
18
19
  } from "./metrics.js";
19
20
  import { loadEffectiveGSDPreferences } from "./preferences.js";
20
21
  import { getActiveWorktreeName } from "./worktree-command.js";
@@ -39,6 +40,9 @@ function unitLabel(type: string): string {
39
40
  case "execute-task": return "Execute";
40
41
  case "complete-slice": return "Complete";
41
42
  case "reassess-roadmap": return "Reassess";
43
+ case "triage-captures": return "Triage";
44
+ case "quick-task": return "Quick Task";
45
+ case "replan-slice": return "Replan";
42
46
  default: return type;
43
47
  }
44
48
  }
@@ -345,6 +349,13 @@ export class GSDDashboardOverlay {
345
349
  lines.push(blank());
346
350
  }
347
351
 
352
+ // Pending captures badge — only shown when captures are waiting for triage
353
+ if (this.dashData.pendingCaptureCount > 0) {
354
+ const count = this.dashData.pendingCaptureCount;
355
+ lines.push(row(th.fg("warning", `📌 ${count} pending capture${count === 1 ? "" : "s"} awaiting triage`)));
356
+ lines.push(blank());
357
+ }
358
+
348
359
  if (this.loading) {
349
360
  lines.push(centered(th.fg("dim", "Loading dashboard…")));
350
361
  return lines;
@@ -403,11 +414,33 @@ export class GSDDashboardOverlay {
403
414
  lines.push(row(th.fg("text", th.bold("Completed"))));
404
415
  lines.push(blank());
405
416
 
417
+ // Build ledger lookup for budget indicators (last entry wins for retries)
418
+ const ledgerLookup = new Map<string, UnitMetrics>();
419
+ const currentLedger = getLedger();
420
+ if (currentLedger) {
421
+ for (const lu of currentLedger.units) {
422
+ ledgerLookup.set(`${lu.type}:${lu.id}`, lu);
423
+ }
424
+ }
425
+
406
426
  const recent = [...this.dashData.completedUnits].reverse().slice(0, 10);
407
427
  for (const u of recent) {
408
428
  const left = ` ${th.fg("success", "✓")} ${th.fg("muted", unitLabel(u.type))} ${th.fg("muted", u.id)}`;
429
+
430
+ // Budget indicators from ledger
431
+ const ledgerEntry = ledgerLookup.get(`${u.type}:${u.id}`);
432
+ let budgetMarkers = "";
433
+ if (ledgerEntry) {
434
+ if (ledgerEntry.truncationSections && ledgerEntry.truncationSections > 0) {
435
+ budgetMarkers += th.fg("warning", ` ▼${ledgerEntry.truncationSections}`);
436
+ }
437
+ if (ledgerEntry.continueHereFired === true) {
438
+ budgetMarkers += th.fg("error", " → wrap-up");
439
+ }
440
+ }
441
+
409
442
  const right = th.fg("dim", formatDuration(u.finishedAt - u.startedAt));
410
- lines.push(row(joinColumns(left, right, contentWidth)));
443
+ lines.push(row(joinColumns(`${left}${budgetMarkers}`, right, contentWidth)));
411
444
  }
412
445
 
413
446
  if (this.dashData.completedUnits.length > 10) {
@@ -438,6 +471,18 @@ export class GSDDashboardOverlay {
438
471
  `${th.fg("dim", "cache-w:")} ${th.fg("text", formatTokenCount(totals.tokens.cacheWrite))}`,
439
472
  ], contentWidth, " ")));
440
473
 
474
+ // Budget aggregate line — only when data exists
475
+ if (totals.totalTruncationSections > 0 || totals.continueHereFiredCount > 0) {
476
+ const budgetParts: string[] = [];
477
+ if (totals.totalTruncationSections > 0) {
478
+ budgetParts.push(th.fg("warning", `${totals.totalTruncationSections} sections truncated`));
479
+ }
480
+ if (totals.continueHereFiredCount > 0) {
481
+ budgetParts.push(th.fg("error", `${totals.continueHereFiredCount} continue-here fired`));
482
+ }
483
+ lines.push(row(budgetParts.join(` ${th.fg("dim", "·")} `)));
484
+ }
485
+
441
486
  const phases = aggregateByPhase(ledger.units);
442
487
  if (phases.length > 0) {
443
488
  lines.push(blank());
@@ -482,14 +527,17 @@ export class GSDDashboardOverlay {
482
527
  }
483
528
 
484
529
  const models = aggregateByModel(ledger.units);
485
- if (models.length > 1) {
530
+ if (models.length >= 1) {
486
531
  lines.push(blank());
487
532
  lines.push(row(th.fg("dim", "By Model")));
488
533
  for (const m of models) {
489
534
  const pct = totals.cost > 0 ? Math.round((m.cost / totals.cost) * 100) : 0;
490
535
  const modelName = truncateToWidth(m.model, 38);
536
+ const ctxWindow = m.contextWindowTokens !== undefined
537
+ ? th.fg("dim", ` [${formatTokenCount(m.contextWindowTokens)}]`)
538
+ : "";
491
539
  const left = ` ${th.fg("text", modelName.padEnd(38))}${th.fg("warning", formatCost(m.cost).padStart(8))}`;
492
- const right = th.fg("dim", `${String(pct).padStart(3)}% ${m.units} units`);
540
+ const right = th.fg("dim", `${String(pct).padStart(3)}% ${m.units} units`) + ctxWindow;
493
541
  lines.push(row(joinColumns(left, right, contentWidth)));
494
542
  }
495
543
  }
@@ -0,0 +1,341 @@
1
+ // GSD DB Writer — Markdown generators + DB-first write helpers
2
+ //
3
+ // The missing DB→markdown direction. S03 established markdown→DB (md-importer.ts).
4
+ // This module generates DECISIONS.md and REQUIREMENTS.md from DB state,
5
+ // computes next decision IDs, and provides write helpers that upsert to DB
6
+ // then regenerate the corresponding markdown file.
7
+ //
8
+ // Critical invariant: generated markdown must round-trip through
9
+ // parseDecisionsTable() and parseRequirementsSections() with field fidelity.
10
+
11
+ import { join, resolve } from 'node:path';
12
+ import type { Decision, Requirement } from './types.js';
13
+ import { resolveGsdRootFile } from './paths.js';
14
+ import { saveFile } from './files.js';
15
+
16
+ // ─── Markdown Generators ──────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Generate full DECISIONS.md content from an array of Decision objects.
20
+ * Produces the canonical format: H1 header, HTML comment block, table header,
21
+ * separator, and one data row per decision.
22
+ *
23
+ * Column order: #, When, Scope, Decision, Choice, Rationale, Revisable?
24
+ */
25
+ export function generateDecisionsMd(decisions: Decision[]): string {
26
+ const lines: string[] = [];
27
+
28
+ lines.push('# Decisions Register');
29
+ lines.push('');
30
+ lines.push('<!-- Append-only. Never edit or remove existing rows.');
31
+ lines.push(' To reverse a decision, add a new row that supersedes it.');
32
+ lines.push(' Read this file at the start of any planning or research phase. -->');
33
+ lines.push('');
34
+ lines.push('| # | When | Scope | Decision | Choice | Rationale | Revisable? |');
35
+ lines.push('|---|------|-------|----------|--------|-----------|------------|');
36
+
37
+ for (const d of decisions) {
38
+ // Escape pipe characters within cell values to preserve table structure
39
+ const cells = [
40
+ d.id,
41
+ d.when_context,
42
+ d.scope,
43
+ d.decision,
44
+ d.choice,
45
+ d.rationale,
46
+ d.revisable,
47
+ ].map(cell => (cell ?? '').replace(/\|/g, '\\|'));
48
+
49
+ lines.push(`| ${cells.join(' | ')} |`);
50
+ }
51
+
52
+ return lines.join('\n') + '\n';
53
+ }
54
+
55
+ // ─── Requirements Markdown Generator ──────────────────────────────────────
56
+
57
+ /** Status values that map to specific sections, in display order. */
58
+ const STATUS_SECTION_MAP: Array<{ status: string; heading: string }> = [
59
+ { status: 'active', heading: 'Active' },
60
+ { status: 'validated', heading: 'Validated' },
61
+ { status: 'deferred', heading: 'Deferred' },
62
+ { status: 'out-of-scope', heading: 'Out of Scope' },
63
+ ];
64
+
65
+ /**
66
+ * Generate full REQUIREMENTS.md content from an array of Requirement objects.
67
+ * Groups requirements by status into sections (## Active, ## Validated, etc.),
68
+ * each containing ### RXXX — Description headings with bullet fields.
69
+ * Only emits sections that have content. Appends Traceability table and
70
+ * Coverage Summary at the bottom.
71
+ */
72
+ export function generateRequirementsMd(requirements: Requirement[]): string {
73
+ const lines: string[] = [];
74
+
75
+ lines.push('# Requirements');
76
+ lines.push('');
77
+ lines.push('This file is the explicit capability and coverage contract for the project.');
78
+ lines.push('');
79
+
80
+ // Group by status
81
+ const byStatus = new Map<string, Requirement[]>();
82
+ for (const r of requirements) {
83
+ const status = (r.status || 'active').toLowerCase();
84
+ if (!byStatus.has(status)) byStatus.set(status, []);
85
+ byStatus.get(status)!.push(r);
86
+ }
87
+
88
+ // Emit sections in canonical order
89
+ for (const { status, heading } of STATUS_SECTION_MAP) {
90
+ const reqs = byStatus.get(status);
91
+ if (!reqs || reqs.length === 0) continue;
92
+
93
+ lines.push(`## ${heading}`);
94
+ lines.push('');
95
+
96
+ for (const r of reqs) {
97
+ lines.push(`### ${r.id} — ${r.description || 'Untitled'}`);
98
+
99
+ // Emit bullet fields — only those with content
100
+ if (r.class) lines.push(`- Class: ${r.class}`);
101
+ if (r.status) lines.push(`- Status: ${r.status}`);
102
+ if (r.description) lines.push(`- Description: ${r.description}`);
103
+ if (r.why) lines.push(`- Why it matters: ${r.why}`);
104
+ if (r.source) lines.push(`- Source: ${r.source}`);
105
+ if (r.primary_owner) lines.push(`- Primary owning slice: ${r.primary_owner}`);
106
+ if (r.supporting_slices) lines.push(`- Supporting slices: ${r.supporting_slices}`);
107
+ if (r.validation) lines.push(`- Validation: ${r.validation}`);
108
+ if (r.notes) lines.push(`- Notes: ${r.notes}`);
109
+ lines.push('');
110
+ }
111
+ }
112
+
113
+ // Traceability table
114
+ lines.push('## Traceability');
115
+ lines.push('');
116
+ lines.push('| ID | Class | Status | Primary owner | Supporting | Proof |');
117
+ lines.push('|---|---|---|---|---|---|');
118
+
119
+ for (const r of requirements) {
120
+ const proof = r.validation || 'unmapped';
121
+ lines.push(
122
+ `| ${r.id} | ${r.class || ''} | ${r.status || ''} | ${r.primary_owner || 'none'} | ${r.supporting_slices || 'none'} | ${proof} |`,
123
+ );
124
+ }
125
+
126
+ lines.push('');
127
+
128
+ // Coverage Summary
129
+ const activeCount = byStatus.get('active')?.length ?? 0;
130
+ const validatedReqs = byStatus.get('validated') ?? [];
131
+ const validatedIds = validatedReqs.map(r => r.id).join(', ');
132
+
133
+ lines.push('## Coverage Summary');
134
+ lines.push('');
135
+ lines.push(`- Active requirements: ${activeCount}`);
136
+ lines.push(`- Mapped to slices: ${activeCount}`);
137
+ lines.push(`- Validated: ${validatedReqs.length}${validatedIds ? ` (${validatedIds})` : ''}`);
138
+ lines.push(`- Unmapped active requirements: 0`);
139
+
140
+ return lines.join('\n') + '\n';
141
+ }
142
+
143
+ // ─── Next Decision ID ─────────────────────────────────────────────────────
144
+
145
+ /**
146
+ * Compute the next decision ID from the current DB state.
147
+ * Queries MAX(CAST(SUBSTR(id, 2) AS INTEGER)) from decisions table.
148
+ * Returns D001 if no decisions exist. Zero-pads to 3 digits.
149
+ */
150
+ export async function nextDecisionId(): Promise<string> {
151
+ try {
152
+ const db = await import('./gsd-db.js');
153
+ const adapter = db._getAdapter();
154
+ if (!adapter) return 'D001';
155
+
156
+ const row = adapter
157
+ .prepare('SELECT MAX(CAST(SUBSTR(id, 2) AS INTEGER)) as max_num FROM decisions')
158
+ .get();
159
+
160
+ const maxNum = row ? (row['max_num'] as number | null) : null;
161
+ if (maxNum == null || isNaN(maxNum)) return 'D001';
162
+
163
+ const next = maxNum + 1;
164
+ return `D${String(next).padStart(3, '0')}`;
165
+ } catch (err) {
166
+ process.stderr.write(`gsd-db: nextDecisionId failed: ${(err as Error).message}\n`);
167
+ return 'D001';
168
+ }
169
+ }
170
+
171
+ // ─── Save Decision to DB + Regenerate Markdown ────────────────────────────
172
+
173
+ export interface SaveDecisionFields {
174
+ scope: string;
175
+ decision: string;
176
+ choice: string;
177
+ rationale: string;
178
+ revisable?: string;
179
+ when_context?: string;
180
+ }
181
+
182
+ /**
183
+ * Save a new decision to DB and regenerate DECISIONS.md.
184
+ * Auto-assigns the next ID via nextDecisionId().
185
+ * Returns the assigned ID.
186
+ */
187
+ export async function saveDecisionToDb(
188
+ fields: SaveDecisionFields,
189
+ basePath: string,
190
+ ): Promise<{ id: string }> {
191
+ try {
192
+ const db = await import('./gsd-db.js');
193
+
194
+ const id = await nextDecisionId();
195
+
196
+ db.upsertDecision({
197
+ id,
198
+ when_context: fields.when_context ?? '',
199
+ scope: fields.scope,
200
+ decision: fields.decision,
201
+ choice: fields.choice,
202
+ rationale: fields.rationale,
203
+ revisable: fields.revisable ?? 'Yes',
204
+ superseded_by: null,
205
+ });
206
+
207
+ // Fetch all decisions (including superseded for the full register)
208
+ const adapter = db._getAdapter();
209
+ let allDecisions: Decision[] = [];
210
+ if (adapter) {
211
+ const rows = adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
212
+ allDecisions = rows.map(row => ({
213
+ seq: row['seq'] as number,
214
+ id: row['id'] as string,
215
+ when_context: row['when_context'] as string,
216
+ scope: row['scope'] as string,
217
+ decision: row['decision'] as string,
218
+ choice: row['choice'] as string,
219
+ rationale: row['rationale'] as string,
220
+ revisable: row['revisable'] as string,
221
+ superseded_by: (row['superseded_by'] as string) ?? null,
222
+ }));
223
+ }
224
+
225
+ const md = generateDecisionsMd(allDecisions);
226
+ const filePath = resolveGsdRootFile(basePath, 'DECISIONS');
227
+ await saveFile(filePath, md);
228
+
229
+ return { id };
230
+ } catch (err) {
231
+ process.stderr.write(`gsd-db: saveDecisionToDb failed: ${(err as Error).message}\n`);
232
+ throw err;
233
+ }
234
+ }
235
+
236
+ // ─── Update Requirement in DB + Regenerate Markdown ───────────────────────
237
+
238
+ /**
239
+ * Update a requirement in DB and regenerate REQUIREMENTS.md.
240
+ * Fetches existing requirement, merges updates, upserts, then regenerates.
241
+ */
242
+ export async function updateRequirementInDb(
243
+ id: string,
244
+ updates: Partial<Requirement>,
245
+ basePath: string,
246
+ ): Promise<void> {
247
+ try {
248
+ const db = await import('./gsd-db.js');
249
+
250
+ const existing = db.getRequirementById(id);
251
+ if (!existing) {
252
+ throw new Error(`Requirement ${id} not found`);
253
+ }
254
+
255
+ // Merge updates into existing
256
+ const merged: Requirement = {
257
+ ...existing,
258
+ ...updates,
259
+ id: existing.id, // ID cannot be changed
260
+ };
261
+
262
+ db.upsertRequirement(merged);
263
+
264
+ // Fetch ALL requirements (including superseded) for full file regeneration
265
+ const adapter = db._getAdapter();
266
+ let allRequirements: Requirement[] = [];
267
+ if (adapter) {
268
+ const rows = adapter.prepare('SELECT * FROM requirements ORDER BY id').all();
269
+ allRequirements = rows.map(row => ({
270
+ id: row['id'] as string,
271
+ class: row['class'] as string,
272
+ status: row['status'] as string,
273
+ description: row['description'] as string,
274
+ why: row['why'] as string,
275
+ source: row['source'] as string,
276
+ primary_owner: row['primary_owner'] as string,
277
+ supporting_slices: row['supporting_slices'] as string,
278
+ validation: row['validation'] as string,
279
+ notes: row['notes'] as string,
280
+ full_content: row['full_content'] as string,
281
+ superseded_by: (row['superseded_by'] as string) ?? null,
282
+ }));
283
+ }
284
+
285
+ // Filter to non-superseded for the markdown file
286
+ // (superseded requirements don't appear in section headings)
287
+ const nonSuperseded = allRequirements.filter(r => r.superseded_by == null);
288
+
289
+ const md = generateRequirementsMd(nonSuperseded);
290
+ const filePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
291
+ await saveFile(filePath, md);
292
+ } catch (err) {
293
+ process.stderr.write(`gsd-db: updateRequirementInDb failed: ${(err as Error).message}\n`);
294
+ throw err;
295
+ }
296
+ }
297
+
298
+ // ─── Save Artifact to DB + Disk ───────────────────────────────────────────
299
+
300
+ export interface SaveArtifactOpts {
301
+ path: string;
302
+ artifact_type: string;
303
+ content: string;
304
+ milestone_id?: string;
305
+ slice_id?: string;
306
+ task_id?: string;
307
+ }
308
+
309
+ /**
310
+ * Save an artifact to DB and write the corresponding markdown file to disk.
311
+ * The path is relative to .gsd/ (e.g. "milestones/M001/slices/S06/tasks/T01-SUMMARY.md").
312
+ * The full file path is computed as basePath + '.gsd/' + path.
313
+ */
314
+ export async function saveArtifactToDb(
315
+ opts: SaveArtifactOpts,
316
+ basePath: string,
317
+ ): Promise<void> {
318
+ try {
319
+ const db = await import('./gsd-db.js');
320
+
321
+ db.insertArtifact({
322
+ path: opts.path,
323
+ artifact_type: opts.artifact_type,
324
+ milestone_id: opts.milestone_id ?? null,
325
+ slice_id: opts.slice_id ?? null,
326
+ task_id: opts.task_id ?? null,
327
+ full_content: opts.content,
328
+ });
329
+
330
+ // Write the file to disk (guard against path traversal)
331
+ const gsdDir = resolve(basePath, '.gsd');
332
+ const fullPath = resolve(basePath, '.gsd', opts.path);
333
+ if (!fullPath.startsWith(gsdDir)) {
334
+ throw new Error(`saveArtifactToDb: path escapes .gsd/ directory: ${opts.path}`);
335
+ }
336
+ await saveFile(fullPath, opts.content);
337
+ } catch (err) {
338
+ process.stderr.write(`gsd-db: saveArtifactToDb failed: ${(err as Error).message}\n`);
339
+ throw err;
340
+ }
341
+ }