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
@@ -8,12 +8,15 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
8
8
  import { AuthStorage } from "@gsd/pi-coding-agent";
9
9
  import { existsSync, readFileSync, mkdirSync } from "node:fs";
10
10
  import { join, dirname } from "node:path";
11
+ import { enableDebug, isDebugEnabled } from "./debug-logger.js";
11
12
  import { fileURLToPath } from "node:url";
12
13
  import { deriveState } from "./state.js";
13
14
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
15
+ import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
14
16
  import { showQueue, showDiscuss } from "./guided-flow.js";
15
17
  import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote } from "./auto.js";
16
18
  import { resolveProjectRoot } from "./worktree.js";
19
+ import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
17
20
  import {
18
21
  getGlobalGSDPreferencesPath,
19
22
  getLegacyGlobalGSDPreferencesPath,
@@ -34,12 +37,13 @@ import {
34
37
  import { loadPrompt } from "./prompt-loader.js";
35
38
 
36
39
  import { handleRemote } from "../remote-questions/remote-command.js";
40
+ import { handleQuick } from "./quick.js";
37
41
  import { handleHistory } from "./history.js";
38
42
  import { handleUndo } from "./undo.js";
39
43
  import { handleExport } from "./export.js";
40
44
  import { nativeBranchList, nativeDetectMainBranch, nativeBranchListMerged, nativeBranchDelete, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
41
45
 
42
- function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
46
+ export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
43
47
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
44
48
  const workflow = readFileSync(workflowPath, "utf-8");
45
49
  const prompt = loadPrompt("doctor-heal", {
@@ -64,12 +68,13 @@ function projectRoot(): string {
64
68
 
65
69
  export function registerGSDCommand(pi: ExtensionAPI): void {
66
70
  pi.registerCommand("gsd", {
67
- description: "GSD — Get Shit Done: /gsd next|auto|stop|pause|status|queue|history|undo|skip|export|cleanup|prefs|config|hooks|doctor|migrate|remote|steer|knowledge",
71
+ description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|history|undo|skip|export|cleanup|mode|prefs|config|hooks|run-hook|skill-health|doctor|migrate|remote|steer|knowledge",
68
72
  getArgumentCompletions: (prefix: string) => {
69
73
  const subcommands = [
70
- "next", "auto", "stop", "pause", "status", "queue", "discuss",
71
- "history", "undo", "skip", "export", "cleanup", "prefs",
72
- "config", "hooks", "doctor", "migrate", "remote", "steer", "knowledge",
74
+ "help", "next", "auto", "stop", "pause", "status", "visualize", "queue", "quick", "discuss",
75
+ "capture", "triage",
76
+ "history", "undo", "skip", "export", "cleanup", "mode", "prefs",
77
+ "config", "hooks", "run-hook", "skill-health", "doctor", "migrate", "remote", "steer", "inspect", "knowledge",
73
78
  ];
74
79
  const parts = prefix.trim().split(/\s+/);
75
80
 
@@ -81,11 +86,18 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
81
86
 
82
87
  if (parts[0] === "auto" && parts.length <= 2) {
83
88
  const flagPrefix = parts[1] ?? "";
84
- return ["--verbose"]
89
+ return ["--verbose", "--debug"]
85
90
  .filter((f) => f.startsWith(flagPrefix))
86
91
  .map((f) => ({ value: `auto ${f}`, label: f }));
87
92
  }
88
93
 
94
+ if (parts[0] === "mode" && parts.length <= 2) {
95
+ const subPrefix = parts[1] ?? "";
96
+ return ["global", "project"]
97
+ .filter((cmd) => cmd.startsWith(subPrefix))
98
+ .map((cmd) => ({ value: `mode ${cmd}`, label: cmd }));
99
+ }
100
+
89
101
  if (parts[0] === "prefs" && parts.length <= 2) {
90
102
  const subPrefix = parts[1] ?? "";
91
103
  return ["global", "project", "status", "wizard", "setup"]
@@ -158,11 +170,30 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
158
170
  async handler(args: string, ctx: ExtensionCommandContext) {
159
171
  const trimmed = (typeof args === "string" ? args : "").trim();
160
172
 
173
+ if (trimmed === "help" || trimmed === "h" || trimmed === "?") {
174
+ showHelp(ctx);
175
+ return;
176
+ }
177
+
161
178
  if (trimmed === "status") {
162
179
  await handleStatus(ctx);
163
180
  return;
164
181
  }
165
182
 
183
+ if (trimmed === "visualize") {
184
+ await handleVisualize(ctx);
185
+ return;
186
+ }
187
+
188
+ if (trimmed === "mode" || trimmed.startsWith("mode ")) {
189
+ const modeArgs = trimmed.replace(/^mode\s*/, "").trim();
190
+ const scope = modeArgs === "project" ? "project" : "global";
191
+ const path = scope === "project" ? getProjectGSDPreferencesPath() : getGlobalGSDPreferencesPath();
192
+ await ensurePreferencesFile(path, ctx, scope);
193
+ await handlePrefsMode(ctx, scope);
194
+ return;
195
+ }
196
+
166
197
  if (trimmed === "prefs" || trimmed.startsWith("prefs ")) {
167
198
  await handlePrefs(trimmed.replace(/^prefs\s*/, "").trim(), ctx);
168
199
  return;
@@ -179,12 +210,16 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
179
210
  return;
180
211
  }
181
212
  const verboseMode = trimmed.includes("--verbose");
213
+ const debugMode = trimmed.includes("--debug");
214
+ if (debugMode) enableDebug(projectRoot());
182
215
  await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
183
216
  return;
184
217
  }
185
218
 
186
219
  if (trimmed === "auto" || trimmed.startsWith("auto ")) {
187
220
  const verboseMode = trimmed.includes("--verbose");
221
+ const debugMode = trimmed.includes("--debug");
222
+ if (debugMode) enableDebug(projectRoot());
188
223
  await startAuto(ctx, pi, projectRoot(), verboseMode);
189
224
  return;
190
225
  }
@@ -259,6 +294,21 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
259
294
  return;
260
295
  }
261
296
 
297
+ if (trimmed.startsWith("capture ") || trimmed === "capture") {
298
+ await handleCapture(trimmed.replace(/^capture\s*/, "").trim(), ctx);
299
+ return;
300
+ }
301
+
302
+ if (trimmed === "triage") {
303
+ await handleTriage(ctx, pi, process.cwd());
304
+ return;
305
+ }
306
+
307
+ if (trimmed === "quick" || trimmed.startsWith("quick ")) {
308
+ await handleQuick(trimmed.replace(/^quick\s*/, "").trim(), ctx, pi);
309
+ return;
310
+ }
311
+
262
312
  if (trimmed === "config") {
263
313
  await handleConfig(ctx);
264
314
  return;
@@ -270,6 +320,32 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
270
320
  return;
271
321
  }
272
322
 
323
+ // ─── Skill Health ────────────────────────────────────────────
324
+ if (trimmed === "skill-health" || trimmed.startsWith("skill-health ")) {
325
+ await handleSkillHealth(trimmed.replace(/^skill-health\s*/, "").trim(), ctx);
326
+ return;
327
+ }
328
+
329
+ if (trimmed.startsWith("run-hook ")) {
330
+ await handleRunHook(trimmed.replace(/^run-hook\s*/, "").trim(), ctx, pi);
331
+ return;
332
+ }
333
+ if (trimmed === "run-hook") {
334
+ ctx.ui.notify(`Usage: /gsd run-hook <hook-name> <unit-type> <unit-id>
335
+
336
+ Unit types:
337
+ execute-task - Task execution (unit-id: M001/S01/T01)
338
+ plan-slice - Slice planning (unit-id: M001/S01)
339
+ research-milestone - Milestone research (unit-id: M001)
340
+ complete-slice - Slice completion (unit-id: M001/S01)
341
+ complete-milestone - Milestone completion (unit-id: M001)
342
+
343
+ Examples:
344
+ /gsd run-hook code-review execute-task M001/S01/T01
345
+ /gsd run-hook lint-check plan-slice M001/S01`, "warning");
346
+ return;
347
+ }
348
+
273
349
  if (trimmed.startsWith("steer ")) {
274
350
  await handleSteer(trimmed.replace(/^steer\s+/, "").trim(), ctx, pi);
275
351
  return;
@@ -299,6 +375,11 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
299
375
  return;
300
376
  }
301
377
 
378
+ if (trimmed === "inspect") {
379
+ await handleInspect(ctx);
380
+ return;
381
+ }
382
+
302
383
  if (trimmed === "") {
303
384
  // Bare /gsd defaults to step mode
304
385
  await startAuto(ctx, pi, projectRoot(), false, { step: true });
@@ -306,13 +387,57 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
306
387
  }
307
388
 
308
389
  ctx.ui.notify(
309
- `Unknown: /gsd ${trimmed}. Use /gsd next|auto|stop|pause|status|queue|discuss|history|undo|skip <unit>|export|cleanup|prefs|config|hooks|doctor|migrate|remote|steer <change>|knowledge <type> <entry>.`,
390
+ `Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`,
310
391
  "warning",
311
392
  );
312
393
  },
313
394
  });
314
395
  }
315
396
 
397
+ function showHelp(ctx: ExtensionCommandContext): void {
398
+ const lines = [
399
+ "GSD — Get Shit Done\n",
400
+ "WORKFLOW",
401
+ " /gsd Run next unit in step mode (same as /gsd next)",
402
+ " /gsd next Execute next task, then pause [--dry-run] [--verbose]",
403
+ " /gsd auto Run all queued units continuously [--verbose]",
404
+ " /gsd stop Stop auto-mode gracefully",
405
+ " /gsd pause Pause auto-mode (preserves state, /gsd auto to resume)",
406
+ " /gsd discuss Start guided milestone/slice discussion",
407
+ "",
408
+ "VISIBILITY",
409
+ " /gsd status Show progress dashboard (Ctrl+Alt+G)",
410
+ " /gsd visualize Interactive 7-tab TUI (progress, deps, metrics, timeline, agent, changes, export)",
411
+ " /gsd queue Show queued/dispatched units and execution order",
412
+ " /gsd history View execution history [--cost] [--phase] [--model] [N]",
413
+ "",
414
+ "COURSE CORRECTION",
415
+ " /gsd steer <desc> Apply user override to active work",
416
+ " /gsd capture <text> Quick-capture a thought to CAPTURES.md",
417
+ " /gsd triage Classify and route pending captures",
418
+ " /gsd skip <unit> Prevent a unit from auto-mode dispatch",
419
+ " /gsd undo Revert last completed unit [--force]",
420
+ "",
421
+ "PROJECT KNOWLEDGE",
422
+ " /gsd knowledge <type> <text> Add rule, pattern, or lesson to KNOWLEDGE.md",
423
+ "",
424
+ "CONFIGURATION",
425
+ " /gsd mode Set workflow mode (solo/team) [global|project]",
426
+ " /gsd prefs Manage preferences [global|project|status|wizard|setup]",
427
+ " /gsd config Set API keys for external tools",
428
+ " /gsd hooks Show post-unit hook configuration",
429
+ "",
430
+ "MAINTENANCE",
431
+ " /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
432
+ " /gsd export Export milestone/slice results [--json|--markdown]",
433
+ " /gsd cleanup Remove merged branches or snapshots [branches|snapshots]",
434
+ " /gsd migrate Upgrade .gsd/ structures to new format",
435
+ " /gsd remote Control remote auto-mode [slack|discord|status|disconnect]",
436
+ " /gsd inspect Show SQLite DB diagnostics (schema, row counts, recent entries)",
437
+ ];
438
+ ctx.ui.notify(lines.join("\n"), "info");
439
+ }
440
+
316
441
  async function handleStatus(ctx: ExtensionCommandContext): Promise<void> {
317
442
  const basePath = projectRoot();
318
443
  const state = await deriveState(basePath);
@@ -344,6 +469,28 @@ export async function fireStatusViaCommand(
344
469
  await handleStatus(ctx as ExtensionCommandContext);
345
470
  }
346
471
 
472
+ async function handleVisualize(ctx: ExtensionCommandContext): Promise<void> {
473
+ if (!ctx.hasUI) {
474
+ ctx.ui.notify("Visualizer requires an interactive terminal.", "warning");
475
+ return;
476
+ }
477
+
478
+ await ctx.ui.custom<void>(
479
+ (tui, theme, _kb, done) => {
480
+ return new GSDVisualizerOverlay(tui, theme, () => done());
481
+ },
482
+ {
483
+ overlay: true,
484
+ overlayOptions: {
485
+ width: "80%",
486
+ minWidth: 80,
487
+ maxHeight: "90%",
488
+ anchor: "center",
489
+ },
490
+ },
491
+ );
492
+ }
493
+
347
494
  async function handlePrefs(args: string, ctx: ExtensionCommandContext): Promise<void> {
348
495
  const trimmed = args.trim();
349
496
 
@@ -393,6 +540,36 @@ async function handlePrefs(args: string, ctx: ExtensionCommandContext): Promise<
393
540
  ctx.ui.notify("Usage: /gsd prefs [global|project|status|wizard|setup]", "info");
394
541
  }
395
542
 
543
+ async function handlePrefsMode(ctx: ExtensionCommandContext, scope: "global" | "project"): Promise<void> {
544
+ const path = scope === "project" ? getProjectGSDPreferencesPath() : getGlobalGSDPreferencesPath();
545
+ const existing = scope === "project" ? loadProjectGSDPreferences() : loadGlobalGSDPreferences();
546
+ const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : {};
547
+
548
+ await configureMode(ctx, prefs);
549
+
550
+ // Serialize and save
551
+ prefs.version = prefs.version || 1;
552
+ const frontmatter = serializePreferencesToFrontmatter(prefs);
553
+
554
+ let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
555
+ if (existsSync(path)) {
556
+ const existingContent = readFileSync(path, "utf-8");
557
+ const closingIdx = existingContent.indexOf("\n---", existingContent.indexOf("---"));
558
+ if (closingIdx !== -1) {
559
+ const afterFrontmatter = existingContent.slice(closingIdx + 4);
560
+ if (afterFrontmatter.trim()) {
561
+ body = afterFrontmatter;
562
+ }
563
+ }
564
+ }
565
+
566
+ const content = `---\n${frontmatter}---${body}`;
567
+ await saveFile(path, content);
568
+ await ctx.waitForIdle();
569
+ await ctx.reload();
570
+ ctx.ui.notify(`Saved ${scope} preferences to ${path}`, "info");
571
+ }
572
+
396
573
  async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
397
574
  const trimmed = args.trim();
398
575
  const parts = trimmed ? trimmed.split(/\s+/) : [];
@@ -431,19 +608,220 @@ async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: Exte
431
608
  }
432
609
  }
433
610
 
611
+ // ─── Inspect ──────────────────────────────────────────────────────────────────
612
+
613
+ export interface InspectData {
614
+ schemaVersion: number | null;
615
+ counts: { decisions: number; requirements: number; artifacts: number };
616
+ recentDecisions: Array<{ id: string; decision: string; choice: string }>;
617
+ recentRequirements: Array<{ id: string; status: string; description: string }>;
618
+ }
619
+
620
+ export function formatInspectOutput(data: InspectData): string {
621
+ const lines: string[] = [];
622
+ lines.push("=== GSD Database Inspect ===");
623
+ lines.push(`Schema version: ${data.schemaVersion ?? "unknown"}`);
624
+ lines.push("");
625
+ lines.push(`Decisions: ${data.counts.decisions}`);
626
+ lines.push(`Requirements: ${data.counts.requirements}`);
627
+ lines.push(`Artifacts: ${data.counts.artifacts}`);
628
+
629
+ if (data.recentDecisions.length > 0) {
630
+ lines.push("");
631
+ lines.push("Recent decisions:");
632
+ for (const d of data.recentDecisions) {
633
+ lines.push(` ${d.id}: ${d.decision} → ${d.choice}`);
634
+ }
635
+ }
636
+
637
+ if (data.recentRequirements.length > 0) {
638
+ lines.push("");
639
+ lines.push("Recent requirements:");
640
+ for (const r of data.recentRequirements) {
641
+ lines.push(` ${r.id} [${r.status}]: ${r.description}`);
642
+ }
643
+ }
644
+
645
+ return lines.join("\n");
646
+ }
647
+
648
+ async function handleInspect(ctx: ExtensionCommandContext): Promise<void> {
649
+ try {
650
+ const { isDbAvailable, _getAdapter } = await import("./gsd-db.js");
651
+
652
+ if (!isDbAvailable()) {
653
+ ctx.ui.notify("No GSD database available. Run /gsd auto to create one.", "info");
654
+ return;
655
+ }
656
+
657
+ const adapter = _getAdapter();
658
+ if (!adapter) {
659
+ ctx.ui.notify("No GSD database available. Run /gsd auto to create one.", "info");
660
+ return;
661
+ }
662
+
663
+ const versionRow = adapter.prepare("SELECT MAX(version) as v FROM schema_version").get();
664
+ const schemaVersion = versionRow ? (versionRow["v"] as number | null) : null;
665
+
666
+ const dCount = adapter.prepare("SELECT count(*) as cnt FROM decisions").get();
667
+ const rCount = adapter.prepare("SELECT count(*) as cnt FROM requirements").get();
668
+ const aCount = adapter.prepare("SELECT count(*) as cnt FROM artifacts").get();
669
+
670
+ const recentDecisions = adapter
671
+ .prepare("SELECT id, decision, choice FROM decisions ORDER BY seq DESC LIMIT 5")
672
+ .all() as Array<{ id: string; decision: string; choice: string }>;
673
+
674
+ const recentRequirements = adapter
675
+ .prepare("SELECT id, status, description FROM requirements ORDER BY id DESC LIMIT 5")
676
+ .all() as Array<{ id: string; status: string; description: string }>;
677
+
678
+ const data: InspectData = {
679
+ schemaVersion,
680
+ counts: {
681
+ decisions: (dCount?.["cnt"] as number) ?? 0,
682
+ requirements: (rCount?.["cnt"] as number) ?? 0,
683
+ artifacts: (aCount?.["cnt"] as number) ?? 0,
684
+ },
685
+ recentDecisions,
686
+ recentRequirements,
687
+ };
688
+
689
+ ctx.ui.notify(formatInspectOutput(data), "info");
690
+ } catch (err) {
691
+ process.stderr.write(`gsd-db: /gsd inspect failed: ${err instanceof Error ? err.message : String(err)}\n`);
692
+ ctx.ui.notify("Failed to inspect GSD database. Check stderr for details.", "error");
693
+ }
694
+ }
695
+
696
+ // ─── Skill Health ─────────────────────────────────────────────────────────────
697
+
698
+ async function handleSkillHealth(args: string, ctx: ExtensionCommandContext): Promise<void> {
699
+ const {
700
+ generateSkillHealthReport,
701
+ formatSkillHealthReport,
702
+ formatSkillDetail,
703
+ } = await import("./skill-health.js");
704
+
705
+ const basePath = projectRoot();
706
+
707
+ // /gsd skill-health <skill-name> — detail view
708
+ if (args && !args.startsWith("--")) {
709
+ const detail = formatSkillDetail(basePath, args);
710
+ ctx.ui.notify(detail, "info");
711
+ return;
712
+ }
713
+
714
+ // Parse flags
715
+ const staleMatch = args.match(/--stale\s+(\d+)/);
716
+ const staleDays = staleMatch ? parseInt(staleMatch[1], 10) : undefined;
717
+ const decliningOnly = args.includes("--declining");
718
+
719
+ const report = generateSkillHealthReport(basePath, staleDays);
720
+
721
+ if (decliningOnly) {
722
+ if (report.decliningSkills.length === 0) {
723
+ ctx.ui.notify("No skills flagged for declining performance.", "info");
724
+ return;
725
+ }
726
+ const filtered = {
727
+ ...report,
728
+ skills: report.skills.filter(s => s.flagged),
729
+ };
730
+ ctx.ui.notify(formatSkillHealthReport(filtered), "info");
731
+ return;
732
+ }
733
+
734
+ ctx.ui.notify(formatSkillHealthReport(report), "info");
735
+ }
736
+
434
737
  // ─── Preferences Wizard ───────────────────────────────────────────────────────
435
738
 
436
- async function handlePrefsWizard(
437
- ctx: ExtensionCommandContext,
438
- scope: "global" | "project",
439
- ): Promise<void> {
440
- const path = scope === "project" ? getProjectGSDPreferencesPath() : getGlobalGSDPreferencesPath();
441
- const existing = scope === "project" ? loadProjectGSDPreferences() : loadGlobalGSDPreferences();
442
- const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : {};
739
+ /** Build short summary strings for each preference category. */
740
+ function buildCategorySummaries(prefs: Record<string, unknown>): Record<string, string> {
741
+ // Mode
742
+ const mode = prefs.mode as string | undefined;
743
+ const modeSummary = mode ?? "(not set)";
744
+
745
+ // Models
746
+ const models = prefs.models as Record<string, string> | undefined;
747
+ let modelsSummary = "(not configured)";
748
+ if (models && Object.keys(models).length > 0) {
749
+ const parts = Object.entries(models).map(([phase, model]) => `${phase}: ${model}`);
750
+ modelsSummary = parts.join(", ");
751
+ }
752
+
753
+ // Timeouts
754
+ const autoSup = prefs.auto_supervisor as Record<string, unknown> | undefined;
755
+ let timeoutsSummary = "(defaults)";
756
+ if (autoSup && Object.keys(autoSup).length > 0) {
757
+ const soft = autoSup.soft_timeout_minutes ?? "20";
758
+ const idle = autoSup.idle_timeout_minutes ?? "10";
759
+ const hard = autoSup.hard_timeout_minutes ?? "30";
760
+ timeoutsSummary = `soft: ${soft}m, idle: ${idle}m, hard: ${hard}m`;
761
+ }
762
+
763
+ // Git
764
+ const git = prefs.git as Record<string, unknown> | undefined;
765
+ let gitSummary = "(defaults)";
766
+ if (git && Object.keys(git).length > 0) {
767
+ const branch = git.main_branch ?? "main";
768
+ const push = git.auto_push ? "on" : "off";
769
+ gitSummary = `main: ${branch}, push: ${push}`;
770
+ }
771
+
772
+ // Skills
773
+ const discovery = prefs.skill_discovery as string | undefined;
774
+ const uat = prefs.uat_dispatch;
775
+ let skillsSummary = "(not configured)";
776
+ if (discovery || uat !== undefined) {
777
+ const parts: string[] = [];
778
+ if (discovery) parts.push(`discovery: ${discovery}`);
779
+ if (uat !== undefined) parts.push(`uat: ${uat}`);
780
+ skillsSummary = parts.join(", ");
781
+ }
782
+
783
+ // Budget
784
+ const ceiling = prefs.budget_ceiling;
785
+ const enforcement = prefs.budget_enforcement as string | undefined;
786
+ let budgetSummary = "(no limit)";
787
+ if (ceiling !== undefined) {
788
+ budgetSummary = `$${ceiling}`;
789
+ if (enforcement) budgetSummary += ` / ${enforcement}`;
790
+ } else if (enforcement) {
791
+ budgetSummary = enforcement;
792
+ }
793
+
794
+ // Notifications
795
+ const notif = prefs.notifications as Record<string, boolean> | undefined;
796
+ let notifSummary = "(defaults)";
797
+ if (notif && Object.keys(notif).length > 0) {
798
+ const allKeys = ["enabled", "on_complete", "on_error", "on_budget", "on_milestone", "on_attention"];
799
+ const enabledCount = allKeys.filter(k => notif[k] !== false).length;
800
+ notifSummary = `${enabledCount}/${allKeys.length} enabled`;
801
+ }
802
+
803
+ // Advanced
804
+ const uniqueIds = prefs.unique_milestone_ids;
805
+ let advancedSummary = "(defaults)";
806
+ if (uniqueIds !== undefined) {
807
+ advancedSummary = `unique IDs: ${uniqueIds ? "on" : "off"}`;
808
+ }
443
809
 
444
- ctx.ui.notify(`GSD preferences wizard (${scope}) — press Escape at any prompt to skip it.`, "info");
810
+ return {
811
+ mode: modeSummary,
812
+ models: modelsSummary,
813
+ timeouts: timeoutsSummary,
814
+ git: gitSummary,
815
+ skills: skillsSummary,
816
+ budget: budgetSummary,
817
+ notifications: notifSummary,
818
+ advanced: advancedSummary,
819
+ };
820
+ }
821
+
822
+ // ─── Category configuration functions ────────────────────────────────────────
445
823
 
446
- // ─── Models ──────────────────────────────────────────────────────────────
824
+ async function configureModels(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
447
825
  const modelPhases = ["research", "planning", "execution", "completion"] as const;
448
826
  const models: Record<string, string> = (prefs.models as Record<string, string>) ?? {};
449
827
 
@@ -466,7 +844,6 @@ async function handlePrefsWizard(
466
844
  }
467
845
  }
468
846
  } else {
469
- // No authenticated models available — fall back to text input
470
847
  for (const phase of modelPhases) {
471
848
  const current = models[phase] ?? "";
472
849
  const input = await ctx.ui.input(
@@ -486,8 +863,9 @@ async function handlePrefsWizard(
486
863
  if (Object.keys(models).length > 0) {
487
864
  prefs.models = models;
488
865
  }
866
+ }
489
867
 
490
- // ─── Auto-supervisor timeouts ────────────────────────────────────────────
868
+ async function configureTimeouts(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
491
869
  const autoSup: Record<string, unknown> = (prefs.auto_supervisor as Record<string, unknown>) ?? {};
492
870
  const timeoutFields = [
493
871
  { key: "soft_timeout_minutes", label: "Soft timeout (minutes)", defaultVal: "20" },
@@ -516,8 +894,9 @@ async function handlePrefsWizard(
516
894
  if (Object.keys(autoSup).length > 0) {
517
895
  prefs.auto_supervisor = autoSup;
518
896
  }
897
+ }
519
898
 
520
- // ─── Git settings ───────────────────────────────────────────────────────
899
+ async function configureGit(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
521
900
  const git: Record<string, unknown> = (prefs.git as Record<string, unknown>) ?? {};
522
901
 
523
902
  // main_branch
@@ -618,7 +997,7 @@ async function handlePrefsWizard(
618
997
  git.isolation = isolationChoice;
619
998
  }
620
999
 
621
- // ─── Git commit_docs ────────────────────────────────────────────────────
1000
+ // commit_docs
622
1001
  const currentCommitDocs = git.commit_docs;
623
1002
  const commitDocsChoice = await ctx.ui.select(
624
1003
  `Track .gsd/ planning docs in git${currentCommitDocs !== undefined ? ` (current: ${currentCommitDocs})` : ""}:`,
@@ -631,8 +1010,10 @@ async function handlePrefsWizard(
631
1010
  if (Object.keys(git).length > 0) {
632
1011
  prefs.git = git;
633
1012
  }
1013
+ }
634
1014
 
635
- // ─── Skill discovery mode ───────────────────────────────────────────────
1015
+ async function configureSkills(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
1016
+ // Skill discovery mode
636
1017
  const currentDiscovery = (prefs.skill_discovery as string) ?? "";
637
1018
  const discoveryChoice = await ctx.ui.select(
638
1019
  `Skill discovery mode${currentDiscovery ? ` (current: ${currentDiscovery})` : ""}:`,
@@ -642,17 +1023,18 @@ async function handlePrefsWizard(
642
1023
  prefs.skill_discovery = discoveryChoice;
643
1024
  }
644
1025
 
645
- // ─── Unique milestone IDs ──────────────────────────────────────────────
646
- const currentUnique = prefs.unique_milestone_ids;
647
- const uniqueChoice = await ctx.ui.select(
648
- `Unique milestone IDs${currentUnique !== undefined ? ` (current: ${currentUnique})` : ""}:`,
1026
+ // UAT dispatch
1027
+ const currentUat = prefs.uat_dispatch;
1028
+ const uatChoice = await ctx.ui.select(
1029
+ `UAT dispatch mode${currentUat !== undefined ? ` (current: ${currentUat})` : " (default: false)"}:`,
649
1030
  ["true", "false", "(keep current)"],
650
1031
  );
651
- if (uniqueChoice && uniqueChoice !== "(keep current)") {
652
- prefs.unique_milestone_ids = uniqueChoice === "true";
1032
+ if (uatChoice && uatChoice !== "(keep current)") {
1033
+ prefs.uat_dispatch = uatChoice === "true";
653
1034
  }
1035
+ }
654
1036
 
655
- // ─── Budget & cost control ────────────────────────────────────────────
1037
+ async function configureBudget(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
656
1038
  const currentCeiling = prefs.budget_ceiling;
657
1039
  const ceilingStr = currentCeiling !== undefined ? String(currentCeiling) : "";
658
1040
  const ceilingInput = await ctx.ui.input(
@@ -698,8 +1080,9 @@ async function handlePrefsWizard(
698
1080
  ctx.ui.notify(`Invalid context pause threshold "${val}" — must be 0-100. Keeping previous value.`, "warning");
699
1081
  }
700
1082
  }
1083
+ }
701
1084
 
702
- // ─── Notifications ────────────────────────────────────────────────────
1085
+ async function configureNotifications(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
703
1086
  const notif: Record<string, boolean> = (prefs.notifications as Record<string, boolean>) ?? {};
704
1087
  const notifFields = [
705
1088
  { key: "enabled", label: "Notifications enabled (master toggle)", defaultVal: true },
@@ -724,15 +1107,88 @@ async function handlePrefsWizard(
724
1107
  if (Object.keys(notif).length > 0) {
725
1108
  prefs.notifications = notif;
726
1109
  }
1110
+ }
727
1111
 
728
- // ─── UAT dispatch ─────────────────────────────────────────────────────
729
- const currentUat = prefs.uat_dispatch;
730
- const uatChoice = await ctx.ui.select(
731
- `UAT dispatch mode${currentUat !== undefined ? ` (current: ${currentUat})` : " (default: false)"}:`,
1112
+ async function configureMode(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
1113
+ const currentMode = prefs.mode as string | undefined;
1114
+ const modeChoice = await ctx.ui.select(
1115
+ `Workflow mode${currentMode ? ` (current: ${currentMode})` : ""}:`,
1116
+ [
1117
+ "solo — auto-push, squash, simple IDs (personal projects)",
1118
+ "team — unique IDs, push branches, pre-merge checks (shared repos)",
1119
+ "(none) — configure everything manually",
1120
+ "(keep current)",
1121
+ ],
1122
+ );
1123
+ const modeStr = typeof modeChoice === "string" ? modeChoice : "";
1124
+ if (modeStr && modeStr !== "(keep current)") {
1125
+ if (modeStr.startsWith("solo")) {
1126
+ prefs.mode = "solo";
1127
+ ctx.ui.notify(
1128
+ "Mode: solo — defaults: auto_push=true, push_branches=false, pre_merge_check=false, merge_strategy=squash, isolation=worktree, commit_docs=true, unique_milestone_ids=false",
1129
+ "info",
1130
+ );
1131
+ } else if (modeStr.startsWith("team")) {
1132
+ prefs.mode = "team";
1133
+ ctx.ui.notify(
1134
+ "Mode: team — defaults: auto_push=false, push_branches=true, pre_merge_check=true, merge_strategy=squash, isolation=worktree, commit_docs=true, unique_milestone_ids=true",
1135
+ "info",
1136
+ );
1137
+ } else {
1138
+ delete prefs.mode;
1139
+ }
1140
+ }
1141
+ }
1142
+
1143
+ async function configureAdvanced(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
1144
+ const currentUnique = prefs.unique_milestone_ids;
1145
+ const uniqueChoice = await ctx.ui.select(
1146
+ `Unique milestone IDs${currentUnique !== undefined ? ` (current: ${currentUnique})` : ""}:`,
732
1147
  ["true", "false", "(keep current)"],
733
1148
  );
734
- if (uatChoice && uatChoice !== "(keep current)") {
735
- prefs.uat_dispatch = uatChoice === "true";
1149
+ if (uniqueChoice && uniqueChoice !== "(keep current)") {
1150
+ prefs.unique_milestone_ids = uniqueChoice === "true";
1151
+ }
1152
+ }
1153
+
1154
+ // ─── Main wizard with category menu ─────────────────────────────────────────
1155
+
1156
+ async function handlePrefsWizard(
1157
+ ctx: ExtensionCommandContext,
1158
+ scope: "global" | "project",
1159
+ ): Promise<void> {
1160
+ const path = scope === "project" ? getProjectGSDPreferencesPath() : getGlobalGSDPreferencesPath();
1161
+ const existing = scope === "project" ? loadProjectGSDPreferences() : loadGlobalGSDPreferences();
1162
+ const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : {};
1163
+
1164
+ ctx.ui.notify(`GSD preferences (${scope}) — pick a category to configure.`, "info");
1165
+
1166
+ while (true) {
1167
+ const summaries = buildCategorySummaries(prefs);
1168
+ const options = [
1169
+ `Workflow Mode ${summaries.mode}`,
1170
+ `Models ${summaries.models}`,
1171
+ `Timeouts ${summaries.timeouts}`,
1172
+ `Git ${summaries.git}`,
1173
+ `Skills ${summaries.skills}`,
1174
+ `Budget ${summaries.budget}`,
1175
+ `Notifications ${summaries.notifications}`,
1176
+ `Advanced ${summaries.advanced}`,
1177
+ `── Save & Exit ──`,
1178
+ ];
1179
+
1180
+ const raw = await ctx.ui.select("GSD Preferences", options);
1181
+ const choice = typeof raw === "string" ? raw : "";
1182
+ if (!choice || choice.includes("Save & Exit")) break;
1183
+
1184
+ if (choice.startsWith("Workflow Mode")) await configureMode(ctx, prefs);
1185
+ else if (choice.startsWith("Models")) await configureModels(ctx, prefs);
1186
+ else if (choice.startsWith("Timeouts")) await configureTimeouts(ctx, prefs);
1187
+ else if (choice.startsWith("Git")) await configureGit(ctx, prefs);
1188
+ else if (choice.startsWith("Skills")) await configureSkills(ctx, prefs);
1189
+ else if (choice.startsWith("Budget")) await configureBudget(ctx, prefs);
1190
+ else if (choice.startsWith("Notifications")) await configureNotifications(ctx, prefs);
1191
+ else if (choice.startsWith("Advanced")) await configureAdvanced(ctx, prefs);
736
1192
  }
737
1193
 
738
1194
  // ─── Serialize to frontmatter ───────────────────────────────────────────
@@ -823,7 +1279,7 @@ function serializePreferencesToFrontmatter(prefs: Record<string, unknown>): stri
823
1279
 
824
1280
  // Ordered keys for consistent output
825
1281
  const orderedKeys = [
826
- "version", "always_use_skills", "prefer_skills", "avoid_skills",
1282
+ "version", "mode", "always_use_skills", "prefer_skills", "avoid_skills",
827
1283
  "skill_rules", "custom_instructions", "models", "skill_discovery",
828
1284
  "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
829
1285
  "budget_ceiling", "budget_enforcement", "context_pause_threshold",
@@ -1195,6 +1651,102 @@ async function handleKnowledge(args: string, ctx: ExtensionCommandContext): Prom
1195
1651
  ctx.ui.notify(`Added ${type} to KNOWLEDGE.md: "${entryText}"`, "success");
1196
1652
  }
1197
1653
 
1654
+ // ─── Capture Command ──────────────────────────────────────────────────────────
1655
+
1656
+ /**
1657
+ * Handle `/gsd capture "..."` — fire-and-forget thought capture.
1658
+ * Appends to `.gsd/CAPTURES.md` without interrupting auto-mode.
1659
+ * Works in all modes: auto running, paused, stopped, no project.
1660
+ */
1661
+ async function handleCapture(args: string, ctx: ExtensionCommandContext): Promise<void> {
1662
+ // Strip surrounding quotes from the argument
1663
+ let text = args.trim();
1664
+ if (!text) {
1665
+ ctx.ui.notify('Usage: /gsd capture "your thought here"', "warning");
1666
+ return;
1667
+ }
1668
+ // Remove wrapping quotes (single or double)
1669
+ if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
1670
+ text = text.slice(1, -1);
1671
+ }
1672
+ if (!text) {
1673
+ ctx.ui.notify('Usage: /gsd capture "your thought here"', "warning");
1674
+ return;
1675
+ }
1676
+
1677
+ const basePath = process.cwd();
1678
+
1679
+ // Ensure .gsd/ exists — capture should work even without a milestone
1680
+ const gsdDir = join(basePath, ".gsd");
1681
+ if (!existsSync(gsdDir)) {
1682
+ mkdirSync(gsdDir, { recursive: true });
1683
+ }
1684
+
1685
+ const id = appendCapture(basePath, text);
1686
+ ctx.ui.notify(`Captured: ${id} — "${text.length > 60 ? text.slice(0, 57) + "..." : text}"`, "info");
1687
+ }
1688
+
1689
+ // ─── Triage Command ───────────────────────────────────────────────────────────
1690
+
1691
+ /**
1692
+ * Handle `/gsd triage` — manually trigger triage of pending captures.
1693
+ * Dispatches the triage prompt to the LLM for classification.
1694
+ * Triage result handling (confirmation UI) is wired in T03.
1695
+ */
1696
+ async function handleTriage(ctx: ExtensionCommandContext, pi: ExtensionAPI, basePath: string): Promise<void> {
1697
+ if (!hasPendingCaptures(basePath)) {
1698
+ ctx.ui.notify("No pending captures to triage.", "info");
1699
+ return;
1700
+ }
1701
+
1702
+ const pending = loadPendingCaptures(basePath);
1703
+ ctx.ui.notify(`Triaging ${pending.length} pending capture${pending.length === 1 ? "" : "s"}...`, "info");
1704
+
1705
+ // Build context for the triage prompt
1706
+ const state = await deriveState(basePath);
1707
+ let currentPlan = "";
1708
+ let roadmapContext = "";
1709
+
1710
+ if (state.activeMilestone && state.activeSlice) {
1711
+ const { resolveSliceFile, resolveMilestoneFile } = await import("./paths.js");
1712
+ const planFile = resolveSliceFile(basePath, state.activeMilestone.id, state.activeSlice.id, "PLAN");
1713
+ if (planFile) {
1714
+ const { loadFile: load } = await import("./files.js");
1715
+ currentPlan = (await load(planFile)) ?? "";
1716
+ }
1717
+ const roadmapFile = resolveMilestoneFile(basePath, state.activeMilestone.id, "ROADMAP");
1718
+ if (roadmapFile) {
1719
+ const { loadFile: load } = await import("./files.js");
1720
+ roadmapContext = (await load(roadmapFile)) ?? "";
1721
+ }
1722
+ }
1723
+
1724
+ // Format pending captures for the prompt
1725
+ const capturesList = pending.map(c =>
1726
+ `- **${c.id}**: "${c.text}" (captured: ${c.timestamp})`
1727
+ ).join("\n");
1728
+
1729
+ // Dispatch triage prompt
1730
+ const { loadPrompt } = await import("./prompt-loader.js");
1731
+ const prompt = loadPrompt("triage-captures", {
1732
+ pendingCaptures: capturesList,
1733
+ currentPlan: currentPlan || "(no active slice plan)",
1734
+ roadmapContext: roadmapContext || "(no active roadmap)",
1735
+ });
1736
+
1737
+ const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
1738
+ const workflow = readFileSync(workflowPath, "utf-8");
1739
+
1740
+ pi.sendMessage(
1741
+ {
1742
+ customType: "gsd-triage",
1743
+ content: `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`,
1744
+ display: false,
1745
+ },
1746
+ { triggerTurn: true },
1747
+ );
1748
+ }
1749
+
1198
1750
  async function handleSteer(change: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
1199
1751
  const basePath = process.cwd();
1200
1752
  const state = await deriveState(basePath);
@@ -1237,3 +1789,69 @@ async function handleSteer(change: string, ctx: ExtensionCommandContext, pi: Ext
1237
1789
  ctx.ui.notify(`Override registered: "${change}". Update plan documents to reflect this change.`, "info");
1238
1790
  }
1239
1791
  }
1792
+
1793
+ async function handleRunHook(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
1794
+ const parts = args.trim().split(/\s+/);
1795
+ if (parts.length < 3) {
1796
+ ctx.ui.notify(`Usage: /gsd run-hook <hook-name> <unit-type> <unit-id>
1797
+
1798
+ Unit types:
1799
+ execute-task - Task execution (unit-id: M001/S01/T01)
1800
+ plan-slice - Slice planning (unit-id: M001/S01)
1801
+ research-milestone - Milestone research (unit-id: M001)
1802
+ complete-slice - Slice completion (unit-id: M001/S01)
1803
+ complete-milestone - Milestone completion (unit-id: M001)
1804
+
1805
+ Examples:
1806
+ /gsd run-hook code-review execute-task M001/S01/T01
1807
+ /gsd run-hook lint-check plan-slice M001/S01`, "warning");
1808
+ return;
1809
+ }
1810
+
1811
+ const [hookName, unitType, unitId] = parts;
1812
+ const basePath = projectRoot();
1813
+
1814
+ // Import the hook trigger function
1815
+ const { triggerHookManually, formatHookStatus, getHookStatus } = await import("./post-unit-hooks.js");
1816
+ const { dispatchHookUnit } = await import("./auto.js");
1817
+
1818
+ // Check if the hook exists
1819
+ const hooks = getHookStatus();
1820
+ const hookExists = hooks.some(h => h.name === hookName);
1821
+ if (!hookExists) {
1822
+ ctx.ui.notify(`Hook "${hookName}" not found. Configured hooks:\n${formatHookStatus()}`, "error");
1823
+ return;
1824
+ }
1825
+
1826
+ // Validate unit ID format
1827
+ const unitIdPattern = /^M\d{3}\/S\d{2,3}\/T\d{2,3}$/;
1828
+ if (!unitIdPattern.test(unitId)) {
1829
+ ctx.ui.notify(`Invalid unit ID format: "${unitId}". Expected format: M004/S04/T03`, "warning");
1830
+ return;
1831
+ }
1832
+
1833
+ // Trigger the hook manually
1834
+ const hookUnit = triggerHookManually(hookName, unitType, unitId, basePath);
1835
+ if (!hookUnit) {
1836
+ ctx.ui.notify(`Failed to trigger hook "${hookName}". The hook may be disabled or not configured for unit type "${unitType}".`, "error");
1837
+ return;
1838
+ }
1839
+
1840
+ ctx.ui.notify(`Manually triggering hook: ${hookName} for ${unitType} ${unitId}`, "info");
1841
+
1842
+ // Dispatch the hook unit directly, bypassing normal pre-dispatch hooks
1843
+ const success = await dispatchHookUnit(
1844
+ ctx,
1845
+ pi,
1846
+ hookName,
1847
+ unitType,
1848
+ unitId,
1849
+ hookUnit.prompt,
1850
+ hookUnit.model,
1851
+ basePath,
1852
+ );
1853
+
1854
+ if (!success) {
1855
+ ctx.ui.notify("Failed to dispatch hook. Auto-mode may have been cancelled.", "error");
1856
+ }
1857
+ }