gsd-pi 2.18.0 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +690 -39
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +654 -36
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- 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 +51 -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 +84 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/dist/resources/extensions/gsd/preferences.ts +198 -150
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- 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/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- 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/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -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/feature-branch-lifecycle-integration.test.ts +434 -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/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -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 +488 -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/routing-history.test.ts +215 -62
- 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/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -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 +290 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- 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/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -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 +505 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
- 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 +35 -4
- package/dist/resources/extensions/remote-questions/format.ts +166 -14
- package/dist/resources/extensions/remote-questions/manager.ts +14 -4
- 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-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +690 -39
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +654 -36
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- 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 +51 -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 +84 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/src/resources/extensions/gsd/preferences.ts +198 -150
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- 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/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- 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/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -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/feature-branch-lifecycle-integration.test.ts +434 -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/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -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 +488 -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/routing-history.test.ts +215 -62
- 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/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -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 +290 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- 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/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -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 +505 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
- 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 +35 -4
- package/src/resources/extensions/remote-questions/format.ts +166 -14
- package/src/resources/extensions/remote-questions/manager.ts +14 -4
- 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,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
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Skill Telemetry — Track which skills are loaded per unit (#599)
|
|
3
|
+
*
|
|
4
|
+
* Captures skill names at dispatch time for inclusion in UnitMetrics.
|
|
5
|
+
* Distinguishes between "available" skills (in system prompt) and
|
|
6
|
+
* "actively loaded" skills (read via tool calls during execution).
|
|
7
|
+
*
|
|
8
|
+
* Data flow:
|
|
9
|
+
* 1. At dispatch, captureAvailableSkills() records skills from the system prompt
|
|
10
|
+
* 2. During execution, recordSkillRead() tracks explicit SKILL.md reads
|
|
11
|
+
* 3. At unit completion, getAndClearSkills() returns the loaded list for metrics
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { getAgentDir } from "@gsd/pi-coding-agent";
|
|
17
|
+
|
|
18
|
+
// ─── In-memory state ──────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/** Skills available in the system prompt for the current unit */
|
|
21
|
+
let availableSkills: string[] = [];
|
|
22
|
+
|
|
23
|
+
/** Skills explicitly read (SKILL.md loaded) during the current unit */
|
|
24
|
+
const activelyLoadedSkills = new Set<string>();
|
|
25
|
+
|
|
26
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Capture the list of available skill names at dispatch time.
|
|
30
|
+
* Called before each unit starts.
|
|
31
|
+
*/
|
|
32
|
+
export function captureAvailableSkills(): void {
|
|
33
|
+
const skillsDir = join(getAgentDir(), "skills");
|
|
34
|
+
availableSkills = listSkillNames(skillsDir);
|
|
35
|
+
activelyLoadedSkills.clear();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Record that a skill was actively loaded (its SKILL.md was read).
|
|
40
|
+
* Call this when the agent reads a SKILL.md file.
|
|
41
|
+
*/
|
|
42
|
+
export function recordSkillRead(skillName: string): void {
|
|
43
|
+
activelyLoadedSkills.add(skillName);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the skill names for the current unit and clear state.
|
|
48
|
+
* Returns actively loaded skills if any, otherwise available skills.
|
|
49
|
+
* This gives the most useful signal: if the agent read specific skills,
|
|
50
|
+
* report those; otherwise report what was available.
|
|
51
|
+
*/
|
|
52
|
+
export function getAndClearSkills(): string[] {
|
|
53
|
+
const result = activelyLoadedSkills.size > 0
|
|
54
|
+
? Array.from(activelyLoadedSkills)
|
|
55
|
+
: [...availableSkills];
|
|
56
|
+
availableSkills = [];
|
|
57
|
+
activelyLoadedSkills.clear();
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Reset all telemetry state. Called when auto-mode stops.
|
|
63
|
+
*/
|
|
64
|
+
export function resetSkillTelemetry(): void {
|
|
65
|
+
availableSkills = [];
|
|
66
|
+
activelyLoadedSkills.clear();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get last-used timestamps for all skills from metrics data.
|
|
71
|
+
* Returns a Map from skill name to most recent ms timestamp.
|
|
72
|
+
*/
|
|
73
|
+
export function getSkillLastUsed(units: Array<{ finishedAt: number; skills?: string[] }>): Map<string, number> {
|
|
74
|
+
const lastUsed = new Map<string, number>();
|
|
75
|
+
for (const u of units) {
|
|
76
|
+
if (!u.skills) continue;
|
|
77
|
+
for (const skill of u.skills) {
|
|
78
|
+
const existing = lastUsed.get(skill) ?? 0;
|
|
79
|
+
if (u.finishedAt > existing) {
|
|
80
|
+
lastUsed.set(skill, u.finishedAt);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return lastUsed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Detect stale skills — those not used within the given threshold (in days).
|
|
89
|
+
* Returns skill names that should be deprioritized.
|
|
90
|
+
*/
|
|
91
|
+
export function detectStaleSkills(
|
|
92
|
+
units: Array<{ finishedAt: number; skills?: string[] }>,
|
|
93
|
+
thresholdDays: number,
|
|
94
|
+
): string[] {
|
|
95
|
+
if (thresholdDays <= 0) return [];
|
|
96
|
+
|
|
97
|
+
const lastUsed = getSkillLastUsed(units);
|
|
98
|
+
const cutoff = Date.now() - (thresholdDays * 24 * 60 * 60 * 1000);
|
|
99
|
+
const stale: string[] = [];
|
|
100
|
+
|
|
101
|
+
// Check all installed skills, not just those with usage data
|
|
102
|
+
const skillsDir = join(getAgentDir(), "skills");
|
|
103
|
+
const installed = listSkillNames(skillsDir);
|
|
104
|
+
|
|
105
|
+
for (const skill of installed) {
|
|
106
|
+
const lastTs = lastUsed.get(skill);
|
|
107
|
+
if (lastTs === undefined || lastTs < cutoff) {
|
|
108
|
+
stale.push(skill);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return stale;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Internals ────────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
function listSkillNames(skillsDir: string): string[] {
|
|
118
|
+
if (!existsSync(skillsDir)) return [];
|
|
119
|
+
try {
|
|
120
|
+
return readdirSync(skillsDir, { withFileTypes: true })
|
|
121
|
+
.filter(d => d.isDirectory() && !d.name.startsWith("."))
|
|
122
|
+
.filter(d => existsSync(join(skillsDir, d.name, "SKILL.md")))
|
|
123
|
+
.map(d => d.name);
|
|
124
|
+
} catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -32,8 +32,10 @@ import {
|
|
|
32
32
|
|
|
33
33
|
import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
|
|
34
34
|
import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-bridge.js';
|
|
35
|
+
import { isDbAvailable, _getAdapter } from './gsd-db.js';
|
|
35
36
|
|
|
36
37
|
import { join, resolve } from 'path';
|
|
38
|
+
import { debugCount, debugTime } from './debug-logger.js';
|
|
37
39
|
|
|
38
40
|
// ─── Query Functions ───────────────────────────────────────────────────────
|
|
39
41
|
|
|
@@ -116,7 +118,10 @@ export async function deriveState(basePath: string): Promise<GSDState> {
|
|
|
116
118
|
return _stateCache.result;
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
const stopTimer = debugTime("derive-state-impl");
|
|
119
122
|
const result = await _deriveStateImpl(basePath);
|
|
123
|
+
stopTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
124
|
+
debugCount("deriveStateCalls");
|
|
120
125
|
_stateCache = { basePath, result, timestamp: Date.now() };
|
|
121
126
|
return result;
|
|
122
127
|
}
|
|
@@ -131,6 +136,30 @@ async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
131
136
|
const fileContentCache = new Map<string, string>();
|
|
132
137
|
const gsdDir = gsdRoot(basePath);
|
|
133
138
|
|
|
139
|
+
// ── DB-first content loading ──
|
|
140
|
+
// When the DB is available, load artifact content from the artifacts table
|
|
141
|
+
// (indexed SELECT instead of O(N) file I/O). Falls back to native Rust batch
|
|
142
|
+
// parser, which in turn falls back to sequential JS reads via cachedLoadFile.
|
|
143
|
+
let dbContentLoaded = false;
|
|
144
|
+
if (isDbAvailable()) {
|
|
145
|
+
const adapter = _getAdapter();
|
|
146
|
+
if (adapter) {
|
|
147
|
+
try {
|
|
148
|
+
const rows = adapter.prepare('SELECT path, full_content FROM artifacts').all();
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
const relPath = (row as Record<string, unknown>)['path'] as string;
|
|
151
|
+
const content = (row as Record<string, unknown>)['full_content'] as string;
|
|
152
|
+
const absPath = resolve(gsdDir, relPath);
|
|
153
|
+
fileContentCache.set(absPath, content);
|
|
154
|
+
}
|
|
155
|
+
dbContentLoaded = rows.length > 0;
|
|
156
|
+
} catch {
|
|
157
|
+
// DB query failed — fall through to native batch parse
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!dbContentLoaded) {
|
|
134
163
|
const batchFiles = nativeBatchParseGsdFiles(gsdDir);
|
|
135
164
|
if (batchFiles) {
|
|
136
165
|
for (const f of batchFiles) {
|
|
@@ -138,6 +167,7 @@ async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
138
167
|
fileContentCache.set(absPath, f.rawContent);
|
|
139
168
|
}
|
|
140
169
|
}
|
|
170
|
+
}
|
|
141
171
|
|
|
142
172
|
/**
|
|
143
173
|
* Load file content from batch cache first, falling back to disk read.
|