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
@@ -0,0 +1,156 @@
1
+ /**
2
+ * GSD Quick Mode — /gsd quick <task>
3
+ * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
4
+ *
5
+ * Lightweight task execution with GSD guarantees (atomic commits, state
6
+ * tracking) but without the full milestone/slice ceremony.
7
+ *
8
+ * Quick tasks live in `.gsd/quick/` and are tracked in STATE.md's
9
+ * "Quick Tasks Completed" table.
10
+ */
11
+
12
+ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
13
+ import { existsSync, mkdirSync, readdirSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { loadPrompt } from "./prompt-loader.js";
16
+ import { gsdRoot } from "./paths.js";
17
+ import { GitServiceImpl, runGit } from "./git-service.js";
18
+ import { loadEffectiveGSDPreferences } from "./preferences.js";
19
+
20
+ // ─── Quick Task Helpers ───────────────────────────────────────────────────────
21
+
22
+ /**
23
+ * Generate a URL-friendly slug from a description.
24
+ * Lowercase, hyphens, max 40 chars.
25
+ */
26
+ function slugify(text: string): string {
27
+ return text
28
+ .toLowerCase()
29
+ .replace(/[^a-z0-9]+/g, "-")
30
+ .replace(/^-|-$/g, "")
31
+ .slice(0, 40)
32
+ .replace(/-$/, "");
33
+ }
34
+
35
+ /**
36
+ * Determine the next quick task number by scanning existing directories.
37
+ */
38
+ function getNextTaskNum(quickDir: string): number {
39
+ if (!existsSync(quickDir)) return 1;
40
+ try {
41
+ const entries = readdirSync(quickDir, { withFileTypes: true });
42
+ let max = 0;
43
+ for (const entry of entries) {
44
+ if (!entry.isDirectory()) continue;
45
+ const match = entry.name.match(/^(\d+)-/);
46
+ if (match) {
47
+ const num = parseInt(match[1], 10);
48
+ if (num > max) max = num;
49
+ }
50
+ }
51
+ return max + 1;
52
+ } catch {
53
+ return 1;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Ensure the quick task directory structure exists.
59
+ * Returns the task directory path.
60
+ */
61
+ function ensureQuickDir(basePath: string, taskNum: number, slug: string): string {
62
+ const quickDir = join(gsdRoot(basePath), "quick");
63
+ const taskDir = join(quickDir, `${taskNum}-${slug}`);
64
+ mkdirSync(taskDir, { recursive: true });
65
+ return taskDir;
66
+ }
67
+
68
+ // ─── Main Handler ─────────────────────────────────────────────────────────────
69
+
70
+ export async function handleQuick(
71
+ args: string,
72
+ ctx: ExtensionCommandContext,
73
+ pi: ExtensionAPI,
74
+ ): Promise<void> {
75
+ const basePath = process.cwd();
76
+ const root = gsdRoot(basePath);
77
+
78
+ // Validate: .gsd/ must exist
79
+ if (!existsSync(root)) {
80
+ ctx.ui.notify(
81
+ "No .gsd/ directory found. Run /gsd to initialize a project first.",
82
+ "error",
83
+ );
84
+ return;
85
+ }
86
+
87
+ // Parse description from args
88
+ let description = args.trim();
89
+ if (!description) {
90
+ ctx.ui.notify(
91
+ "Usage: /gsd quick <task description>\n\nExample: /gsd quick fix login button not responding on mobile",
92
+ "info",
93
+ );
94
+ return;
95
+ }
96
+
97
+ // Setup
98
+ const quickDir = join(root, "quick");
99
+ const taskNum = getNextTaskNum(quickDir);
100
+ const slug = slugify(description);
101
+ const taskDir = ensureQuickDir(basePath, taskNum, slug);
102
+ const taskDirRel = `.gsd/quick/${taskNum}-${slug}`;
103
+ const date = new Date().toISOString().split("T")[0];
104
+
105
+ // Create git branch for the quick task
106
+ const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
107
+ const git = new GitServiceImpl(basePath, gitPrefs);
108
+ const branchName = `gsd/quick/${taskNum}-${slug}`;
109
+
110
+ let branchCreated = false;
111
+ try {
112
+ const current = git.getCurrentBranch();
113
+ if (current !== branchName) {
114
+ // Auto-commit any dirty state before switching
115
+ try {
116
+ git.autoCommit("quick-task", `Q${taskNum}`, []);
117
+ } catch { /* nothing to commit — fine */ }
118
+
119
+ runGit(basePath, ["checkout", "-b", branchName]);
120
+ branchCreated = true;
121
+ }
122
+ } catch (err) {
123
+ // Branch creation failed — continue on current branch
124
+ const message = err instanceof Error ? err.message : String(err);
125
+ ctx.ui.notify(`Could not create branch ${branchName}: ${message}. Working on current branch.`, "warning");
126
+ }
127
+
128
+ const actualBranch = branchCreated ? branchName : git.getCurrentBranch();
129
+
130
+ // Notify user
131
+ ctx.ui.notify(
132
+ `Quick task ${taskNum}: ${description}\nDirectory: ${taskDirRel}\nBranch: ${actualBranch}`,
133
+ "info",
134
+ );
135
+
136
+ // Build and dispatch the quick task prompt
137
+ const summaryPath = `${taskDirRel}/${taskNum}-SUMMARY.md`;
138
+ const prompt = loadPrompt("quick-task", {
139
+ description,
140
+ taskDir: taskDirRel,
141
+ branch: actualBranch,
142
+ summaryPath,
143
+ date,
144
+ taskNum: String(taskNum),
145
+ slug,
146
+ });
147
+
148
+ pi.sendMessage(
149
+ {
150
+ customType: "gsd-quick-task",
151
+ content: prompt,
152
+ display: false,
153
+ },
154
+ { triggerTurn: true },
155
+ );
156
+ }
@@ -110,10 +110,12 @@ function listSkillDirs(): string[] {
110
110
  function parseSkillFrontmatter(path: string): { name?: string; description?: string } | null {
111
111
  try {
112
112
  const content = readFileSync(path, "utf-8");
113
- const match = content.match(/^---\n([\s\S]*?)\n---/);
114
- if (!match) return null;
113
+ // Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468)
114
+ if (!content.startsWith('---\n')) return null;
115
+ const endIdx = content.indexOf('\n---', 4);
116
+ if (endIdx === -1) return null;
115
117
 
116
- const fm = match[1];
118
+ const fm = content.slice(4, endIdx);
117
119
  const result: { name?: string; description?: string } = {};
118
120
 
119
121
  const nameMatch = fm.match(/^name:\s*(.+)$/m);
@@ -0,0 +1,417 @@
1
+ /**
2
+ * GSD Skill Health — Dashboard, Staleness, and Heal-Skill Integration (#599)
3
+ *
4
+ * Aggregates skill telemetry from metrics.json to surface:
5
+ * - Per-skill pass/fail rates, token usage, and trends
6
+ * - Staleness warnings for unused skills
7
+ * - Declining performance flags
8
+ * - Heal-skill suggestions (inspired by glittercowboy's heal-skill command)
9
+ *
10
+ * The heal-skill concept: when an agent deviates from what a skill recommends
11
+ * during execution, detect the drift and propose specific fixes with user
12
+ * approval before applying. This closes the feedback loop that SkillsBench
13
+ * research identified as critical for skill quality.
14
+ */
15
+
16
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { getAgentDir } from "@gsd/pi-coding-agent";
19
+ import type { UnitMetrics, MetricsLedger } from "./metrics.js";
20
+ import { formatCost, formatTokenCount, loadLedgerFromDisk } from "./metrics.js";
21
+ import { getSkillLastUsed, detectStaleSkills } from "./skill-telemetry.js";
22
+
23
+ // ─── Types ────────────────────────────────────────────────────────────────────
24
+
25
+ export interface SkillHealthEntry {
26
+ name: string;
27
+ totalUses: number;
28
+ /** Success rate: units with this skill that completed without retry */
29
+ successRate: number;
30
+ /** Average tokens per unit when this skill is loaded */
31
+ avgTokens: number;
32
+ /** Token trend over recent uses */
33
+ tokenTrend: "stable" | "rising" | "declining";
34
+ /** Timestamp of most recent use */
35
+ lastUsed: number;
36
+ /** Days since last use */
37
+ staleDays: number;
38
+ /** Average cost per unit when this skill is loaded */
39
+ avgCost: number;
40
+ /** Whether this skill is flagged for review */
41
+ flagged: boolean;
42
+ /** Reason for flag, if any */
43
+ flagReason?: string;
44
+ }
45
+
46
+ export interface SkillHealthReport {
47
+ generatedAt: string;
48
+ totalUnitsWithSkills: number;
49
+ skills: SkillHealthEntry[];
50
+ staleSkills: string[];
51
+ decliningSkills: string[];
52
+ suggestions: SkillHealSuggestion[];
53
+ }
54
+
55
+ export interface SkillHealSuggestion {
56
+ skillName: string;
57
+ trigger: "declining_success" | "rising_tokens" | "high_retry_rate" | "stale";
58
+ message: string;
59
+ severity: "info" | "warning" | "critical";
60
+ }
61
+
62
+ // ─── Constants ────────────────────────────────────────────────────────────────
63
+
64
+ /** Default staleness threshold in days */
65
+ const DEFAULT_STALE_DAYS = 60;
66
+
67
+ /** Success rate below this triggers a flag */
68
+ const SUCCESS_RATE_THRESHOLD = 0.70;
69
+
70
+ /** Token increase percentage that triggers a "rising" flag */
71
+ const TOKEN_RISE_THRESHOLD = 0.20;
72
+
73
+ /** Minimum uses before trend analysis kicks in */
74
+ const MIN_USES_FOR_TREND = 5;
75
+
76
+ /** Window size for trend comparison (compare last N to previous N) */
77
+ const TREND_WINDOW = 5;
78
+
79
+ // ─── Public API ───────────────────────────────────────────────────────────────
80
+
81
+ /**
82
+ * Generate a full skill health report from metrics data.
83
+ */
84
+ export function generateSkillHealthReport(basePath: string, staleDays?: number): SkillHealthReport {
85
+ const ledger = loadLedgerFromDisk(basePath);
86
+ const unitsWithSkills = (ledger?.units ?? []).filter(u => u.skills && u.skills.length > 0);
87
+ const threshold = staleDays ?? DEFAULT_STALE_DAYS;
88
+
89
+ const skillMap = aggregateBySkill(unitsWithSkills);
90
+ const skills = Array.from(skillMap.values()).sort((a, b) => b.totalUses - a.totalUses);
91
+ const staleSkills = detectStaleSkills(unitsWithSkills, threshold);
92
+ const decliningSkills = skills.filter(s => s.flagged).map(s => s.name);
93
+ const suggestions = generateSuggestions(skills, staleSkills);
94
+
95
+ return {
96
+ generatedAt: new Date().toISOString(),
97
+ totalUnitsWithSkills: unitsWithSkills.length,
98
+ skills,
99
+ staleSkills,
100
+ decliningSkills,
101
+ suggestions,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Format a skill health report for terminal display.
107
+ */
108
+ export function formatSkillHealthReport(report: SkillHealthReport): string {
109
+ const lines: string[] = [];
110
+
111
+ lines.push("Skill Health Report");
112
+ lines.push("═".repeat(60));
113
+ lines.push(`Generated: ${report.generatedAt}`);
114
+ lines.push(`Units with skill data: ${report.totalUnitsWithSkills}`);
115
+ lines.push("");
116
+
117
+ if (report.skills.length === 0) {
118
+ lines.push("No skill telemetry data yet. Run auto-mode to start collecting.");
119
+ lines.push("Skill usage is recorded per-unit in metrics.json.");
120
+ return lines.join("\n");
121
+ }
122
+
123
+ // Main table
124
+ lines.push("Skill Uses Success% Avg Tokens Trend Last Used");
125
+ lines.push("─".repeat(80));
126
+
127
+ for (const s of report.skills) {
128
+ const name = s.name.padEnd(24).slice(0, 24);
129
+ const uses = String(s.totalUses).padStart(5);
130
+ const success = `${Math.round(s.successRate * 100)}%`.padStart(8);
131
+ const tokens = formatTokenCount(s.avgTokens).padStart(11);
132
+ const trend = s.tokenTrend.padEnd(10);
133
+ const lastUsed = s.staleDays === 0 ? "today" :
134
+ s.staleDays === 1 ? "1 day ago" :
135
+ `${s.staleDays} days ago`;
136
+ const flag = s.flagged ? " ⚠" : "";
137
+ lines.push(`${name}${uses}${success}${tokens} ${trend}${lastUsed}${flag}`);
138
+ }
139
+
140
+ // Stale skills
141
+ if (report.staleSkills.length > 0) {
142
+ lines.push("");
143
+ lines.push("Stale Skills (unused for 60+ days):");
144
+ for (const name of report.staleSkills) {
145
+ lines.push(` ⏸ ${name}`);
146
+ }
147
+ }
148
+
149
+ // Declining skills
150
+ if (report.decliningSkills.length > 0) {
151
+ lines.push("");
152
+ lines.push("Declining Skills (flagged for review):");
153
+ for (const name of report.decliningSkills) {
154
+ const entry = report.skills.find(s => s.name === name);
155
+ if (entry?.flagReason) {
156
+ lines.push(` ⚠ ${name}: ${entry.flagReason}`);
157
+ }
158
+ }
159
+ }
160
+
161
+ // Suggestions
162
+ if (report.suggestions.length > 0) {
163
+ lines.push("");
164
+ lines.push("Heal Suggestions:");
165
+ for (const sug of report.suggestions) {
166
+ const icon = sug.severity === "critical" ? "🔴" : sug.severity === "warning" ? "🟡" : "🔵";
167
+ lines.push(` ${icon} ${sug.skillName}: ${sug.message}`);
168
+ }
169
+ }
170
+
171
+ return lines.join("\n");
172
+ }
173
+
174
+ /**
175
+ * Format a detailed health view for a single skill.
176
+ */
177
+ export function formatSkillDetail(basePath: string, skillName: string): string {
178
+ const ledger = loadLedgerFromDisk(basePath);
179
+ const units = (ledger?.units ?? []).filter(u => u.skills?.includes(skillName));
180
+ const lines: string[] = [];
181
+
182
+ lines.push(`Skill Detail: ${skillName}`);
183
+ lines.push("═".repeat(50));
184
+
185
+ if (units.length === 0) {
186
+ lines.push("No usage data recorded for this skill.");
187
+ return lines.join("\n");
188
+ }
189
+
190
+ const totalTokens = units.reduce((s, u) => s + u.tokens.total, 0);
191
+ const totalCost = units.reduce((s, u) => s + u.cost, 0);
192
+ const avgTokens = Math.round(totalTokens / units.length);
193
+ const avgCost = totalCost / units.length;
194
+
195
+ lines.push(`Total uses: ${units.length}`);
196
+ lines.push(`Total tokens: ${formatTokenCount(totalTokens)}`);
197
+ lines.push(`Total cost: ${formatCost(totalCost)}`);
198
+ lines.push(`Avg tokens/use: ${formatTokenCount(avgTokens)}`);
199
+ lines.push(`Avg cost/use: ${formatCost(avgCost)}`);
200
+ lines.push("");
201
+
202
+ // Recent uses
203
+ lines.push("Recent uses:");
204
+ const recent = units.slice(-10).reverse();
205
+ for (const u of recent) {
206
+ const date = new Date(u.finishedAt).toISOString().slice(0, 10);
207
+ lines.push(` ${date} ${u.id.padEnd(20)} ${formatTokenCount(u.tokens.total).padStart(8)} tokens ${formatCost(u.cost)}`);
208
+ }
209
+
210
+ // Check for SKILL.md existence
211
+ const skillPath = join(getAgentDir(), "skills", skillName, "SKILL.md");
212
+ if (existsSync(skillPath)) {
213
+ const stat = require("node:fs").statSync(skillPath);
214
+ lines.push("");
215
+ lines.push(`SKILL.md: ${skillPath}`);
216
+ lines.push(`Last modified: ${stat.mtime.toISOString().slice(0, 10)}`);
217
+ }
218
+
219
+ return lines.join("\n");
220
+ }
221
+
222
+ /**
223
+ * Build the heal-skill prompt for a post-unit hook.
224
+ * This is the GSD-integrated version of glittercowboy's heal-skill concept.
225
+ *
226
+ * The prompt instructs the agent to:
227
+ * 1. Detect which skill was loaded during the completed unit
228
+ * 2. Analyze whether the agent deviated from the skill's instructions
229
+ * 3. If deviations found, propose specific fixes (not auto-apply)
230
+ * 4. Write suggestions to a review queue for human approval
231
+ */
232
+ export function buildHealSkillPrompt(unitId: string): string {
233
+ return `## Skill Heal Analysis
234
+
235
+ Analyze the just-completed unit (${unitId}) for skill drift.
236
+
237
+ ### Steps
238
+
239
+ 1. **Identify loaded skill**: Check which SKILL.md file was read during this unit.
240
+ If no skill was loaded, write "No skill loaded — skipping heal analysis" and stop.
241
+
242
+ 2. **Read the skill**: Load the SKILL.md that was used.
243
+
244
+ 3. **Compare execution to skill guidance**: Review what the agent actually did vs what
245
+ the skill recommended. Look for:
246
+ - API patterns the skill recommended that the agent did differently
247
+ - Error handling approaches the skill specified but the agent bypassed
248
+ - Conventions the skill documented that the agent ignored
249
+ - Outdated instructions in the skill that caused errors or retries
250
+
251
+ 4. **Assess drift severity**:
252
+ - **None**: Agent followed skill correctly → write "No drift detected" to the summary and stop
253
+ - **Minor**: Agent found a better approach but skill isn't wrong → note in KNOWLEDGE.md
254
+ - **Significant**: Skill has outdated or incorrect guidance → propose fix
255
+
256
+ 5. **If significant drift found**, write a heal suggestion to \`.gsd/skill-review-queue.md\`:
257
+
258
+ \`\`\`markdown
259
+ ### {skill-name} (flagged {date})
260
+ - **Unit:** ${unitId}
261
+ - **Issue:** {1-2 sentence description}
262
+ - **Root cause:** {outdated API / incorrect pattern / missing context}
263
+ - **Proposed fix:**
264
+ - File: SKILL.md
265
+ - Section: {section name}
266
+ - Current: {quote the incorrect text}
267
+ - Suggested: {the corrected text}
268
+ - **Action:** [ ] Reviewed [ ] Updated [ ] Dismissed
269
+ \`\`\`
270
+
271
+ **Important:** Do NOT modify the skill directly. Write the suggestion to the review queue.
272
+ The SkillsBench research shows that human-curated skills outperform auto-generated ones by +16.2pp.
273
+ The human review step is what makes this valuable.`;
274
+ }
275
+
276
+ /**
277
+ * Compute stale skills that should be added to avoid_skills.
278
+ * Returns only skills not already in the avoid list.
279
+ */
280
+ export function computeStaleAvoidList(
281
+ basePath: string,
282
+ currentAvoidList: string[],
283
+ staleDays?: number,
284
+ ): string[] {
285
+ const ledger = loadLedgerFromDisk(basePath);
286
+ const units = (ledger?.units ?? []).filter(u => u.skills && u.skills.length > 0);
287
+ const stale = detectStaleSkills(units, staleDays ?? DEFAULT_STALE_DAYS);
288
+ const avoidSet = new Set(currentAvoidList);
289
+
290
+ return stale.filter(s => !avoidSet.has(s));
291
+ }
292
+
293
+ // ─── Internals ────────────────────────────────────────────────────────────────
294
+
295
+ function aggregateBySkill(units: UnitMetrics[]): Map<string, SkillHealthEntry> {
296
+ const map = new Map<string, { uses: UnitMetrics[] }>();
297
+
298
+ for (const u of units) {
299
+ if (!u.skills) continue;
300
+ for (const skill of u.skills) {
301
+ let entry = map.get(skill);
302
+ if (!entry) {
303
+ entry = { uses: [] };
304
+ map.set(skill, entry);
305
+ }
306
+ entry.uses.push(u);
307
+ }
308
+ }
309
+
310
+ const result = new Map<string, SkillHealthEntry>();
311
+ const now = Date.now();
312
+
313
+ for (const [name, { uses }] of map) {
314
+ const totalTokens = uses.reduce((s, u) => s + u.tokens.total, 0);
315
+ const totalCost = uses.reduce((s, u) => s + u.cost, 0);
316
+ const avgTokens = Math.round(totalTokens / uses.length);
317
+ const avgCost = totalCost / uses.length;
318
+
319
+ // Success rate: units that didn't have excessive retries (proxy: low tool call count relative to messages)
320
+ // Without direct retry tracking, use a heuristic: success if toolCalls < assistantMessages * 20
321
+ const successCount = uses.filter(u => u.toolCalls < u.assistantMessages * 20).length;
322
+ const successRate = uses.length > 0 ? successCount / uses.length : 1;
323
+
324
+ // Token trend
325
+ const tokenTrend = computeTokenTrend(uses);
326
+
327
+ // Last used
328
+ const lastUsed = Math.max(...uses.map(u => u.finishedAt));
329
+ const staleDays = Math.floor((now - lastUsed) / (24 * 60 * 60 * 1000));
330
+
331
+ // Flag conditions
332
+ let flagged = false;
333
+ let flagReason: string | undefined;
334
+
335
+ if (uses.length >= MIN_USES_FOR_TREND) {
336
+ if (successRate < SUCCESS_RATE_THRESHOLD) {
337
+ flagged = true;
338
+ flagReason = `Success rate ${Math.round(successRate * 100)}% (below ${Math.round(SUCCESS_RATE_THRESHOLD * 100)}% threshold)`;
339
+ } else if (tokenTrend === "rising") {
340
+ flagged = true;
341
+ flagReason = `Token usage trending upward (${Math.round(TOKEN_RISE_THRESHOLD * 100)}%+ increase)`;
342
+ }
343
+ }
344
+
345
+ result.set(name, {
346
+ name,
347
+ totalUses: uses.length,
348
+ successRate,
349
+ avgTokens,
350
+ tokenTrend,
351
+ lastUsed,
352
+ staleDays,
353
+ avgCost,
354
+ flagged,
355
+ flagReason,
356
+ });
357
+ }
358
+
359
+ return result;
360
+ }
361
+
362
+ function computeTokenTrend(uses: UnitMetrics[]): "stable" | "rising" | "declining" {
363
+ if (uses.length < MIN_USES_FOR_TREND * 2) return "stable";
364
+
365
+ // Sort by start time
366
+ const sorted = [...uses].sort((a, b) => a.startedAt - b.startedAt);
367
+ const window = Math.min(TREND_WINDOW, Math.floor(sorted.length / 2));
368
+
369
+ const recent = sorted.slice(-window);
370
+ const previous = sorted.slice(-window * 2, -window);
371
+
372
+ const recentAvg = recent.reduce((s, u) => s + u.tokens.total, 0) / recent.length;
373
+ const previousAvg = previous.reduce((s, u) => s + u.tokens.total, 0) / previous.length;
374
+
375
+ if (previousAvg === 0) return "stable";
376
+
377
+ const change = (recentAvg - previousAvg) / previousAvg;
378
+
379
+ if (change > TOKEN_RISE_THRESHOLD) return "rising";
380
+ if (change < -TOKEN_RISE_THRESHOLD) return "declining";
381
+ return "stable";
382
+ }
383
+
384
+ function generateSuggestions(skills: SkillHealthEntry[], staleSkills: string[]): SkillHealSuggestion[] {
385
+ const suggestions: SkillHealSuggestion[] = [];
386
+
387
+ for (const skill of skills) {
388
+ if (skill.totalUses >= MIN_USES_FOR_TREND && skill.successRate < SUCCESS_RATE_THRESHOLD) {
389
+ suggestions.push({
390
+ skillName: skill.name,
391
+ trigger: "declining_success",
392
+ message: `Success rate dropped to ${Math.round(skill.successRate * 100)}% over ${skill.totalUses} uses. Review SKILL.md for outdated patterns.`,
393
+ severity: skill.successRate < 0.5 ? "critical" : "warning",
394
+ });
395
+ }
396
+
397
+ if (skill.tokenTrend === "rising" && skill.totalUses >= MIN_USES_FOR_TREND * 2) {
398
+ suggestions.push({
399
+ skillName: skill.name,
400
+ trigger: "rising_tokens",
401
+ message: `Token usage trending upward. Skill may be causing inefficient execution patterns.`,
402
+ severity: "info",
403
+ });
404
+ }
405
+ }
406
+
407
+ for (const name of staleSkills) {
408
+ suggestions.push({
409
+ skillName: name,
410
+ trigger: "stale",
411
+ message: `Not used in ${DEFAULT_STALE_DAYS}+ days. Consider archiving or updating.`,
412
+ severity: "info",
413
+ });
414
+ }
415
+
416
+ return suggestions;
417
+ }