gsd-pi 2.19.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 (249) 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-prompts.ts +103 -24
  8. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  9. package/dist/resources/extensions/gsd/auto.ts +424 -30
  10. package/dist/resources/extensions/gsd/commands.ts +518 -36
  11. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  12. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  13. package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  14. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  15. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  16. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  17. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  19. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  20. package/dist/resources/extensions/gsd/export.ts +81 -2
  21. package/dist/resources/extensions/gsd/files.ts +39 -9
  22. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  23. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  24. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  25. package/dist/resources/extensions/gsd/history.ts +0 -1
  26. package/dist/resources/extensions/gsd/index.ts +277 -1
  27. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  28. package/dist/resources/extensions/gsd/metrics.ts +39 -3
  29. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  30. package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  31. package/dist/resources/extensions/gsd/preferences.ts +125 -150
  32. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  33. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  34. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  35. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  36. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  37. package/dist/resources/extensions/gsd/quick.ts +156 -0
  38. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  39. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  40. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  41. package/dist/resources/extensions/gsd/state.ts +30 -0
  42. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  43. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  44. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  45. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  46. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  47. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  48. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  49. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  50. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  51. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  52. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  53. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  54. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  55. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  56. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  57. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  58. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  59. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  60. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  61. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  62. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  63. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  64. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  65. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  66. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  67. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  68. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  69. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  70. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  71. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  72. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  73. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  74. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  75. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  76. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  77. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  78. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  79. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  80. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  81. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  82. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  83. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  84. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  85. package/dist/resources/extensions/gsd/types.ts +29 -0
  86. package/dist/resources/extensions/gsd/undo.ts +0 -1
  87. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  88. package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
  89. package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  90. package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
  91. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  92. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  93. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  94. package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  95. package/dist/resources/extensions/remote-questions/format.ts +154 -8
  96. package/dist/resources/extensions/remote-questions/manager.ts +9 -7
  97. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  98. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  99. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  100. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  101. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  102. package/dist/resources/extensions/voice/index.ts +4 -3
  103. package/package.json +1 -1
  104. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  106. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  109. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  113. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  117. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  119. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  120. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  122. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  124. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  126. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  128. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  130. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  133. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  136. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  138. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  141. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  143. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  144. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  145. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  146. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  147. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  148. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  149. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  150. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  151. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  152. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  153. package/src/resources/extensions/google-search/index.ts +164 -47
  154. package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
  155. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  156. package/src/resources/extensions/gsd/auto.ts +424 -30
  157. package/src/resources/extensions/gsd/commands.ts +518 -36
  158. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  159. package/src/resources/extensions/gsd/context-store.ts +195 -0
  160. package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  161. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  162. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  163. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  164. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  165. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  166. package/src/resources/extensions/gsd/doctor.ts +283 -2
  167. package/src/resources/extensions/gsd/export.ts +81 -2
  168. package/src/resources/extensions/gsd/files.ts +39 -9
  169. package/src/resources/extensions/gsd/git-service.ts +6 -0
  170. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  171. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  172. package/src/resources/extensions/gsd/history.ts +0 -1
  173. package/src/resources/extensions/gsd/index.ts +277 -1
  174. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  175. package/src/resources/extensions/gsd/metrics.ts +39 -3
  176. package/src/resources/extensions/gsd/notifications.ts +0 -1
  177. package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  178. package/src/resources/extensions/gsd/preferences.ts +125 -150
  179. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  180. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  181. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  182. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  183. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  184. package/src/resources/extensions/gsd/quick.ts +156 -0
  185. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  186. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  187. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  188. package/src/resources/extensions/gsd/state.ts +30 -0
  189. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  190. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  191. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  192. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  193. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  194. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  195. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  196. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  197. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  198. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  199. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  200. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  201. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  202. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  203. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  204. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  205. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  206. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  207. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  208. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  209. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  210. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  211. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  212. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  213. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  214. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  216. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  217. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  218. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  219. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  220. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  221. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  222. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  223. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  224. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  225. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  226. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  227. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  228. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  229. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  230. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  231. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  232. package/src/resources/extensions/gsd/types.ts +29 -0
  233. package/src/resources/extensions/gsd/undo.ts +0 -1
  234. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  235. package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
  236. package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  237. package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
  238. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  239. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  240. package/src/resources/extensions/remote-questions/config.ts +4 -2
  241. package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  242. package/src/resources/extensions/remote-questions/format.ts +154 -8
  243. package/src/resources/extensions/remote-questions/manager.ts +9 -7
  244. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  245. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  246. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  247. package/src/resources/extensions/remote-questions/types.ts +2 -1
  248. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  249. package/src/resources/extensions/voice/index.ts +4 -3
