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.
- package/README.md +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +424 -30
- package/dist/resources/extensions/gsd/commands.ts +518 -36
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +39 -3
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/dist/resources/extensions/gsd/preferences.ts +125 -150
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/dist/resources/extensions/remote-questions/format.ts +154 -8
- package/dist/resources/extensions/remote-questions/manager.ts +9 -7
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +424 -30
- package/src/resources/extensions/gsd/commands.ts +518 -36
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +39 -3
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/src/resources/extensions/gsd/preferences.ts +125 -150
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/src/resources/extensions/remote-questions/format.ts +154 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -7
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- 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
|
-
|
|
114
|
-
if (!
|
|
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 =
|
|
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
|
+
}
|