@@ -8,6 +8,7 @@ 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";
@@ -36,12 +37,13 @@ import {
36
37
  import { loadPrompt } from "./prompt-loader.js";
37
38
 
38
39
  import { handleRemote } from "../remote-questions/remote-command.js";
40
+ import { handleQuick } from "./quick.js";
39
41
  import { handleHistory } from "./history.js";
40
42
  import { handleUndo } from "./undo.js";
41
43
  import { handleExport } from "./export.js";
42
44
  import { nativeBranchList, nativeDetectMainBranch, nativeBranchListMerged, nativeBranchDelete, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
43
45
 
44
- 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 {
45
47
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
46
48
  const workflow = readFileSync(workflowPath, "utf-8");
47
49
  const prompt = loadPrompt("doctor-heal", {
@@ -66,13 +68,13 @@ function projectRoot(): string {
66
68
 
67
69
  export function registerGSDCommand(pi: ExtensionAPI): void {
68
70
  pi.registerCommand("gsd", {
69
- description: "GSD — Get Shit Done: /gsd next|auto|stop|pause|status|visualize|queue|capture|triage|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",
70
72
  getArgumentCompletions: (prefix: string) => {
71
73
  const subcommands = [
72
- "next", "auto", "stop", "pause", "status", "visualize", "queue", "discuss",
74
+ "help", "next", "auto", "stop", "pause", "status", "visualize", "queue", "quick", "discuss",
73
75
  "capture", "triage",
74
- "history", "undo", "skip", "export", "cleanup", "prefs",
75
- "config", "hooks", "doctor", "migrate", "remote", "steer", "knowledge",
76
+ "history", "undo", "skip", "export", "cleanup", "mode", "prefs",
77
+ "config", "hooks", "run-hook", "skill-health", "doctor", "migrate", "remote", "steer", "inspect", "knowledge",
76
78
  ];
77
79
  const parts = prefix.trim().split(/\s+/);
78
80
 
@@ -84,11 +86,18 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
84
86
 
85
87
  if (parts[0] === "auto" && parts.length <= 2) {
86
88
  const flagPrefix = parts[1] ?? "";
87
- return ["--verbose"]
89
+ return ["--verbose", "--debug"]
88
90
  .filter((f) => f.startsWith(flagPrefix))
89
91
  .map((f) => ({ value: `auto ${f}`, label: f }));
90
92
  }
91
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
+
92
101
  if (parts[0] === "prefs" && parts.length <= 2) {
93
102
  const subPrefix = parts[1] ?? "";
94
103
  return ["global", "project", "status", "wizard", "setup"]
@@ -161,6 +170,11 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
161
170
  async handler(args: string, ctx: ExtensionCommandContext) {
162
171
  const trimmed = (typeof args === "string" ? args : "").trim();
163
172
 
173
+ if (trimmed === "help" || trimmed === "h" || trimmed === "?") {
174
+ showHelp(ctx);
175
+ return;
176
+ }
177
+
164
178
  if (trimmed === "status") {
165
179
  await handleStatus(ctx);
166
180
  return;
@@ -171,6 +185,15 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
171
185
  return;
172
186
  }
173
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
+
174
197
  if (trimmed === "prefs" || trimmed.startsWith("prefs ")) {
175
198
  await handlePrefs(trimmed.replace(/^prefs\s*/, "").trim(), ctx);
176
199
  return;
@@ -187,12 +210,16 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
187
210
  return;
188
211
  }
189
212
  const verboseMode = trimmed.includes("--verbose");
213
+ const debugMode = trimmed.includes("--debug");
214
+ if (debugMode) enableDebug(projectRoot());
190
215
  await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
191
216
  return;
192
217
  }
193
218
 
194
219
  if (trimmed === "auto" || trimmed.startsWith("auto ")) {
195
220
  const verboseMode = trimmed.includes("--verbose");
221
+ const debugMode = trimmed.includes("--debug");
222
+ if (debugMode) enableDebug(projectRoot());
196
223
  await startAuto(ctx, pi, projectRoot(), verboseMode);
197
224
  return;
198
225
  }
@@ -277,6 +304,11 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
277
304
  return;
278
305
  }
279
306
 
307
+ if (trimmed === "quick" || trimmed.startsWith("quick ")) {
308
+ await handleQuick(trimmed.replace(/^quick\s*/, "").trim(), ctx, pi);
309
+ return;
310
+ }
311
+
280
312
  if (trimmed === "config") {
281
313
  await handleConfig(ctx);
282
314
  return;
@@ -288,6 +320,32 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
288
320
  return;
289
321
  }
290
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
+
291
349
  if (trimmed.startsWith("steer ")) {
292
350
  await handleSteer(trimmed.replace(/^steer\s+/, "").trim(), ctx, pi);
293
351
  return;
@@ -317,6 +375,11 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
317
375
  return;
318
376
  }
319
377
 
378
+ if (trimmed === "inspect") {
379
+ await handleInspect(ctx);
380
+ return;
381
+ }
382
+
320
383
  if (trimmed === "") {
321
384
  // Bare /gsd defaults to step mode
322
385
  await startAuto(ctx, pi, projectRoot(), false, { step: true });
@@ -324,13 +387,57 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
324
387
  }
325
388
 
326
389
  ctx.ui.notify(
327
- `Unknown: /gsd ${trimmed}. Use /gsd next|auto|stop|pause|status|visualize|queue|capture|triage|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.`,
328
391
  "warning",
329
392
  );
330
393
  },
331
394
  });
332
395
  }
333
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
+
334
441
  async function handleStatus(ctx: ExtensionCommandContext): Promise<void> {
335
442
  const basePath = projectRoot();
336
443
  const state = await deriveState(basePath);
@@ -433,6 +540,36 @@ async function handlePrefs(args: string, ctx: ExtensionCommandContext): Promise<
433
540
  ctx.ui.notify("Usage: /gsd prefs [global|project|status|wizard|setup]", "info");
434
541
  }
435
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
+
436
573
  async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
437
574
  const trimmed = args.trim();
438
575
  const parts = trimmed ? trimmed.split(/\s+/) : [];
@@ -471,19 +608,220 @@ async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: Exte
471
608
  }
472
609
  }
473
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
+
474
737
  // ─── Preferences Wizard ───────────────────────────────────────────────────────
475
738
 
476
- async function handlePrefsWizard(
477
- ctx: ExtensionCommandContext,
478
- scope: "global" | "project",
479
- ): Promise<void> {
480
- const path = scope === "project" ? getProjectGSDPreferencesPath() : getGlobalGSDPreferencesPath();
481
- const existing = scope === "project" ? loadProjectGSDPreferences() : loadGlobalGSDPreferences();
482
- 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
+ }
483
809
 
484
- 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 ────────────────────────────────────────
485
823
 
486
- // ─── Models ──────────────────────────────────────────────────────────────
824
+ async function configureModels(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
487
825
  const modelPhases = ["research", "planning", "execution", "completion"] as const;
488
826
  const models: Record<string, string> = (prefs.models as Record<string, string>) ?? {};
489
827
 
@@ -506,7 +844,6 @@ async function handlePrefsWizard(
506
844
  }
507
845
  }
508
846
  } else {
509
- // No authenticated models available — fall back to text input
510
847
  for (const phase of modelPhases) {
511
848
  const current = models[phase] ?? "";
512
849
  const input = await ctx.ui.input(
@@ -526,8 +863,9 @@ async function handlePrefsWizard(
526
863
  if (Object.keys(models).length > 0) {
527
864
  prefs.models = models;
528
865
  }
866
+ }
529
867
 
530
- // ─── Auto-supervisor timeouts ────────────────────────────────────────────
868
+ async function configureTimeouts(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
531
869
  const autoSup: Record<string, unknown> = (prefs.auto_supervisor as Record<string, unknown>) ?? {};
532
870
  const timeoutFields = [
533
871
  { key: "soft_timeout_minutes", label: "Soft timeout (minutes)", defaultVal: "20" },
@@ -556,8 +894,9 @@ async function handlePrefsWizard(
556
894
  if (Object.keys(autoSup).length > 0) {
557
895
  prefs.auto_supervisor = autoSup;
558
896
  }
897
+ }
559
898
 
560
- // ─── Git settings ───────────────────────────────────────────────────────
899
+ async function configureGit(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
561
900
  const git: Record<string, unknown> = (prefs.git as Record<string, unknown>) ?? {};
562
901
 
563
902
  // main_branch
@@ -658,7 +997,7 @@ async function handlePrefsWizard(
658
997
  git.isolation = isolationChoice;
659
998
  }
660
999
 
661
- // ─── Git commit_docs ────────────────────────────────────────────────────
1000
+ // commit_docs
662
1001
  const currentCommitDocs = git.commit_docs;
663
1002
  const commitDocsChoice = await ctx.ui.select(
664
1003
  `Track .gsd/ planning docs in git${currentCommitDocs !== undefined ? ` (current: ${currentCommitDocs})` : ""}:`,
@@ -671,8 +1010,10 @@ async function handlePrefsWizard(
671
1010
  if (Object.keys(git).length > 0) {
672
1011
  prefs.git = git;
673
1012
  }
1013
+ }
674
1014
 
675
- // ─── Skill discovery mode ───────────────────────────────────────────────
1015
+ async function configureSkills(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
1016
+ // Skill discovery mode
676
1017
  const currentDiscovery = (prefs.skill_discovery as string) ?? "";
677
1018
  const discoveryChoice = await ctx.ui.select(
678
1019
  `Skill discovery mode${currentDiscovery ? ` (current: ${currentDiscovery})` : ""}:`,
@@ -682,17 +1023,18 @@ async function handlePrefsWizard(
682
1023
  prefs.skill_discovery = discoveryChoice;
683
1024
  }
684
1025
 
685
- // ─── Unique milestone IDs ──────────────────────────────────────────────
686
- const currentUnique = prefs.unique_milestone_ids;
687
- const uniqueChoice = await ctx.ui.select(
688
- `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)"}:`,
689
1030
  ["true", "false", "(keep current)"],
690
1031
  );
691
- if (uniqueChoice && uniqueChoice !== "(keep current)") {
692
- prefs.unique_milestone_ids = uniqueChoice === "true";
1032
+ if (uatChoice && uatChoice !== "(keep current)") {
1033
+ prefs.uat_dispatch = uatChoice === "true";
693
1034
  }
1035
+ }
694
1036
 
695
- // ─── Budget & cost control ────────────────────────────────────────────
1037
+ async function configureBudget(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
696
1038
  const currentCeiling = prefs.budget_ceiling;
697
1039
  const ceilingStr = currentCeiling !== undefined ? String(currentCeiling) : "";
698
1040
  const ceilingInput = await ctx.ui.input(
@@ -738,8 +1080,9 @@ async function handlePrefsWizard(
738
1080
  ctx.ui.notify(`Invalid context pause threshold "${val}" — must be 0-100. Keeping previous value.`, "warning");
739
1081
  }
740
1082
  }
1083
+ }
741
1084
 
742
- // ─── Notifications ────────────────────────────────────────────────────
1085
+ async function configureNotifications(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
743
1086
  const notif: Record<string, boolean> = (prefs.notifications as Record<string, boolean>) ?? {};
744
1087
  const notifFields = [
745
1088
  { key: "enabled", label: "Notifications enabled (master toggle)", defaultVal: true },
@@ -764,15 +1107,88 @@ async function handlePrefsWizard(
764
1107
  if (Object.keys(notif).length > 0) {
765
1108
  prefs.notifications = notif;
766
1109
  }
1110
+ }
767
1111
 
768
- // ─── UAT dispatch ─────────────────────────────────────────────────────
769
- const currentUat = prefs.uat_dispatch;
770
- const uatChoice = await ctx.ui.select(
771
- `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})` : ""}:`,
772
1147
  ["true", "false", "(keep current)"],
773
1148
  );
774
- if (uatChoice && uatChoice !== "(keep current)") {
775
- 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);
776
1192
  }
777
1193
 
778
1194
  // ─── Serialize to frontmatter ───────────────────────────────────────────
@@ -863,7 +1279,7 @@ function serializePreferencesToFrontmatter(prefs: Record<string, unknown>): stri
863
1279
 
864
1280
  // Ordered keys for consistent output
865
1281
  const orderedKeys = [
866
- "version", "always_use_skills", "prefer_skills", "avoid_skills",
1282
+ "version", "mode", "always_use_skills", "prefer_skills", "avoid_skills",
867
1283
  "skill_rules", "custom_instructions", "models", "skill_discovery",
868
1284
  "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
869
1285
  "budget_ceiling", "budget_enforcement", "context_pause_threshold",
@@ -1373,3 +1789,69 @@ async function handleSteer(change: string, ctx: ExtensionCommandContext, pi: Ext
1373
1789
  ctx.ui.notify(`Override registered: "${change}". Update plan documents to reflect this change.`, "info");
1374
1790
  }
1375
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
+ }