selftune 0.2.22 → 0.2.24
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/CHANGELOG.md +6 -0
- package/README.md +95 -15
- package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
- package/apps/local-dashboard/dist/assets/index-Dmx7LPVX.js +15 -0
- package/apps/local-dashboard/dist/assets/vendor-react-C5oyHiV1.js +11 -0
- package/apps/local-dashboard/dist/assets/{vendor-table-BIiI3YhS.js → vendor-table-Bc_bbKd8.js} +1 -1
- package/apps/local-dashboard/dist/assets/vendor-ui-B3BPIYy7.js +1 -0
- package/apps/local-dashboard/dist/index.html +5 -5
- package/cli/selftune/adapters/codex/install.ts +310 -78
- package/cli/selftune/adapters/opencode/install.ts +3 -4
- package/cli/selftune/adapters/pi/hook.ts +273 -0
- package/cli/selftune/adapters/pi/install.ts +207 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +3 -3
- package/cli/selftune/alpha-upload/stage-canonical.ts +17 -11
- package/cli/selftune/auto-update.ts +200 -8
- package/cli/selftune/canonical-export.ts +55 -25
- package/cli/selftune/command-surface.ts +397 -0
- package/cli/selftune/constants.ts +10 -1
- package/cli/selftune/contribute/contribute.ts +64 -13
- package/cli/selftune/contribution-config.ts +57 -3
- package/cli/selftune/contribution-preferences.ts +117 -0
- package/cli/selftune/contribution-signals.ts +8 -4
- package/cli/selftune/contribution-staging.ts +13 -2
- package/cli/selftune/contributions.ts +55 -121
- package/cli/selftune/creator-contributions.ts +29 -10
- package/cli/selftune/cron/setup.ts +7 -3
- package/cli/selftune/dashboard-contract.ts +87 -0
- package/cli/selftune/dashboard-server.ts +168 -17
- package/cli/selftune/dashboard.ts +350 -17
- package/cli/selftune/eval/baseline.ts +21 -5
- package/cli/selftune/eval/execution-eval.ts +170 -0
- package/cli/selftune/eval/family-overlap.ts +2 -2
- package/cli/selftune/eval/hooks-to-evals.ts +228 -82
- package/cli/selftune/eval/import-skillsbench.ts +2 -2
- package/cli/selftune/eval/invocation-classifier.ts +56 -0
- package/cli/selftune/eval/synthetic-evals.ts +5 -3
- package/cli/selftune/eval/unit-test-cli.ts +7 -4
- package/cli/selftune/evolution/apply-proposal.ts +295 -0
- package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
- package/cli/selftune/evolution/engines/replay-engine.ts +180 -0
- package/cli/selftune/evolution/evidence.ts +2 -6
- package/cli/selftune/evolution/evolve-body.ts +152 -38
- package/cli/selftune/evolution/evolve.ts +244 -52
- package/cli/selftune/evolution/rollback.ts +0 -1
- package/cli/selftune/evolution/validate-body.ts +111 -49
- package/cli/selftune/evolution/validate-host-replay.ts +510 -60
- package/cli/selftune/evolution/validate-proposal.ts +11 -150
- package/cli/selftune/evolution/validate-routing.ts +51 -108
- package/cli/selftune/evolution/validation-contract.ts +91 -0
- package/cli/selftune/grading/auto-grade.ts +11 -7
- package/cli/selftune/grading/grade-session.ts +10 -16
- package/cli/selftune/hooks/skill-eval.ts +2 -1
- package/cli/selftune/hooks-shared/types.ts +1 -0
- package/cli/selftune/index.ts +58 -15
- package/cli/selftune/ingestors/claude-replay.ts +15 -10
- package/cli/selftune/ingestors/codex-wrapper.ts +3 -3
- package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
- package/cli/selftune/ingestors/pi-ingest.ts +727 -0
- package/cli/selftune/init.ts +38 -4
- package/cli/selftune/localdb/direct-write.ts +120 -1
- package/cli/selftune/localdb/materialize.ts +6 -7
- package/cli/selftune/localdb/queries/cron.ts +34 -0
- package/cli/selftune/localdb/queries/dashboard.ts +834 -0
- package/cli/selftune/localdb/queries/evolution.ts +158 -0
- package/cli/selftune/localdb/queries/execution.ts +133 -0
- package/cli/selftune/localdb/queries/json.ts +18 -0
- package/cli/selftune/localdb/queries/monitoring.ts +263 -0
- package/cli/selftune/localdb/queries/raw.ts +95 -0
- package/cli/selftune/localdb/queries/staging.ts +270 -0
- package/cli/selftune/localdb/queries/trust.ts +392 -0
- package/cli/selftune/localdb/queries.ts +60 -2162
- package/cli/selftune/localdb/schema.ts +59 -0
- package/cli/selftune/monitoring/watch.ts +96 -29
- package/cli/selftune/normalization.ts +3 -0
- package/cli/selftune/observability.ts +12 -3
- package/cli/selftune/orchestrate/cli.ts +161 -0
- package/cli/selftune/orchestrate/execute.ts +295 -0
- package/cli/selftune/orchestrate/finalize.ts +157 -0
- package/cli/selftune/orchestrate/locks.ts +40 -0
- package/cli/selftune/orchestrate/plan.ts +131 -0
- package/cli/selftune/orchestrate/post-run.ts +59 -0
- package/cli/selftune/orchestrate/prepare.ts +334 -0
- package/cli/selftune/orchestrate/report.ts +182 -0
- package/cli/selftune/orchestrate/runtime.ts +120 -0
- package/cli/selftune/orchestrate/signals.ts +48 -0
- package/cli/selftune/orchestrate.ts +162 -1142
- package/cli/selftune/registry/client.ts +74 -0
- package/cli/selftune/registry/history.ts +54 -0
- package/cli/selftune/registry/index.ts +90 -0
- package/cli/selftune/registry/install.ts +141 -0
- package/cli/selftune/registry/list.ts +44 -0
- package/cli/selftune/registry/push.ts +171 -0
- package/cli/selftune/registry/rollback.ts +49 -0
- package/cli/selftune/registry/status.ts +62 -0
- package/cli/selftune/registry/sync.ts +125 -0
- package/cli/selftune/repair/skill-usage.ts +9 -3
- package/cli/selftune/routes/overview.ts +5 -2
- package/cli/selftune/routes/skill-report.ts +15 -2
- package/cli/selftune/schedule.ts +5 -5
- package/cli/selftune/status.ts +70 -2
- package/cli/selftune/sync.ts +127 -23
- package/cli/selftune/testing-readiness.ts +597 -0
- package/cli/selftune/types.ts +46 -5
- package/cli/selftune/uninstall.ts +2 -1
- package/cli/selftune/utils/canonical-log.ts +1 -9
- package/cli/selftune/utils/cli-error.ts +9 -0
- package/cli/selftune/utils/jsonl.ts +1 -30
- package/cli/selftune/utils/llm-call.ts +126 -6
- package/cli/selftune/utils/skill-discovery.ts +24 -0
- package/cli/selftune/workflows/proposals.ts +184 -0
- package/cli/selftune/workflows/skill-scaffold.ts +241 -0
- package/cli/selftune/workflows/workflows.ts +100 -26
- package/node_modules/@selftune/telemetry-contract/fixtures/complete-push.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
- package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
- package/node_modules/@selftune/telemetry-contract/package.json +1 -1
- package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +63 -5
- package/node_modules/@selftune/telemetry-contract/src/types.ts +97 -7
- package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/package.json +25 -9
- package/packages/dashboard-core/AGENTS.md +18 -0
- package/packages/dashboard-core/README.md +30 -0
- package/packages/dashboard-core/index.ts +3 -0
- package/packages/dashboard-core/package.json +39 -0
- package/packages/dashboard-core/src/chrome/DashboardChrome.tsx +74 -0
- package/packages/dashboard-core/src/chrome/DashboardHeader.tsx +200 -0
- package/packages/dashboard-core/src/chrome/DashboardSidebar.tsx +219 -0
- package/packages/dashboard-core/src/chrome/RuntimeBadge.tsx +46 -0
- package/packages/dashboard-core/src/chrome/index.ts +14 -0
- package/packages/dashboard-core/src/chrome/types.ts +81 -0
- package/packages/dashboard-core/src/chrome/utils.ts +23 -0
- package/packages/dashboard-core/src/gates/FeatureGate.tsx +11 -0
- package/packages/dashboard-core/src/gates/LockedRoute.tsx +29 -0
- package/packages/dashboard-core/src/gates/UpgradeCard.tsx +89 -0
- package/packages/dashboard-core/src/gates/index.ts +3 -0
- package/packages/dashboard-core/src/host/DashboardHostProvider.tsx +62 -0
- package/packages/dashboard-core/src/host/adapter.ts +47 -0
- package/packages/dashboard-core/src/host/capabilities.ts +55 -0
- package/packages/dashboard-core/src/host/index.ts +3 -0
- package/packages/dashboard-core/src/models/analytics.ts +39 -0
- package/packages/dashboard-core/src/models/index.ts +4 -0
- package/packages/dashboard-core/src/models/overview.ts +98 -0
- package/packages/dashboard-core/src/models/runtime.ts +7 -0
- package/packages/dashboard-core/src/models/skills.ts +34 -0
- package/packages/dashboard-core/src/routes/index.ts +2 -0
- package/packages/dashboard-core/src/routes/manifest.test.ts +70 -0
- package/packages/dashboard-core/src/routes/manifest.ts +451 -0
- package/packages/dashboard-core/src/routes/types.ts +39 -0
- package/packages/dashboard-core/src/screens/analytics/AnalyticsScreen.tsx +278 -0
- package/packages/dashboard-core/src/screens/analytics/index.ts +1 -0
- package/packages/dashboard-core/src/screens/index.ts +37 -0
- package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.test.ts +101 -0
- package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.tsx +393 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.test.tsx +113 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.tsx +72 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCoreSurface.tsx +71 -0
- package/packages/dashboard-core/src/screens/overview/OverviewOnboardingBanner.tsx +90 -0
- package/packages/dashboard-core/src/screens/overview/OverviewRunSummary.tsx +40 -0
- package/packages/dashboard-core/src/screens/overview/index.ts +16 -0
- package/packages/dashboard-core/src/screens/overview/types.ts +13 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportDailyBreakdownSection.tsx +99 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportDataQualityTabContent.tsx +35 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceRail.tsx +71 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceSection.tsx +63 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceTabContent.tsx +25 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportInvocationsSection.tsx +24 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportMissedQueriesSection.tsx +79 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportScaffold.tsx +150 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportSections.test.tsx +224 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.test.tsx +76 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.tsx +88 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTrendSection.tsx +33 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTrustBadge.tsx +67 -0
- package/packages/dashboard-core/src/screens/skill-report/index.ts +45 -0
- package/packages/dashboard-core/src/screens/skills/SkillsLibraryScreen.tsx +162 -0
- package/packages/dashboard-core/src/screens/skills/index.ts +6 -0
- package/packages/telemetry-contract/fixtures/complete-push.ts +1 -1
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
- package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +63 -5
- package/packages/telemetry-contract/src/types.ts +97 -7
- package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/packages/ui/AGENTS.md +16 -0
- package/packages/ui/README.md +1 -1
- package/packages/ui/package.json +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
- package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +229 -464
- package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
- package/packages/ui/src/components/InfoTip.tsx +1 -2
- package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
- package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
- package/packages/ui/src/components/OverviewPanels.tsx +693 -0
- package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
- package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
- package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
- package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
- package/packages/ui/src/components/index.ts +56 -1
- package/packages/ui/src/components/section-cards.tsx +18 -35
- package/packages/ui/src/components/skill-health-grid.tsx +47 -37
- package/packages/ui/src/lib/constants.tsx +0 -1
- package/packages/ui/src/primitives/card.tsx +1 -1
- package/packages/ui/src/primitives/checkbox.tsx +1 -1
- package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
- package/packages/ui/src/primitives/select.tsx +2 -2
- package/packages/ui/src/primitives/tabs.tsx +7 -6
- package/packages/ui/src/types.ts +182 -4
- package/skill/SKILL.md +130 -318
- package/skill/agents/diagnosis-analyst.md +3 -3
- package/skill/agents/evolution-reviewer.md +3 -3
- package/skill/agents/integration-guide.md +3 -3
- package/skill/agents/pattern-analyst.md +2 -2
- package/skill/references/cli-quick-reference.md +89 -0
- package/skill/references/creator-playbook.md +131 -0
- package/skill/references/examples.md +48 -0
- package/skill/references/troubleshooting.md +47 -0
- package/skill/references/version-history.md +1 -1
- package/skill/selftune.contribute.json +11 -0
- package/skill/{Workflows → workflows}/Baseline.md +20 -1
- package/skill/{Workflows → workflows}/Contribute.md +23 -10
- package/skill/{Workflows → workflows}/Contributions.md +13 -5
- package/skill/workflows/CreateTestDeploy.md +170 -0
- package/skill/{Workflows → workflows}/CreatorContributions.md +18 -6
- package/skill/{Workflows → workflows}/Cron.md +1 -1
- package/skill/{Workflows → workflows}/Dashboard.md +20 -0
- package/skill/{Workflows → workflows}/Doctor.md +1 -1
- package/skill/{Workflows → workflows}/Evals.md +67 -2
- package/skill/{Workflows → workflows}/Evolve.md +119 -30
- package/skill/{Workflows → workflows}/EvolveBody.md +41 -1
- package/skill/{Workflows → workflows}/Grade.md +1 -1
- package/skill/{Workflows → workflows}/Ingest.md +60 -2
- package/skill/{Workflows → workflows}/Initialize.md +16 -9
- package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
- package/skill/{Workflows → workflows}/PlatformHooks.md +19 -3
- package/skill/workflows/Registry.md +99 -0
- package/skill/{Workflows → workflows}/Schedule.md +3 -3
- package/skill/workflows/SignalsDashboard.md +87 -0
- package/skill/{Workflows → workflows}/Sync.md +3 -1
- package/skill/{Workflows → workflows}/UnitTest.md +19 -0
- package/skill/{Workflows → workflows}/Watch.md +42 -2
- package/skill/{Workflows → workflows}/Workflows.md +39 -2
- package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
- package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
- package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
- package/cli/selftune/utils/html.ts +0 -27
- package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
- /package/skill/{Workflows → workflows}/AlphaUpload.md +0 -0
- /package/skill/{Workflows → workflows}/AutoActivation.md +0 -0
- /package/skill/{Workflows → workflows}/Badge.md +0 -0
- /package/skill/{Workflows → workflows}/Composability.md +0 -0
- /package/skill/{Workflows → workflows}/EvolutionMemory.md +0 -0
- /package/skill/{Workflows → workflows}/ExportCanonical.md +0 -0
- /package/skill/{Workflows → workflows}/Hook.md +0 -0
- /package/skill/{Workflows → workflows}/ImportSkillsBench.md +0 -0
- /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
- /package/skill/{Workflows → workflows}/Recover.md +0 -0
- /package/skill/{Workflows → workflows}/RepairSkillUsage.md +0 -0
- /package/skill/{Workflows → workflows}/Replay.md +0 -0
- /package/skill/{Workflows → workflows}/Rollback.md +0 -0
- /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
- /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
|
@@ -1,2162 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const skillRows = db
|
|
62
|
-
.query(
|
|
63
|
-
`SELECT occurred_at, session_id, skill_name, skill_path, query, triggered, source
|
|
64
|
-
FROM skill_invocations
|
|
65
|
-
ORDER BY occurred_at DESC
|
|
66
|
-
LIMIT 2000`,
|
|
67
|
-
)
|
|
68
|
-
.all() as Array<{
|
|
69
|
-
occurred_at: string;
|
|
70
|
-
session_id: string;
|
|
71
|
-
skill_name: string;
|
|
72
|
-
skill_path: string;
|
|
73
|
-
query: string;
|
|
74
|
-
triggered: number;
|
|
75
|
-
source: string | null;
|
|
76
|
-
}>;
|
|
77
|
-
|
|
78
|
-
const skills = skillRows.map((row) => ({
|
|
79
|
-
timestamp: row.occurred_at,
|
|
80
|
-
session_id: row.session_id,
|
|
81
|
-
skill_name: row.skill_name,
|
|
82
|
-
skill_path: row.skill_path,
|
|
83
|
-
query: row.query,
|
|
84
|
-
triggered: row.triggered === 1,
|
|
85
|
-
source: row.source,
|
|
86
|
-
}));
|
|
87
|
-
|
|
88
|
-
// Evolution audit (bounded to most recent 500)
|
|
89
|
-
const evolution = db
|
|
90
|
-
.query(
|
|
91
|
-
`SELECT timestamp, proposal_id, skill_name, action, details
|
|
92
|
-
FROM evolution_audit
|
|
93
|
-
ORDER BY timestamp DESC
|
|
94
|
-
LIMIT 500`,
|
|
95
|
-
)
|
|
96
|
-
.all() as Array<{
|
|
97
|
-
timestamp: string;
|
|
98
|
-
proposal_id: string;
|
|
99
|
-
skill_name: string | null;
|
|
100
|
-
action: string;
|
|
101
|
-
details: string;
|
|
102
|
-
}>;
|
|
103
|
-
|
|
104
|
-
// Counts (single query instead of 6 separate ones)
|
|
105
|
-
const counts = db
|
|
106
|
-
.query(
|
|
107
|
-
`SELECT
|
|
108
|
-
(SELECT COUNT(*) FROM session_telemetry) as telemetry,
|
|
109
|
-
(SELECT COUNT(*) FROM skill_invocations) as skills,
|
|
110
|
-
(SELECT COUNT(*) FROM evolution_audit) as evolution,
|
|
111
|
-
(SELECT COUNT(*) FROM evolution_evidence) as evidence,
|
|
112
|
-
(SELECT COUNT(*) FROM sessions) as sessions,
|
|
113
|
-
(SELECT COUNT(*) FROM prompts) as prompts`,
|
|
114
|
-
)
|
|
115
|
-
.get() as {
|
|
116
|
-
telemetry: number;
|
|
117
|
-
skills: number;
|
|
118
|
-
evolution: number;
|
|
119
|
-
evidence: number;
|
|
120
|
-
sessions: number;
|
|
121
|
-
prompts: number;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// Unmatched queries: skill_invocations entries where triggered = 0 and no other
|
|
125
|
-
// record for the same query text triggered
|
|
126
|
-
const unmatchedRows = db
|
|
127
|
-
.query(
|
|
128
|
-
`SELECT si.occurred_at AS timestamp, si.session_id, si.query
|
|
129
|
-
FROM skill_invocations si
|
|
130
|
-
WHERE si.triggered = 0
|
|
131
|
-
AND NOT EXISTS (
|
|
132
|
-
SELECT 1 FROM skill_invocations si2
|
|
133
|
-
WHERE si2.query = si.query AND si2.triggered = 1
|
|
134
|
-
)
|
|
135
|
-
ORDER BY si.occurred_at DESC
|
|
136
|
-
LIMIT 500`,
|
|
137
|
-
)
|
|
138
|
-
.all() as Array<{ timestamp: string; session_id: string; query: string }>;
|
|
139
|
-
|
|
140
|
-
// Pending proposals: created/validated but no terminal action (deduped in SQL)
|
|
141
|
-
const pending_proposals = getPendingProposals(db);
|
|
142
|
-
|
|
143
|
-
// Active sessions and recent activity
|
|
144
|
-
const active_sessions = getActiveSessionCount(db);
|
|
145
|
-
const recent_activity = getRecentActivity(db);
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
telemetry,
|
|
149
|
-
skills,
|
|
150
|
-
evolution,
|
|
151
|
-
counts,
|
|
152
|
-
unmatched_queries: unmatchedRows,
|
|
153
|
-
pending_proposals,
|
|
154
|
-
active_sessions,
|
|
155
|
-
recent_activity,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Build the skill report payload for a specific skill.
|
|
161
|
-
*/
|
|
162
|
-
export function getSkillReportPayload(db: Database, skillName: string): SkillReportPayload {
|
|
163
|
-
// Usage stats
|
|
164
|
-
const usageRow = db
|
|
165
|
-
.query(
|
|
166
|
-
`SELECT
|
|
167
|
-
COUNT(*) as total_checks,
|
|
168
|
-
SUM(CASE WHEN triggered = 1 THEN 1 ELSE 0 END) as triggered_count
|
|
169
|
-
FROM skill_invocations
|
|
170
|
-
WHERE skill_name = ?`,
|
|
171
|
-
)
|
|
172
|
-
.get(skillName) as { total_checks: number; triggered_count: number };
|
|
173
|
-
|
|
174
|
-
const total = usageRow.total_checks;
|
|
175
|
-
const triggered = usageRow.triggered_count;
|
|
176
|
-
const passRate = total > 0 ? triggered / total : 0;
|
|
177
|
-
|
|
178
|
-
// Recent invocations (last 100)
|
|
179
|
-
const invocationRows = db
|
|
180
|
-
.query(
|
|
181
|
-
`SELECT occurred_at, session_id, query, triggered, source
|
|
182
|
-
FROM skill_invocations
|
|
183
|
-
WHERE skill_name = ?
|
|
184
|
-
ORDER BY occurred_at DESC
|
|
185
|
-
LIMIT 100`,
|
|
186
|
-
)
|
|
187
|
-
.all(skillName) as Array<{
|
|
188
|
-
occurred_at: string;
|
|
189
|
-
session_id: string;
|
|
190
|
-
query: string;
|
|
191
|
-
triggered: number;
|
|
192
|
-
source: string | null;
|
|
193
|
-
}>;
|
|
194
|
-
|
|
195
|
-
const recent_invocations = invocationRows.map((row) => ({
|
|
196
|
-
timestamp: row.occurred_at,
|
|
197
|
-
session_id: row.session_id,
|
|
198
|
-
query: row.query,
|
|
199
|
-
triggered: row.triggered === 1,
|
|
200
|
-
source: row.source,
|
|
201
|
-
}));
|
|
202
|
-
|
|
203
|
-
// Evolution evidence (bounded to most recent 200)
|
|
204
|
-
const evidenceRows = db
|
|
205
|
-
.query(
|
|
206
|
-
`SELECT proposal_id, target, stage, timestamp, rationale, confidence,
|
|
207
|
-
original_text, proposed_text, validation_json, details, eval_set_json
|
|
208
|
-
FROM evolution_evidence
|
|
209
|
-
WHERE skill_name = ?
|
|
210
|
-
ORDER BY timestamp DESC
|
|
211
|
-
LIMIT 200`,
|
|
212
|
-
)
|
|
213
|
-
.all(skillName) as Array<{
|
|
214
|
-
proposal_id: string;
|
|
215
|
-
target: string;
|
|
216
|
-
stage: string;
|
|
217
|
-
timestamp: string;
|
|
218
|
-
rationale: string | null;
|
|
219
|
-
confidence: number | null;
|
|
220
|
-
original_text: string | null;
|
|
221
|
-
proposed_text: string | null;
|
|
222
|
-
validation_json: string | null;
|
|
223
|
-
details: string | null;
|
|
224
|
-
eval_set_json: string | null;
|
|
225
|
-
}>;
|
|
226
|
-
|
|
227
|
-
const evidence = evidenceRows.map((row) => ({
|
|
228
|
-
proposal_id: row.proposal_id,
|
|
229
|
-
target: row.target,
|
|
230
|
-
stage: row.stage,
|
|
231
|
-
timestamp: row.timestamp,
|
|
232
|
-
rationale: row.rationale,
|
|
233
|
-
confidence: row.confidence,
|
|
234
|
-
original_text: row.original_text,
|
|
235
|
-
proposed_text: row.proposed_text,
|
|
236
|
-
validation: safeParseJson(row.validation_json),
|
|
237
|
-
details: row.details,
|
|
238
|
-
eval_set: safeParseJsonArray<Record<string, unknown>>(row.eval_set_json),
|
|
239
|
-
}));
|
|
240
|
-
|
|
241
|
-
// Unique sessions count
|
|
242
|
-
const sessionsRow = db
|
|
243
|
-
.query(`SELECT COUNT(DISTINCT session_id) as c FROM skill_invocations WHERE skill_name = ?`)
|
|
244
|
-
.get(skillName) as { c: number };
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
skill_name: skillName,
|
|
248
|
-
usage: {
|
|
249
|
-
total_checks: total,
|
|
250
|
-
triggered_count: triggered,
|
|
251
|
-
pass_rate: passRate,
|
|
252
|
-
},
|
|
253
|
-
recent_invocations,
|
|
254
|
-
evidence,
|
|
255
|
-
sessions_with_skill: sessionsRow.c,
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// -- Cursor-based paginated queries -------------------------------------------
|
|
260
|
-
|
|
261
|
-
export interface OverviewPaginationOptions {
|
|
262
|
-
telemetry_cursor?: PaginationCursor | null;
|
|
263
|
-
telemetry_limit?: number;
|
|
264
|
-
skills_cursor?: PaginationCursor | null;
|
|
265
|
-
skills_limit?: number;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export interface SkillReportPaginationOptions {
|
|
269
|
-
invocations_cursor?: PaginationCursor | null;
|
|
270
|
-
invocations_limit?: number;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Build a paginated overview payload from SQLite.
|
|
275
|
-
*
|
|
276
|
-
* Uses (timestamp, session_id) composite cursors for stable backward pagination.
|
|
277
|
-
* When no cursor is provided, returns the first page starting from most recent.
|
|
278
|
-
*/
|
|
279
|
-
export function getOverviewPayloadPaginated(
|
|
280
|
-
db: Database,
|
|
281
|
-
opts: OverviewPaginationOptions = {},
|
|
282
|
-
): OverviewPaginatedPayload {
|
|
283
|
-
const telemetryLimit = opts.telemetry_limit ?? 1000;
|
|
284
|
-
const skillsLimit = opts.skills_limit ?? 2000;
|
|
285
|
-
|
|
286
|
-
// Paginated telemetry
|
|
287
|
-
const telemetry_page = paginateTelemetry(db, telemetryLimit, opts.telemetry_cursor ?? null);
|
|
288
|
-
|
|
289
|
-
// Paginated skill invocations
|
|
290
|
-
const skills_page = paginateSkillInvocations(db, skillsLimit, opts.skills_cursor ?? null);
|
|
291
|
-
|
|
292
|
-
// Non-paginated parts reuse existing logic
|
|
293
|
-
const evolution = db
|
|
294
|
-
.query(
|
|
295
|
-
`SELECT timestamp, proposal_id, skill_name, action, details
|
|
296
|
-
FROM evolution_audit
|
|
297
|
-
ORDER BY timestamp DESC
|
|
298
|
-
LIMIT 500`,
|
|
299
|
-
)
|
|
300
|
-
.all() as Array<{
|
|
301
|
-
timestamp: string;
|
|
302
|
-
proposal_id: string;
|
|
303
|
-
skill_name: string | null;
|
|
304
|
-
action: string;
|
|
305
|
-
details: string;
|
|
306
|
-
}>;
|
|
307
|
-
|
|
308
|
-
const counts = db
|
|
309
|
-
.query(
|
|
310
|
-
`SELECT
|
|
311
|
-
(SELECT COUNT(*) FROM session_telemetry) as telemetry,
|
|
312
|
-
(SELECT COUNT(*) FROM skill_invocations) as skills,
|
|
313
|
-
(SELECT COUNT(*) FROM evolution_audit) as evolution,
|
|
314
|
-
(SELECT COUNT(*) FROM evolution_evidence) as evidence,
|
|
315
|
-
(SELECT COUNT(*) FROM sessions) as sessions,
|
|
316
|
-
(SELECT COUNT(*) FROM prompts) as prompts`,
|
|
317
|
-
)
|
|
318
|
-
.get() as {
|
|
319
|
-
telemetry: number;
|
|
320
|
-
skills: number;
|
|
321
|
-
evolution: number;
|
|
322
|
-
evidence: number;
|
|
323
|
-
sessions: number;
|
|
324
|
-
prompts: number;
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
const unmatchedRows = db
|
|
328
|
-
.query(
|
|
329
|
-
`SELECT si.occurred_at AS timestamp, si.session_id, si.query
|
|
330
|
-
FROM skill_invocations si
|
|
331
|
-
WHERE si.triggered = 0
|
|
332
|
-
AND NOT EXISTS (
|
|
333
|
-
SELECT 1 FROM skill_invocations si2
|
|
334
|
-
WHERE si2.query = si.query AND si2.triggered = 1
|
|
335
|
-
)
|
|
336
|
-
ORDER BY si.occurred_at DESC
|
|
337
|
-
LIMIT 500`,
|
|
338
|
-
)
|
|
339
|
-
.all() as Array<{ timestamp: string; session_id: string; query: string }>;
|
|
340
|
-
|
|
341
|
-
const pending_proposals = getPendingProposals(db);
|
|
342
|
-
const active_sessions = getActiveSessionCount(db);
|
|
343
|
-
const recent_activity = getRecentActivity(db);
|
|
344
|
-
|
|
345
|
-
return {
|
|
346
|
-
telemetry_page,
|
|
347
|
-
skills_page,
|
|
348
|
-
evolution,
|
|
349
|
-
counts,
|
|
350
|
-
unmatched_queries: unmatchedRows,
|
|
351
|
-
pending_proposals,
|
|
352
|
-
active_sessions,
|
|
353
|
-
recent_activity,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Build a paginated skill report payload for a specific skill.
|
|
359
|
-
*
|
|
360
|
-
* Uses (occurred_at, skill_invocation_id) composite cursor for the recent
|
|
361
|
-
* invocations sub-query. Non-paginated fields (usage stats, evidence, sessions)
|
|
362
|
-
* are returned in full.
|
|
363
|
-
*/
|
|
364
|
-
export function getSkillReportPayloadPaginated(
|
|
365
|
-
db: Database,
|
|
366
|
-
skillName: string,
|
|
367
|
-
opts: SkillReportPaginationOptions = {},
|
|
368
|
-
): SkillReportPaginatedPayload {
|
|
369
|
-
const invocationsLimit = opts.invocations_limit ?? 100;
|
|
370
|
-
|
|
371
|
-
// Usage stats (unchanged)
|
|
372
|
-
const usageRow = db
|
|
373
|
-
.query(
|
|
374
|
-
`SELECT
|
|
375
|
-
COUNT(*) as total_checks,
|
|
376
|
-
SUM(CASE WHEN triggered = 1 THEN 1 ELSE 0 END) as triggered_count
|
|
377
|
-
FROM skill_invocations
|
|
378
|
-
WHERE skill_name = ?`,
|
|
379
|
-
)
|
|
380
|
-
.get(skillName) as { total_checks: number; triggered_count: number };
|
|
381
|
-
|
|
382
|
-
const total = usageRow.total_checks;
|
|
383
|
-
const triggered = usageRow.triggered_count;
|
|
384
|
-
const passRate = total > 0 ? triggered / total : 0;
|
|
385
|
-
|
|
386
|
-
// Paginated invocations
|
|
387
|
-
const invocations_page = paginateSkillReportInvocations(
|
|
388
|
-
db,
|
|
389
|
-
skillName,
|
|
390
|
-
invocationsLimit,
|
|
391
|
-
opts.invocations_cursor ?? null,
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
// Evidence (unchanged)
|
|
395
|
-
const evidenceRows = db
|
|
396
|
-
.query(
|
|
397
|
-
`SELECT proposal_id, target, stage, timestamp, rationale, confidence,
|
|
398
|
-
original_text, proposed_text, validation_json, details, eval_set_json
|
|
399
|
-
FROM evolution_evidence
|
|
400
|
-
WHERE skill_name = ?
|
|
401
|
-
ORDER BY timestamp DESC
|
|
402
|
-
LIMIT 200`,
|
|
403
|
-
)
|
|
404
|
-
.all(skillName) as Array<{
|
|
405
|
-
proposal_id: string;
|
|
406
|
-
target: string;
|
|
407
|
-
stage: string;
|
|
408
|
-
timestamp: string;
|
|
409
|
-
rationale: string | null;
|
|
410
|
-
confidence: number | null;
|
|
411
|
-
original_text: string | null;
|
|
412
|
-
proposed_text: string | null;
|
|
413
|
-
validation_json: string | null;
|
|
414
|
-
details: string | null;
|
|
415
|
-
eval_set_json: string | null;
|
|
416
|
-
}>;
|
|
417
|
-
|
|
418
|
-
const evidence = evidenceRows.map((row) => ({
|
|
419
|
-
proposal_id: row.proposal_id,
|
|
420
|
-
target: row.target,
|
|
421
|
-
stage: row.stage,
|
|
422
|
-
timestamp: row.timestamp,
|
|
423
|
-
rationale: row.rationale,
|
|
424
|
-
confidence: row.confidence,
|
|
425
|
-
original_text: row.original_text,
|
|
426
|
-
proposed_text: row.proposed_text,
|
|
427
|
-
validation: safeParseJson(row.validation_json),
|
|
428
|
-
details: row.details,
|
|
429
|
-
eval_set: safeParseJsonArray<Record<string, unknown>>(row.eval_set_json),
|
|
430
|
-
}));
|
|
431
|
-
|
|
432
|
-
const sessionsRow = db
|
|
433
|
-
.query(`SELECT COUNT(DISTINCT session_id) as c FROM skill_invocations WHERE skill_name = ?`)
|
|
434
|
-
.get(skillName) as { c: number };
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
skill_name: skillName,
|
|
438
|
-
usage: {
|
|
439
|
-
total_checks: total,
|
|
440
|
-
triggered_count: triggered,
|
|
441
|
-
pass_rate: passRate,
|
|
442
|
-
},
|
|
443
|
-
invocations_page,
|
|
444
|
-
evidence,
|
|
445
|
-
sessions_with_skill: sessionsRow.c,
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// -- Internal pagination helpers ------------------------------------------------
|
|
450
|
-
|
|
451
|
-
function paginateTelemetry(
|
|
452
|
-
db: Database,
|
|
453
|
-
limit: number,
|
|
454
|
-
cursor: PaginationCursor | null,
|
|
455
|
-
): PaginatedResult<TelemetryRecord> {
|
|
456
|
-
// Fetch one extra to detect has_more
|
|
457
|
-
const fetchLimit = limit + 1;
|
|
458
|
-
|
|
459
|
-
let rows: Array<{
|
|
460
|
-
timestamp: string;
|
|
461
|
-
session_id: string;
|
|
462
|
-
skills_triggered_json: string | null;
|
|
463
|
-
errors_encountered: number;
|
|
464
|
-
total_tool_calls: number;
|
|
465
|
-
}>;
|
|
466
|
-
|
|
467
|
-
if (cursor) {
|
|
468
|
-
rows = db
|
|
469
|
-
.query(
|
|
470
|
-
`SELECT timestamp, session_id, skills_triggered_json, errors_encountered, total_tool_calls
|
|
471
|
-
FROM session_telemetry
|
|
472
|
-
WHERE (timestamp < ? OR (timestamp = ? AND session_id < ?))
|
|
473
|
-
ORDER BY timestamp DESC, session_id DESC
|
|
474
|
-
LIMIT ?`,
|
|
475
|
-
)
|
|
476
|
-
.all(cursor.timestamp, cursor.timestamp, String(cursor.id), fetchLimit) as typeof rows;
|
|
477
|
-
} else {
|
|
478
|
-
rows = db
|
|
479
|
-
.query(
|
|
480
|
-
`SELECT timestamp, session_id, skills_triggered_json, errors_encountered, total_tool_calls
|
|
481
|
-
FROM session_telemetry
|
|
482
|
-
ORDER BY timestamp DESC, session_id DESC
|
|
483
|
-
LIMIT ?`,
|
|
484
|
-
)
|
|
485
|
-
.all(fetchLimit) as typeof rows;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const hasMore = rows.length > limit;
|
|
489
|
-
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
490
|
-
|
|
491
|
-
const items: TelemetryRecord[] = pageRows.map((row) => ({
|
|
492
|
-
timestamp: row.timestamp,
|
|
493
|
-
session_id: row.session_id,
|
|
494
|
-
skills_triggered: safeParseJsonArray<string>(row.skills_triggered_json),
|
|
495
|
-
errors_encountered: row.errors_encountered,
|
|
496
|
-
total_tool_calls: row.total_tool_calls,
|
|
497
|
-
}));
|
|
498
|
-
|
|
499
|
-
const lastItem = pageRows[pageRows.length - 1];
|
|
500
|
-
const next_cursor: PaginationCursor | null =
|
|
501
|
-
hasMore && lastItem ? { timestamp: lastItem.timestamp, id: lastItem.session_id } : null;
|
|
502
|
-
|
|
503
|
-
return { items, next_cursor, has_more: hasMore };
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function paginateSkillInvocations(
|
|
507
|
-
db: Database,
|
|
508
|
-
limit: number,
|
|
509
|
-
cursor: PaginationCursor | null,
|
|
510
|
-
): PaginatedResult<SkillUsageRecord> {
|
|
511
|
-
const fetchLimit = limit + 1;
|
|
512
|
-
|
|
513
|
-
let rows: Array<{
|
|
514
|
-
occurred_at: string;
|
|
515
|
-
session_id: string;
|
|
516
|
-
skill_name: string;
|
|
517
|
-
skill_path: string;
|
|
518
|
-
query: string;
|
|
519
|
-
triggered: number;
|
|
520
|
-
source: string | null;
|
|
521
|
-
skill_invocation_id: string;
|
|
522
|
-
}>;
|
|
523
|
-
|
|
524
|
-
if (cursor) {
|
|
525
|
-
rows = db
|
|
526
|
-
.query(
|
|
527
|
-
`SELECT occurred_at, session_id, skill_name, skill_path, query, triggered, source, skill_invocation_id
|
|
528
|
-
FROM skill_invocations
|
|
529
|
-
WHERE (occurred_at < ? OR (occurred_at = ? AND skill_invocation_id < ?))
|
|
530
|
-
ORDER BY occurred_at DESC, skill_invocation_id DESC
|
|
531
|
-
LIMIT ?`,
|
|
532
|
-
)
|
|
533
|
-
.all(cursor.timestamp, cursor.timestamp, String(cursor.id), fetchLimit) as typeof rows;
|
|
534
|
-
} else {
|
|
535
|
-
rows = db
|
|
536
|
-
.query(
|
|
537
|
-
`SELECT occurred_at, session_id, skill_name, skill_path, query, triggered, source, skill_invocation_id
|
|
538
|
-
FROM skill_invocations
|
|
539
|
-
ORDER BY occurred_at DESC, skill_invocation_id DESC
|
|
540
|
-
LIMIT ?`,
|
|
541
|
-
)
|
|
542
|
-
.all(fetchLimit) as typeof rows;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
const hasMore = rows.length > limit;
|
|
546
|
-
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
547
|
-
|
|
548
|
-
const items: SkillUsageRecord[] = pageRows.map((row) => ({
|
|
549
|
-
timestamp: row.occurred_at,
|
|
550
|
-
session_id: row.session_id,
|
|
551
|
-
skill_name: row.skill_name,
|
|
552
|
-
skill_path: row.skill_path,
|
|
553
|
-
query: row.query,
|
|
554
|
-
triggered: row.triggered === 1,
|
|
555
|
-
source: row.source,
|
|
556
|
-
}));
|
|
557
|
-
|
|
558
|
-
const lastRow = pageRows[pageRows.length - 1];
|
|
559
|
-
const next_cursor: PaginationCursor | null =
|
|
560
|
-
hasMore && lastRow ? { timestamp: lastRow.occurred_at, id: lastRow.skill_invocation_id } : null;
|
|
561
|
-
|
|
562
|
-
return { items, next_cursor, has_more: hasMore };
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function paginateSkillReportInvocations(
|
|
566
|
-
db: Database,
|
|
567
|
-
skillName: string,
|
|
568
|
-
limit: number,
|
|
569
|
-
cursor: PaginationCursor | null,
|
|
570
|
-
): PaginatedResult<{
|
|
571
|
-
timestamp: string;
|
|
572
|
-
session_id: string;
|
|
573
|
-
query: string;
|
|
574
|
-
triggered: boolean;
|
|
575
|
-
source: string | null;
|
|
576
|
-
}> {
|
|
577
|
-
const fetchLimit = limit + 1;
|
|
578
|
-
|
|
579
|
-
let rows: Array<{
|
|
580
|
-
occurred_at: string;
|
|
581
|
-
session_id: string;
|
|
582
|
-
query: string;
|
|
583
|
-
triggered: number;
|
|
584
|
-
source: string | null;
|
|
585
|
-
skill_invocation_id: string;
|
|
586
|
-
}>;
|
|
587
|
-
|
|
588
|
-
if (cursor) {
|
|
589
|
-
rows = db
|
|
590
|
-
.query(
|
|
591
|
-
`SELECT si.occurred_at, si.session_id, COALESCE(si.query, p.prompt_text) as query,
|
|
592
|
-
si.triggered, si.source, si.skill_invocation_id
|
|
593
|
-
FROM skill_invocations si
|
|
594
|
-
LEFT JOIN prompts p ON si.matched_prompt_id = p.prompt_id
|
|
595
|
-
WHERE si.skill_name = ?
|
|
596
|
-
AND (si.occurred_at < ? OR (si.occurred_at = ? AND si.skill_invocation_id < ?))
|
|
597
|
-
ORDER BY si.occurred_at DESC, si.skill_invocation_id DESC
|
|
598
|
-
LIMIT ?`,
|
|
599
|
-
)
|
|
600
|
-
.all(
|
|
601
|
-
skillName,
|
|
602
|
-
cursor.timestamp,
|
|
603
|
-
cursor.timestamp,
|
|
604
|
-
String(cursor.id),
|
|
605
|
-
fetchLimit,
|
|
606
|
-
) as typeof rows;
|
|
607
|
-
} else {
|
|
608
|
-
rows = db
|
|
609
|
-
.query(
|
|
610
|
-
`SELECT si.occurred_at, si.session_id, COALESCE(si.query, p.prompt_text) as query,
|
|
611
|
-
si.triggered, si.source, si.skill_invocation_id
|
|
612
|
-
FROM skill_invocations si
|
|
613
|
-
LEFT JOIN prompts p ON si.matched_prompt_id = p.prompt_id
|
|
614
|
-
WHERE si.skill_name = ?
|
|
615
|
-
ORDER BY si.occurred_at DESC, si.skill_invocation_id DESC
|
|
616
|
-
LIMIT ?`,
|
|
617
|
-
)
|
|
618
|
-
.all(skillName, fetchLimit) as typeof rows;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const hasMore = rows.length > limit;
|
|
622
|
-
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
623
|
-
|
|
624
|
-
const items = pageRows.map((row) => ({
|
|
625
|
-
timestamp: row.occurred_at,
|
|
626
|
-
session_id: row.session_id,
|
|
627
|
-
query: row.query ?? "",
|
|
628
|
-
triggered: row.triggered === 1,
|
|
629
|
-
source: row.source,
|
|
630
|
-
}));
|
|
631
|
-
|
|
632
|
-
const lastRow = pageRows[pageRows.length - 1];
|
|
633
|
-
const next_cursor: PaginationCursor | null =
|
|
634
|
-
hasMore && lastRow ? { timestamp: lastRow.occurred_at, id: lastRow.skill_invocation_id } : null;
|
|
635
|
-
|
|
636
|
-
return { items, next_cursor, has_more: hasMore };
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* Get a summary list of all skills with aggregated stats.
|
|
641
|
-
*/
|
|
642
|
-
export function getSkillsList(db: Database): SkillSummary[] {
|
|
643
|
-
const trustedRows = queryTrustedSkillObservationRows(db);
|
|
644
|
-
const bySkill = new Map<
|
|
645
|
-
string,
|
|
646
|
-
Array<{
|
|
647
|
-
skill_name: string;
|
|
648
|
-
session_id: string;
|
|
649
|
-
occurred_at: string | null;
|
|
650
|
-
triggered: number;
|
|
651
|
-
matched_prompt_id: string | null;
|
|
652
|
-
confidence: number | null;
|
|
653
|
-
}>
|
|
654
|
-
>();
|
|
655
|
-
|
|
656
|
-
for (const row of trustedRows) {
|
|
657
|
-
const arr = bySkill.get(row.skill_name);
|
|
658
|
-
const base = {
|
|
659
|
-
skill_name: row.skill_name,
|
|
660
|
-
session_id: row.session_id,
|
|
661
|
-
occurred_at: row.occurred_at,
|
|
662
|
-
triggered: row.triggered,
|
|
663
|
-
matched_prompt_id: row.matched_prompt_id,
|
|
664
|
-
confidence: row.confidence,
|
|
665
|
-
};
|
|
666
|
-
if (arr) arr.push(base);
|
|
667
|
-
else bySkill.set(row.skill_name, [base]);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Get set of skill names with evidence
|
|
671
|
-
const evidenceSkills = new Set(
|
|
672
|
-
(
|
|
673
|
-
db.query(`SELECT DISTINCT skill_name FROM evolution_evidence`).all() as Array<{
|
|
674
|
-
skill_name: string;
|
|
675
|
-
}>
|
|
676
|
-
).map((r) => r.skill_name),
|
|
677
|
-
);
|
|
678
|
-
|
|
679
|
-
const skillScopeRows = db
|
|
680
|
-
.query(
|
|
681
|
-
`SELECT
|
|
682
|
-
si.skill_name,
|
|
683
|
-
COALESCE(
|
|
684
|
-
(SELECT s2.skill_scope FROM skill_invocations s2
|
|
685
|
-
WHERE s2.skill_name = si.skill_name AND s2.skill_scope IS NOT NULL
|
|
686
|
-
ORDER BY s2.occurred_at DESC LIMIT 1),
|
|
687
|
-
(SELECT su.skill_scope FROM skill_usage su
|
|
688
|
-
WHERE su.skill_name = si.skill_name AND su.skill_scope IS NOT NULL
|
|
689
|
-
ORDER BY su.timestamp DESC LIMIT 1)
|
|
690
|
-
) as skill_scope
|
|
691
|
-
FROM skill_invocations si
|
|
692
|
-
GROUP BY si.skill_name`,
|
|
693
|
-
)
|
|
694
|
-
.all() as Array<{ skill_name: string; skill_scope: string | null }>;
|
|
695
|
-
const scopeBySkill = new Map(skillScopeRows.map((row) => [row.skill_name, row.skill_scope]));
|
|
696
|
-
|
|
697
|
-
return [...bySkill.entries()]
|
|
698
|
-
.map(([skillName, rows]) => {
|
|
699
|
-
const totalChecks = rows.length;
|
|
700
|
-
const triggeredCount = rows.filter((row) => row.triggered === 1).length;
|
|
701
|
-
const uniqueSessions = new Set(rows.map((row) => row.session_id)).size;
|
|
702
|
-
const lastSeen =
|
|
703
|
-
rows
|
|
704
|
-
.map((row) => row.occurred_at)
|
|
705
|
-
.filter((value): value is string => value != null)
|
|
706
|
-
.sort((a, b) => b.localeCompare(a))[0] ?? null;
|
|
707
|
-
const withConfidence = rows.filter((row) => row.confidence != null);
|
|
708
|
-
const routingConfidence =
|
|
709
|
-
withConfidence.length > 0
|
|
710
|
-
? withConfidence.reduce((sum, row) => sum + (row.confidence ?? 0), 0) /
|
|
711
|
-
withConfidence.length
|
|
712
|
-
: null;
|
|
713
|
-
|
|
714
|
-
return {
|
|
715
|
-
skill_name: skillName,
|
|
716
|
-
skill_scope: scopeBySkill.get(skillName) ?? null,
|
|
717
|
-
total_checks: totalChecks,
|
|
718
|
-
triggered_count: triggeredCount,
|
|
719
|
-
pass_rate: totalChecks > 0 ? triggeredCount / totalChecks : 0,
|
|
720
|
-
unique_sessions: uniqueSessions,
|
|
721
|
-
last_seen: lastSeen,
|
|
722
|
-
has_evidence: evidenceSkills.has(skillName),
|
|
723
|
-
routing_confidence: routingConfidence,
|
|
724
|
-
confidence_coverage: totalChecks > 0 ? withConfidence.length / totalChecks : 0,
|
|
725
|
-
};
|
|
726
|
-
})
|
|
727
|
-
.sort((a, b) => b.total_checks - a.total_checks);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
/**
|
|
731
|
-
* Build the performance analytics payload from SQLite.
|
|
732
|
-
* Powers the GET /api/v2/analytics endpoint.
|
|
733
|
-
*/
|
|
734
|
-
export function getAnalyticsPayload(db: Database): AnalyticsResponse {
|
|
735
|
-
const trustedRows = queryTrustedSkillObservationRows(db);
|
|
736
|
-
const today = new Date();
|
|
737
|
-
const dateKey = (value: string | null): string | null => {
|
|
738
|
-
if (!value) return null;
|
|
739
|
-
const parsed = new Date(value);
|
|
740
|
-
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString().slice(0, 10);
|
|
741
|
-
};
|
|
742
|
-
const cutoffDate = (days: number): string => {
|
|
743
|
-
const cutoff = new Date(today);
|
|
744
|
-
cutoff.setUTCDate(cutoff.getUTCDate() - days);
|
|
745
|
-
return cutoff.toISOString().slice(0, 10);
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
// 1. Pass rate trend — last 90 days, bucketed by day
|
|
749
|
-
const passRateTrendByDate = new Map<string, { triggered: number; total: number }>();
|
|
750
|
-
for (const row of trustedRows) {
|
|
751
|
-
const occurredDate = dateKey(row.occurred_at);
|
|
752
|
-
if (!occurredDate || occurredDate < cutoffDate(90)) continue;
|
|
753
|
-
const counts = passRateTrendByDate.get(occurredDate) ?? { triggered: 0, total: 0 };
|
|
754
|
-
counts.total += 1;
|
|
755
|
-
if (row.triggered === 1) counts.triggered += 1;
|
|
756
|
-
passRateTrendByDate.set(occurredDate, counts);
|
|
757
|
-
}
|
|
758
|
-
const passRateTrendRows = [...passRateTrendByDate.entries()]
|
|
759
|
-
.map(([date, counts]) => ({
|
|
760
|
-
date,
|
|
761
|
-
pass_rate: counts.total > 0 ? counts.triggered / counts.total : 0,
|
|
762
|
-
total_checks: counts.total,
|
|
763
|
-
}))
|
|
764
|
-
.sort((a, b) => a.date.localeCompare(b.date));
|
|
765
|
-
|
|
766
|
-
const pass_rate_trend = passRateTrendRows.map((row) => ({
|
|
767
|
-
date: row.date,
|
|
768
|
-
pass_rate: row.pass_rate,
|
|
769
|
-
total_checks: row.total_checks,
|
|
770
|
-
}));
|
|
771
|
-
|
|
772
|
-
// 2. Skill rankings — all skills with at least 1 check, ordered by pass rate
|
|
773
|
-
const skillRankingMap = new Map<string, { triggered_count: number; total_checks: number }>();
|
|
774
|
-
for (const row of trustedRows) {
|
|
775
|
-
const counts = skillRankingMap.get(row.skill_name) ?? { triggered_count: 0, total_checks: 0 };
|
|
776
|
-
counts.total_checks += 1;
|
|
777
|
-
if (row.triggered === 1) counts.triggered_count += 1;
|
|
778
|
-
skillRankingMap.set(row.skill_name, counts);
|
|
779
|
-
}
|
|
780
|
-
const skillRankingRows = [...skillRankingMap.entries()]
|
|
781
|
-
.map(([skill_name, counts]) => ({
|
|
782
|
-
skill_name,
|
|
783
|
-
pass_rate: counts.total_checks > 0 ? counts.triggered_count / counts.total_checks : 0,
|
|
784
|
-
total_checks: counts.total_checks,
|
|
785
|
-
triggered_count: counts.triggered_count,
|
|
786
|
-
}))
|
|
787
|
-
.sort(
|
|
788
|
-
(a, b) =>
|
|
789
|
-
b.pass_rate - a.pass_rate ||
|
|
790
|
-
b.total_checks - a.total_checks ||
|
|
791
|
-
a.skill_name.localeCompare(b.skill_name),
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
const skill_rankings = skillRankingRows.map((row) => ({
|
|
795
|
-
skill_name: row.skill_name,
|
|
796
|
-
pass_rate: row.pass_rate,
|
|
797
|
-
total_checks: row.total_checks,
|
|
798
|
-
triggered_count: row.triggered_count,
|
|
799
|
-
}));
|
|
800
|
-
|
|
801
|
-
// 3. Daily activity — last 84 days (12 weeks) for heatmap
|
|
802
|
-
const dailyActivityByDate = new Map<string, number>();
|
|
803
|
-
for (const row of trustedRows) {
|
|
804
|
-
const occurredDate = dateKey(row.occurred_at);
|
|
805
|
-
if (!occurredDate || occurredDate < cutoffDate(84)) continue;
|
|
806
|
-
dailyActivityByDate.set(occurredDate, (dailyActivityByDate.get(occurredDate) ?? 0) + 1);
|
|
807
|
-
}
|
|
808
|
-
const dailyActivityRows = [...dailyActivityByDate.entries()]
|
|
809
|
-
.map(([date, checks]) => ({ date, checks }))
|
|
810
|
-
.sort((a, b) => a.date.localeCompare(b.date));
|
|
811
|
-
|
|
812
|
-
const daily_activity = dailyActivityRows.map((row) => ({
|
|
813
|
-
date: row.date,
|
|
814
|
-
checks: row.checks,
|
|
815
|
-
}));
|
|
816
|
-
|
|
817
|
-
// 4. Evolution impact — before/after pass rates for deployed evolutions
|
|
818
|
-
const deployedRows = db
|
|
819
|
-
.query(
|
|
820
|
-
`SELECT ea.skill_name, ea.proposal_id, ea.timestamp as deployed_at
|
|
821
|
-
FROM evolution_audit ea
|
|
822
|
-
WHERE ea.action = 'deployed' AND ea.skill_name IS NOT NULL
|
|
823
|
-
ORDER BY ea.timestamp DESC`,
|
|
824
|
-
)
|
|
825
|
-
.all() as Array<{ skill_name: string; proposal_id: string; deployed_at: string }>;
|
|
826
|
-
|
|
827
|
-
const evolution_impact: AnalyticsResponse["evolution_impact"] = [];
|
|
828
|
-
for (const deploy of deployedRows) {
|
|
829
|
-
const beforeRows = trustedRows.filter(
|
|
830
|
-
(row) => row.skill_name === deploy.skill_name && (row.occurred_at ?? "") < deploy.deployed_at,
|
|
831
|
-
);
|
|
832
|
-
const afterRows = trustedRows.filter(
|
|
833
|
-
(row) =>
|
|
834
|
-
row.skill_name === deploy.skill_name && (row.occurred_at ?? "") >= deploy.deployed_at,
|
|
835
|
-
);
|
|
836
|
-
|
|
837
|
-
evolution_impact.push({
|
|
838
|
-
skill_name: deploy.skill_name,
|
|
839
|
-
proposal_id: deploy.proposal_id,
|
|
840
|
-
deployed_at: deploy.deployed_at,
|
|
841
|
-
pass_rate_before:
|
|
842
|
-
beforeRows.length > 0
|
|
843
|
-
? beforeRows.filter((row) => row.triggered === 1).length / beforeRows.length
|
|
844
|
-
: 0,
|
|
845
|
-
pass_rate_after:
|
|
846
|
-
afterRows.length > 0
|
|
847
|
-
? afterRows.filter((row) => row.triggered === 1).length / afterRows.length
|
|
848
|
-
: 0,
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
// 5. Summary aggregates
|
|
853
|
-
const totalEvolutionsRow = db
|
|
854
|
-
.query(`SELECT COUNT(*) as c FROM evolution_audit WHERE action = 'deployed'`)
|
|
855
|
-
.get() as { c: number } | null;
|
|
856
|
-
|
|
857
|
-
const checks30dRows = trustedRows.filter((row) => {
|
|
858
|
-
const occurredDate = dateKey(row.occurred_at);
|
|
859
|
-
return occurredDate != null && occurredDate >= cutoffDate(30);
|
|
860
|
-
});
|
|
861
|
-
const activeSkills30d = new Set(checks30dRows.map((row) => row.skill_name));
|
|
862
|
-
|
|
863
|
-
// Average improvement across all deployed evolutions
|
|
864
|
-
let avgImprovement = 0;
|
|
865
|
-
if (evolution_impact.length > 0) {
|
|
866
|
-
const totalImprovement = evolution_impact.reduce(
|
|
867
|
-
(sum, e) => sum + (e.pass_rate_after - e.pass_rate_before),
|
|
868
|
-
0,
|
|
869
|
-
);
|
|
870
|
-
avgImprovement = totalImprovement / evolution_impact.length;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
const summary: AnalyticsResponse["summary"] = {
|
|
874
|
-
total_evolutions: totalEvolutionsRow?.c ?? 0,
|
|
875
|
-
avg_improvement: avgImprovement,
|
|
876
|
-
total_checks_30d: checks30dRows.length,
|
|
877
|
-
active_skills: activeSkills30d.size,
|
|
878
|
-
};
|
|
879
|
-
|
|
880
|
-
return {
|
|
881
|
-
pass_rate_trend,
|
|
882
|
-
skill_rankings,
|
|
883
|
-
daily_activity,
|
|
884
|
-
evolution_impact,
|
|
885
|
-
summary,
|
|
886
|
-
};
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* Get pending proposals (created/validated with no terminal action).
|
|
891
|
-
* Optionally filtered by skill_name.
|
|
892
|
-
*/
|
|
893
|
-
export function getPendingProposals(db: Database, skillName?: string): PendingProposal[] {
|
|
894
|
-
const whereClause = skillName ? "WHERE ea.skill_name = ? AND" : "WHERE";
|
|
895
|
-
const params = skillName ? [skillName] : [];
|
|
896
|
-
return db
|
|
897
|
-
.query(
|
|
898
|
-
`WITH latest AS (
|
|
899
|
-
SELECT ea.proposal_id, ea.action, ea.timestamp, ea.details, ea.skill_name,
|
|
900
|
-
ROW_NUMBER() OVER (PARTITION BY ea.proposal_id ORDER BY ea.timestamp DESC, ea.id DESC) AS rn
|
|
901
|
-
FROM evolution_audit ea
|
|
902
|
-
LEFT JOIN evolution_audit ea2
|
|
903
|
-
ON ea2.proposal_id = ea.proposal_id
|
|
904
|
-
AND ea2.action IN ('deployed', 'rejected', 'rolled_back')
|
|
905
|
-
${whereClause} ea.action IN ('created', 'validated')
|
|
906
|
-
AND ea2.id IS NULL
|
|
907
|
-
)
|
|
908
|
-
SELECT proposal_id, action, timestamp, details, skill_name
|
|
909
|
-
FROM latest
|
|
910
|
-
WHERE rn = 1
|
|
911
|
-
ORDER BY timestamp DESC`,
|
|
912
|
-
)
|
|
913
|
-
.all(...params) as PendingProposal[];
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
/**
|
|
917
|
-
* Get recent orchestrate run reports (most recent first).
|
|
918
|
-
*/
|
|
919
|
-
export function getOrchestrateRuns(db: Database, limit = 20): OrchestrateRunReport[] {
|
|
920
|
-
const rows = db
|
|
921
|
-
.query(
|
|
922
|
-
`SELECT run_id, timestamp, elapsed_ms, dry_run, approval_mode,
|
|
923
|
-
total_skills, evaluated, evolved, deployed, watched, skipped,
|
|
924
|
-
skill_actions_json
|
|
925
|
-
FROM orchestrate_runs
|
|
926
|
-
ORDER BY timestamp DESC
|
|
927
|
-
LIMIT ?`,
|
|
928
|
-
)
|
|
929
|
-
.all(limit) as Array<{
|
|
930
|
-
run_id: string;
|
|
931
|
-
timestamp: string;
|
|
932
|
-
elapsed_ms: number;
|
|
933
|
-
dry_run: number;
|
|
934
|
-
approval_mode: string;
|
|
935
|
-
total_skills: number;
|
|
936
|
-
evaluated: number;
|
|
937
|
-
evolved: number;
|
|
938
|
-
deployed: number;
|
|
939
|
-
watched: number;
|
|
940
|
-
skipped: number;
|
|
941
|
-
skill_actions_json: string;
|
|
942
|
-
}>;
|
|
943
|
-
|
|
944
|
-
return rows.map((r) => ({
|
|
945
|
-
run_id: r.run_id,
|
|
946
|
-
timestamp: r.timestamp,
|
|
947
|
-
elapsed_ms: r.elapsed_ms,
|
|
948
|
-
dry_run: r.dry_run === 1,
|
|
949
|
-
approval_mode: r.approval_mode as "auto" | "review",
|
|
950
|
-
total_skills: r.total_skills,
|
|
951
|
-
evaluated: r.evaluated,
|
|
952
|
-
evolved: r.evolved,
|
|
953
|
-
deployed: r.deployed,
|
|
954
|
-
watched: r.watched,
|
|
955
|
-
skipped: r.skipped,
|
|
956
|
-
skill_actions: safeParseJsonArray(r.skill_actions_json),
|
|
957
|
-
}));
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/**
|
|
961
|
-
* Count sessions that have queries recorded but no session_telemetry yet
|
|
962
|
-
* (i.e., the session is still in progress).
|
|
963
|
-
*/
|
|
964
|
-
export function getActiveSessionCount(db: Database): number {
|
|
965
|
-
const row = db
|
|
966
|
-
.query(
|
|
967
|
-
`SELECT COUNT(DISTINCT q.session_id) as count
|
|
968
|
-
FROM queries q
|
|
969
|
-
WHERE NOT EXISTS (
|
|
970
|
-
SELECT 1 FROM session_telemetry st WHERE st.session_id = q.session_id
|
|
971
|
-
)`,
|
|
972
|
-
)
|
|
973
|
-
.get() as { count: number };
|
|
974
|
-
return row.count;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
/**
|
|
978
|
-
* Get the most recent skill invocations with a flag indicating whether the
|
|
979
|
-
* session is still in progress (no session_telemetry row yet).
|
|
980
|
-
*/
|
|
981
|
-
export function getRecentActivity(db: Database, limit = 20): RecentActivityItem[] {
|
|
982
|
-
// Step 1: Fetch recent invocations without JOIN (avoids materializing full JOIN before LIMIT)
|
|
983
|
-
const rows = db
|
|
984
|
-
.query(
|
|
985
|
-
`SELECT occurred_at, session_id, skill_name, query, triggered
|
|
986
|
-
FROM skill_invocations
|
|
987
|
-
ORDER BY occurred_at DESC
|
|
988
|
-
LIMIT ?`,
|
|
989
|
-
)
|
|
990
|
-
.all(limit) as Array<{
|
|
991
|
-
occurred_at: string;
|
|
992
|
-
session_id: string;
|
|
993
|
-
skill_name: string;
|
|
994
|
-
query: string;
|
|
995
|
-
triggered: number;
|
|
996
|
-
}>;
|
|
997
|
-
|
|
998
|
-
if (rows.length === 0) return [];
|
|
999
|
-
|
|
1000
|
-
// Step 2: Batch lookup which sessions have completed (have a telemetry row)
|
|
1001
|
-
const uniqueSessionIds = [...new Set(rows.map((r) => r.session_id))];
|
|
1002
|
-
const placeholders = uniqueSessionIds.map(() => "?").join(",");
|
|
1003
|
-
const completedRows = db
|
|
1004
|
-
.query(
|
|
1005
|
-
`SELECT DISTINCT session_id FROM session_telemetry WHERE session_id IN (${placeholders})`,
|
|
1006
|
-
)
|
|
1007
|
-
.all(...uniqueSessionIds) as Array<{ session_id: string }>;
|
|
1008
|
-
const completedSessions = new Set(completedRows.map((r) => r.session_id));
|
|
1009
|
-
|
|
1010
|
-
return rows.map((row) => ({
|
|
1011
|
-
timestamp: row.occurred_at,
|
|
1012
|
-
session_id: row.session_id,
|
|
1013
|
-
skill_name: row.skill_name,
|
|
1014
|
-
query: row.query ?? "",
|
|
1015
|
-
triggered: row.triggered === 1,
|
|
1016
|
-
is_live: !completedSessions.has(row.session_id),
|
|
1017
|
-
}));
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// -- Generic read queries (Phase 3: replace readJsonl calls) ------------------
|
|
1021
|
-
|
|
1022
|
-
/**
|
|
1023
|
-
* Read all session telemetry records from SQLite.
|
|
1024
|
-
* Replaces: readJsonl<SessionTelemetryRecord>(TELEMETRY_LOG)
|
|
1025
|
-
*/
|
|
1026
|
-
export function querySessionTelemetry(db: Database): Array<{
|
|
1027
|
-
timestamp: string;
|
|
1028
|
-
session_id: string;
|
|
1029
|
-
cwd: string;
|
|
1030
|
-
transcript_path: string;
|
|
1031
|
-
tool_calls: Record<string, number>;
|
|
1032
|
-
total_tool_calls: number;
|
|
1033
|
-
bash_commands: string[];
|
|
1034
|
-
skills_triggered: string[];
|
|
1035
|
-
skills_invoked?: string[];
|
|
1036
|
-
assistant_turns: number;
|
|
1037
|
-
errors_encountered: number;
|
|
1038
|
-
transcript_chars: number;
|
|
1039
|
-
last_user_query: string;
|
|
1040
|
-
source?: string;
|
|
1041
|
-
input_tokens?: number;
|
|
1042
|
-
output_tokens?: number;
|
|
1043
|
-
}> {
|
|
1044
|
-
const rows = db.query(`SELECT * FROM session_telemetry ORDER BY timestamp DESC`).all() as Array<
|
|
1045
|
-
Record<string, unknown>
|
|
1046
|
-
>;
|
|
1047
|
-
return rows.map((r) => ({
|
|
1048
|
-
timestamp: r.timestamp as string,
|
|
1049
|
-
session_id: r.session_id as string,
|
|
1050
|
-
cwd: r.cwd as string,
|
|
1051
|
-
transcript_path: r.transcript_path as string,
|
|
1052
|
-
tool_calls: (safeParseJson(r.tool_calls_json as string) as Record<string, number>) ?? {},
|
|
1053
|
-
total_tool_calls: r.total_tool_calls as number,
|
|
1054
|
-
bash_commands: safeParseJsonArray<string>(r.bash_commands_json as string),
|
|
1055
|
-
skills_triggered: safeParseJsonArray<string>(r.skills_triggered_json as string),
|
|
1056
|
-
skills_invoked: r.skills_invoked_json
|
|
1057
|
-
? safeParseJsonArray<string>(r.skills_invoked_json as string)
|
|
1058
|
-
: undefined,
|
|
1059
|
-
assistant_turns: r.assistant_turns as number,
|
|
1060
|
-
errors_encountered: r.errors_encountered as number,
|
|
1061
|
-
transcript_chars: (r.transcript_chars as number) ?? 0,
|
|
1062
|
-
last_user_query: (r.last_user_query as string) ?? "",
|
|
1063
|
-
source: r.source as string | undefined,
|
|
1064
|
-
input_tokens: r.input_tokens as number | undefined,
|
|
1065
|
-
output_tokens: r.output_tokens as number | undefined,
|
|
1066
|
-
}));
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
/**
|
|
1070
|
-
* Read all skill invocation records from SQLite.
|
|
1071
|
-
* Replaces: readEffectiveSkillUsageRecords()
|
|
1072
|
-
*/
|
|
1073
|
-
export function querySkillRecords(db: Database): Array<{
|
|
1074
|
-
timestamp: string;
|
|
1075
|
-
session_id: string;
|
|
1076
|
-
skill_name: string;
|
|
1077
|
-
skill_path: string;
|
|
1078
|
-
skill_scope?: string;
|
|
1079
|
-
query: string;
|
|
1080
|
-
triggered: boolean;
|
|
1081
|
-
source?: string;
|
|
1082
|
-
}> {
|
|
1083
|
-
const rows = db
|
|
1084
|
-
.query(
|
|
1085
|
-
`SELECT occurred_at, session_id, skill_name, skill_path, skill_scope, query, triggered, source
|
|
1086
|
-
FROM skill_invocations ORDER BY occurred_at DESC`,
|
|
1087
|
-
)
|
|
1088
|
-
.all() as Array<Record<string, unknown>>;
|
|
1089
|
-
return rows.map((r) => ({
|
|
1090
|
-
timestamp: r.occurred_at as string,
|
|
1091
|
-
session_id: r.session_id as string,
|
|
1092
|
-
skill_name: r.skill_name as string,
|
|
1093
|
-
skill_path: r.skill_path as string,
|
|
1094
|
-
skill_scope: r.skill_scope as string | undefined,
|
|
1095
|
-
query: r.query as string,
|
|
1096
|
-
triggered: (r.triggered as number) === 1,
|
|
1097
|
-
source: r.source as string | undefined,
|
|
1098
|
-
}));
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
/** @deprecated Use querySkillRecords instead. Kept for backward compatibility. */
|
|
1102
|
-
export const querySkillUsageRecords = querySkillRecords;
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* Read all query log records from SQLite.
|
|
1106
|
-
* Replaces: readJsonl<QueryLogRecord>(QUERY_LOG)
|
|
1107
|
-
*/
|
|
1108
|
-
export function queryQueryLog(db: Database): Array<{
|
|
1109
|
-
timestamp: string;
|
|
1110
|
-
session_id: string;
|
|
1111
|
-
query: string;
|
|
1112
|
-
source?: string;
|
|
1113
|
-
}> {
|
|
1114
|
-
return db
|
|
1115
|
-
.query(`SELECT timestamp, session_id, query, source FROM queries ORDER BY timestamp DESC`)
|
|
1116
|
-
.all() as Array<{ timestamp: string; session_id: string; query: string; source?: string }>;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
/**
|
|
1120
|
-
* Read all evolution audit entries from SQLite.
|
|
1121
|
-
* Replaces: readJsonl<EvolutionAuditEntry>(EVOLUTION_AUDIT_LOG)
|
|
1122
|
-
*/
|
|
1123
|
-
export function queryEvolutionAudit(
|
|
1124
|
-
db: Database,
|
|
1125
|
-
skillName?: string,
|
|
1126
|
-
): Array<{
|
|
1127
|
-
timestamp: string;
|
|
1128
|
-
proposal_id: string;
|
|
1129
|
-
skill_name?: string;
|
|
1130
|
-
action: string;
|
|
1131
|
-
details: string;
|
|
1132
|
-
eval_snapshot?: Record<string, unknown>;
|
|
1133
|
-
validation_mode?: string;
|
|
1134
|
-
validation_agent?: string;
|
|
1135
|
-
validation_fixture_id?: string;
|
|
1136
|
-
validation_evidence_ref?: string;
|
|
1137
|
-
}> {
|
|
1138
|
-
const sql = skillName
|
|
1139
|
-
? `SELECT * FROM evolution_audit
|
|
1140
|
-
WHERE skill_name = ?
|
|
1141
|
-
OR (skill_name IS NULL AND proposal_id LIKE 'evo-' || ? || '-%')
|
|
1142
|
-
ORDER BY timestamp DESC`
|
|
1143
|
-
: `SELECT * FROM evolution_audit ORDER BY timestamp DESC`;
|
|
1144
|
-
const rows = (skillName ? db.query(sql).all(skillName, skillName) : db.query(sql).all()) as Array<
|
|
1145
|
-
Record<string, unknown>
|
|
1146
|
-
>;
|
|
1147
|
-
return rows.map((r) => ({
|
|
1148
|
-
timestamp: r.timestamp as string,
|
|
1149
|
-
proposal_id: r.proposal_id as string,
|
|
1150
|
-
skill_name: typeof r.skill_name === "string" ? r.skill_name : undefined,
|
|
1151
|
-
action: r.action as string,
|
|
1152
|
-
details: r.details as string,
|
|
1153
|
-
eval_snapshot: r.eval_snapshot_json
|
|
1154
|
-
? (safeParseJson(r.eval_snapshot_json as string) as Record<string, unknown>)
|
|
1155
|
-
: undefined,
|
|
1156
|
-
validation_mode: typeof r.validation_mode === "string" ? r.validation_mode : undefined,
|
|
1157
|
-
validation_agent: typeof r.validation_agent === "string" ? r.validation_agent : undefined,
|
|
1158
|
-
validation_fixture_id:
|
|
1159
|
-
typeof r.validation_fixture_id === "string" ? r.validation_fixture_id : undefined,
|
|
1160
|
-
validation_evidence_ref:
|
|
1161
|
-
typeof r.validation_evidence_ref === "string" ? r.validation_evidence_ref : undefined,
|
|
1162
|
-
}));
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
/**
|
|
1166
|
-
* Read all evolution evidence entries from SQLite.
|
|
1167
|
-
* Replaces: readEvidenceTrail() / readJsonl<EvolutionEvidenceEntry>(EVOLUTION_EVIDENCE_LOG)
|
|
1168
|
-
*/
|
|
1169
|
-
export function queryEvolutionEvidence(
|
|
1170
|
-
db: Database,
|
|
1171
|
-
skillName?: string,
|
|
1172
|
-
): Array<{
|
|
1173
|
-
timestamp: string;
|
|
1174
|
-
proposal_id: string;
|
|
1175
|
-
skill_name: string;
|
|
1176
|
-
skill_path: string;
|
|
1177
|
-
target: string;
|
|
1178
|
-
stage: string;
|
|
1179
|
-
rationale?: string;
|
|
1180
|
-
confidence?: number;
|
|
1181
|
-
details?: string;
|
|
1182
|
-
original_text?: string;
|
|
1183
|
-
proposed_text?: string;
|
|
1184
|
-
eval_set?: Record<string, unknown>[];
|
|
1185
|
-
validation?: Record<string, unknown>;
|
|
1186
|
-
}> {
|
|
1187
|
-
const sql = skillName
|
|
1188
|
-
? `SELECT * FROM evolution_evidence WHERE skill_name = ? ORDER BY timestamp DESC`
|
|
1189
|
-
: `SELECT * FROM evolution_evidence ORDER BY timestamp DESC`;
|
|
1190
|
-
const rows = (skillName ? db.query(sql).all(skillName) : db.query(sql).all()) as Array<
|
|
1191
|
-
Record<string, unknown>
|
|
1192
|
-
>;
|
|
1193
|
-
return rows.map((r) => ({
|
|
1194
|
-
timestamp: r.timestamp as string,
|
|
1195
|
-
proposal_id: r.proposal_id as string,
|
|
1196
|
-
skill_name: r.skill_name as string,
|
|
1197
|
-
skill_path: r.skill_path as string,
|
|
1198
|
-
target: r.target as string,
|
|
1199
|
-
stage: r.stage as string,
|
|
1200
|
-
rationale: r.rationale as string | undefined,
|
|
1201
|
-
confidence: r.confidence as number | undefined,
|
|
1202
|
-
details: r.details as string | undefined,
|
|
1203
|
-
original_text: r.original_text as string | undefined,
|
|
1204
|
-
proposed_text: r.proposed_text as string | undefined,
|
|
1205
|
-
eval_set: r.eval_set_json
|
|
1206
|
-
? safeParseJsonArray<Record<string, unknown>>(r.eval_set_json as string)
|
|
1207
|
-
: undefined,
|
|
1208
|
-
validation: r.validation_json
|
|
1209
|
-
? (safeParseJson(r.validation_json as string) as Record<string, unknown>)
|
|
1210
|
-
: undefined,
|
|
1211
|
-
}));
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
/**
|
|
1215
|
-
* Read improvement signals from SQLite.
|
|
1216
|
-
* Replaces: readJsonl<ImprovementSignalRecord>(SIGNAL_LOG)
|
|
1217
|
-
*/
|
|
1218
|
-
export function queryImprovementSignals(
|
|
1219
|
-
db: Database,
|
|
1220
|
-
consumedOnly?: boolean,
|
|
1221
|
-
): Array<{
|
|
1222
|
-
timestamp: string;
|
|
1223
|
-
session_id: string;
|
|
1224
|
-
query: string;
|
|
1225
|
-
signal_type: string;
|
|
1226
|
-
mentioned_skill?: string;
|
|
1227
|
-
consumed: boolean;
|
|
1228
|
-
consumed_at?: string;
|
|
1229
|
-
consumed_by_run?: string;
|
|
1230
|
-
}> {
|
|
1231
|
-
const where =
|
|
1232
|
-
consumedOnly === undefined ? "" : consumedOnly ? " WHERE consumed = 1" : " WHERE consumed = 0";
|
|
1233
|
-
const rows = db
|
|
1234
|
-
.query(`SELECT * FROM improvement_signals${where} ORDER BY timestamp DESC`)
|
|
1235
|
-
.all() as Array<Record<string, unknown>>;
|
|
1236
|
-
return rows.map((r) => ({
|
|
1237
|
-
timestamp: r.timestamp as string,
|
|
1238
|
-
session_id: r.session_id as string,
|
|
1239
|
-
query: r.query as string,
|
|
1240
|
-
signal_type: r.signal_type as string,
|
|
1241
|
-
mentioned_skill: r.mentioned_skill as string | undefined,
|
|
1242
|
-
consumed: (r.consumed as number) === 1,
|
|
1243
|
-
consumed_at: r.consumed_at as string | undefined,
|
|
1244
|
-
consumed_by_run: r.consumed_by_run as string | undefined,
|
|
1245
|
-
}));
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
// -- Grading results query ----------------------------------------------------
|
|
1249
|
-
|
|
1250
|
-
/**
|
|
1251
|
-
* Read grading results from SQLite for upload staging.
|
|
1252
|
-
*/
|
|
1253
|
-
export function queryGradingResults(db: Database): Array<{
|
|
1254
|
-
grading_id: string;
|
|
1255
|
-
session_id: string;
|
|
1256
|
-
skill_name: string;
|
|
1257
|
-
transcript_path: string | null;
|
|
1258
|
-
graded_at: string;
|
|
1259
|
-
pass_rate: number | null;
|
|
1260
|
-
mean_score: number | null;
|
|
1261
|
-
score_std_dev: number | null;
|
|
1262
|
-
passed_count: number | null;
|
|
1263
|
-
failed_count: number | null;
|
|
1264
|
-
total_count: number | null;
|
|
1265
|
-
expectations_json: string | null;
|
|
1266
|
-
claims_json: string | null;
|
|
1267
|
-
eval_feedback_json: string | null;
|
|
1268
|
-
failure_feedback_json: string | null;
|
|
1269
|
-
execution_metrics_json: string | null;
|
|
1270
|
-
}> {
|
|
1271
|
-
return db
|
|
1272
|
-
.query(
|
|
1273
|
-
`SELECT grading_id, session_id, skill_name, transcript_path, graded_at,
|
|
1274
|
-
pass_rate, mean_score, score_std_dev, passed_count, failed_count, total_count,
|
|
1275
|
-
expectations_json, claims_json, eval_feedback_json, failure_feedback_json,
|
|
1276
|
-
execution_metrics_json
|
|
1277
|
-
FROM grading_results
|
|
1278
|
-
ORDER BY graded_at DESC`,
|
|
1279
|
-
)
|
|
1280
|
-
.all() as Array<{
|
|
1281
|
-
grading_id: string;
|
|
1282
|
-
session_id: string;
|
|
1283
|
-
skill_name: string;
|
|
1284
|
-
transcript_path: string | null;
|
|
1285
|
-
graded_at: string;
|
|
1286
|
-
pass_rate: number | null;
|
|
1287
|
-
mean_score: number | null;
|
|
1288
|
-
score_std_dev: number | null;
|
|
1289
|
-
passed_count: number | null;
|
|
1290
|
-
failed_count: number | null;
|
|
1291
|
-
total_count: number | null;
|
|
1292
|
-
expectations_json: string | null;
|
|
1293
|
-
claims_json: string | null;
|
|
1294
|
-
eval_feedback_json: string | null;
|
|
1295
|
-
failure_feedback_json: string | null;
|
|
1296
|
-
execution_metrics_json: string | null;
|
|
1297
|
-
}>;
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
export function getCreatorContributionStagingCounts(db: Database): Array<{
|
|
1301
|
-
skill_name: string;
|
|
1302
|
-
pending_count: number;
|
|
1303
|
-
}> {
|
|
1304
|
-
return db
|
|
1305
|
-
.query(
|
|
1306
|
-
`SELECT skill_name, COUNT(*) AS pending_count
|
|
1307
|
-
FROM creator_contribution_staging
|
|
1308
|
-
WHERE status = 'pending'
|
|
1309
|
-
GROUP BY skill_name
|
|
1310
|
-
ORDER BY skill_name`,
|
|
1311
|
-
)
|
|
1312
|
-
.all() as Array<{
|
|
1313
|
-
skill_name: string;
|
|
1314
|
-
pending_count: number;
|
|
1315
|
-
}>;
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
export interface CreatorContributionRelayStats {
|
|
1319
|
-
pending: number;
|
|
1320
|
-
sending: number;
|
|
1321
|
-
sent: number;
|
|
1322
|
-
failed: number;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
export interface CreatorContributionStagingRow {
|
|
1326
|
-
id: number;
|
|
1327
|
-
dedupe_key: string;
|
|
1328
|
-
skill_name: string;
|
|
1329
|
-
creator_id: string;
|
|
1330
|
-
payload_json: string;
|
|
1331
|
-
status: string;
|
|
1332
|
-
staged_at: string;
|
|
1333
|
-
updated_at: string;
|
|
1334
|
-
last_error: string | null;
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
export function getCreatorContributionRelayStats(db: Database): CreatorContributionRelayStats {
|
|
1338
|
-
const row = db
|
|
1339
|
-
.query(
|
|
1340
|
-
`SELECT
|
|
1341
|
-
COALESCE(SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END), 0) AS pending,
|
|
1342
|
-
COALESCE(SUM(CASE WHEN status = 'sending' THEN 1 ELSE 0 END), 0) AS sending,
|
|
1343
|
-
COALESCE(SUM(CASE WHEN status = 'sent' THEN 1 ELSE 0 END), 0) AS sent,
|
|
1344
|
-
COALESCE(SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END), 0) AS failed
|
|
1345
|
-
FROM creator_contribution_staging`,
|
|
1346
|
-
)
|
|
1347
|
-
.get() as CreatorContributionRelayStats | null;
|
|
1348
|
-
return row ?? { pending: 0, sending: 0, sent: 0, failed: 0 };
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
export function getPendingCreatorContributionRows(
|
|
1352
|
-
db: Database,
|
|
1353
|
-
limit = 50,
|
|
1354
|
-
): CreatorContributionStagingRow[] {
|
|
1355
|
-
return db
|
|
1356
|
-
.query(
|
|
1357
|
-
`SELECT id, dedupe_key, skill_name, creator_id, payload_json, status, staged_at, updated_at, last_error
|
|
1358
|
-
FROM creator_contribution_staging
|
|
1359
|
-
WHERE status = 'pending'
|
|
1360
|
-
ORDER BY id ASC
|
|
1361
|
-
LIMIT ?`,
|
|
1362
|
-
)
|
|
1363
|
-
.all(limit) as CreatorContributionStagingRow[];
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
// -- Canonical record staging query -------------------------------------------
|
|
1367
|
-
|
|
1368
|
-
/**
|
|
1369
|
-
* Query canonical records from SQLite tables for upload staging.
|
|
1370
|
-
*
|
|
1371
|
-
* Reads from sessions, prompts, skill_invocations, and execution_facts tables,
|
|
1372
|
-
* shaping each row into a CanonicalRecord-compatible object with record_kind.
|
|
1373
|
-
*
|
|
1374
|
-
* Returns all records; dedup is handled by INSERT OR IGNORE in the staging table.
|
|
1375
|
-
*/
|
|
1376
|
-
export function queryCanonicalRecordsForStaging(db: Database): Record<string, unknown>[] {
|
|
1377
|
-
const records: Record<string, unknown>[] = [];
|
|
1378
|
-
|
|
1379
|
-
// Sessions
|
|
1380
|
-
const sessions = db
|
|
1381
|
-
.query(
|
|
1382
|
-
`SELECT session_id, started_at, ended_at, platform, model, completion_status,
|
|
1383
|
-
source_session_kind, agent_cli, workspace_path, repo_remote, branch,
|
|
1384
|
-
schema_version, normalized_at, normalizer_version, capture_mode, raw_source_ref
|
|
1385
|
-
FROM sessions ORDER BY normalized_at`,
|
|
1386
|
-
)
|
|
1387
|
-
.all() as Array<Record<string, unknown>>;
|
|
1388
|
-
const sessionById = new Map(sessions.map((s) => [s.session_id as string, s]));
|
|
1389
|
-
for (const s of sessions) {
|
|
1390
|
-
records.push({
|
|
1391
|
-
record_kind: "session",
|
|
1392
|
-
schema_version: s.schema_version ?? undefined,
|
|
1393
|
-
normalizer_version: s.normalizer_version ?? undefined,
|
|
1394
|
-
normalized_at: s.normalized_at ?? undefined,
|
|
1395
|
-
platform: s.platform ?? undefined,
|
|
1396
|
-
capture_mode: s.capture_mode ?? undefined,
|
|
1397
|
-
raw_source_ref: safeParseJson(s.raw_source_ref as string | null) ?? undefined,
|
|
1398
|
-
source_session_kind: s.source_session_kind ?? undefined,
|
|
1399
|
-
session_id: s.session_id,
|
|
1400
|
-
started_at: s.started_at ?? undefined,
|
|
1401
|
-
ended_at: s.ended_at ?? undefined,
|
|
1402
|
-
model: s.model ?? undefined,
|
|
1403
|
-
completion_status: s.completion_status ?? undefined,
|
|
1404
|
-
agent_cli: s.agent_cli ?? undefined,
|
|
1405
|
-
workspace_path: s.workspace_path ?? undefined,
|
|
1406
|
-
repo_remote: s.repo_remote ?? undefined,
|
|
1407
|
-
branch: s.branch ?? undefined,
|
|
1408
|
-
});
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
// Prompts
|
|
1412
|
-
const prompts = db
|
|
1413
|
-
.query(
|
|
1414
|
-
`SELECT prompt_id, session_id, occurred_at, prompt_kind, is_actionable, prompt_index, prompt_text,
|
|
1415
|
-
schema_version, platform, normalized_at, normalizer_version, capture_mode, raw_source_ref
|
|
1416
|
-
FROM prompts ORDER BY occurred_at`,
|
|
1417
|
-
)
|
|
1418
|
-
.all() as Array<Record<string, unknown>>;
|
|
1419
|
-
for (const p of prompts) {
|
|
1420
|
-
// Fall back to session-level envelope fields if prompt doesn't have its own
|
|
1421
|
-
const sessionEnvelope = sessionById.get(p.session_id as string);
|
|
1422
|
-
records.push({
|
|
1423
|
-
record_kind: "prompt",
|
|
1424
|
-
schema_version: p.schema_version ?? sessionEnvelope?.schema_version ?? undefined,
|
|
1425
|
-
normalizer_version: p.normalizer_version ?? sessionEnvelope?.normalizer_version ?? undefined,
|
|
1426
|
-
normalized_at: p.normalized_at ?? sessionEnvelope?.normalized_at ?? undefined,
|
|
1427
|
-
platform: p.platform ?? sessionEnvelope?.platform ?? undefined,
|
|
1428
|
-
capture_mode: p.capture_mode ?? sessionEnvelope?.capture_mode ?? undefined,
|
|
1429
|
-
raw_source_ref:
|
|
1430
|
-
safeParseJson(p.raw_source_ref as string | null) ??
|
|
1431
|
-
safeParseJson(sessionEnvelope?.raw_source_ref as string | null) ??
|
|
1432
|
-
undefined,
|
|
1433
|
-
source_session_kind: sessionEnvelope?.source_session_kind ?? undefined,
|
|
1434
|
-
session_id: p.session_id,
|
|
1435
|
-
prompt_id: p.prompt_id,
|
|
1436
|
-
occurred_at: p.occurred_at,
|
|
1437
|
-
prompt_text: p.prompt_text,
|
|
1438
|
-
prompt_kind: p.prompt_kind,
|
|
1439
|
-
is_actionable: (p.is_actionable as number) === 1,
|
|
1440
|
-
prompt_index: p.prompt_index ?? undefined,
|
|
1441
|
-
});
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
// Skill invocations
|
|
1445
|
-
const invocations = db
|
|
1446
|
-
.query(
|
|
1447
|
-
`SELECT skill_invocation_id, session_id, occurred_at, skill_name, skill_path, invocation_mode,
|
|
1448
|
-
triggered, confidence, tool_name, matched_prompt_id, agent_type,
|
|
1449
|
-
schema_version, platform, normalized_at, normalizer_version, capture_mode, raw_source_ref
|
|
1450
|
-
FROM skill_invocations ORDER BY occurred_at`,
|
|
1451
|
-
)
|
|
1452
|
-
.all() as Array<Record<string, unknown>>;
|
|
1453
|
-
for (const si of invocations) {
|
|
1454
|
-
const sessionEnvelope = sessionById.get(si.session_id as string);
|
|
1455
|
-
records.push({
|
|
1456
|
-
record_kind: "skill_invocation",
|
|
1457
|
-
schema_version: si.schema_version ?? sessionEnvelope?.schema_version ?? undefined,
|
|
1458
|
-
normalizer_version: si.normalizer_version ?? sessionEnvelope?.normalizer_version ?? undefined,
|
|
1459
|
-
normalized_at: si.normalized_at ?? sessionEnvelope?.normalized_at ?? undefined,
|
|
1460
|
-
platform: si.platform ?? sessionEnvelope?.platform ?? undefined,
|
|
1461
|
-
capture_mode: si.capture_mode ?? sessionEnvelope?.capture_mode ?? undefined,
|
|
1462
|
-
raw_source_ref:
|
|
1463
|
-
safeParseJson(si.raw_source_ref as string | null) ??
|
|
1464
|
-
safeParseJson(sessionEnvelope?.raw_source_ref as string | null) ??
|
|
1465
|
-
undefined,
|
|
1466
|
-
source_session_kind: sessionEnvelope?.source_session_kind ?? undefined,
|
|
1467
|
-
session_id: si.session_id,
|
|
1468
|
-
skill_invocation_id: si.skill_invocation_id,
|
|
1469
|
-
occurred_at: si.occurred_at,
|
|
1470
|
-
skill_name: si.skill_name,
|
|
1471
|
-
skill_path: si.skill_path ?? undefined,
|
|
1472
|
-
invocation_mode: si.invocation_mode,
|
|
1473
|
-
triggered: (si.triggered as number) === 1,
|
|
1474
|
-
confidence: si.confidence,
|
|
1475
|
-
tool_name: si.tool_name ?? undefined,
|
|
1476
|
-
matched_prompt_id: si.matched_prompt_id ?? undefined,
|
|
1477
|
-
agent_type: si.agent_type ?? undefined,
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
// Execution facts
|
|
1482
|
-
const facts = db
|
|
1483
|
-
.query(
|
|
1484
|
-
`SELECT id AS execution_fact_id, session_id, occurred_at, prompt_id, tool_calls_json, total_tool_calls,
|
|
1485
|
-
assistant_turns, errors_encountered, input_tokens, output_tokens,
|
|
1486
|
-
duration_ms, completion_status,
|
|
1487
|
-
schema_version, platform, normalized_at, normalizer_version, capture_mode, raw_source_ref
|
|
1488
|
-
FROM execution_facts ORDER BY occurred_at`,
|
|
1489
|
-
)
|
|
1490
|
-
.all() as Array<Record<string, unknown>>;
|
|
1491
|
-
for (const ef of facts) {
|
|
1492
|
-
const sessionEnvelope = sessionById.get(ef.session_id as string);
|
|
1493
|
-
records.push({
|
|
1494
|
-
record_kind: "execution_fact",
|
|
1495
|
-
schema_version: ef.schema_version ?? sessionEnvelope?.schema_version ?? undefined,
|
|
1496
|
-
normalizer_version: ef.normalizer_version ?? sessionEnvelope?.normalizer_version ?? undefined,
|
|
1497
|
-
normalized_at: ef.normalized_at ?? sessionEnvelope?.normalized_at ?? undefined,
|
|
1498
|
-
platform: ef.platform ?? sessionEnvelope?.platform ?? undefined,
|
|
1499
|
-
capture_mode: ef.capture_mode ?? sessionEnvelope?.capture_mode ?? undefined,
|
|
1500
|
-
raw_source_ref:
|
|
1501
|
-
safeParseJson(ef.raw_source_ref as string | null) ??
|
|
1502
|
-
safeParseJson(sessionEnvelope?.raw_source_ref as string | null) ??
|
|
1503
|
-
undefined,
|
|
1504
|
-
source_session_kind: sessionEnvelope?.source_session_kind ?? undefined,
|
|
1505
|
-
session_id: ef.session_id,
|
|
1506
|
-
execution_fact_id: String(ef.execution_fact_id),
|
|
1507
|
-
occurred_at: ef.occurred_at,
|
|
1508
|
-
prompt_id: ef.prompt_id ?? undefined,
|
|
1509
|
-
tool_calls_json: safeParseJson(ef.tool_calls_json as string | null) ?? {},
|
|
1510
|
-
total_tool_calls: ef.total_tool_calls,
|
|
1511
|
-
assistant_turns: ef.assistant_turns,
|
|
1512
|
-
errors_encountered: ef.errors_encountered,
|
|
1513
|
-
input_tokens: ef.input_tokens ?? undefined,
|
|
1514
|
-
output_tokens: ef.output_tokens ?? undefined,
|
|
1515
|
-
duration_ms: ef.duration_ms ?? undefined,
|
|
1516
|
-
completion_status: ef.completion_status ?? undefined,
|
|
1517
|
-
});
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
return records;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
// -- Alpha upload query helpers -----------------------------------------------
|
|
1524
|
-
|
|
1525
|
-
/**
|
|
1526
|
-
* Get the most recent failed queue item's error and timestamp.
|
|
1527
|
-
* Returns null if no failed items exist.
|
|
1528
|
-
*/
|
|
1529
|
-
export function getLastUploadError(
|
|
1530
|
-
db: Database,
|
|
1531
|
-
): { last_error: string | null; updated_at: string } | null {
|
|
1532
|
-
try {
|
|
1533
|
-
const row = db
|
|
1534
|
-
.query(
|
|
1535
|
-
`SELECT last_error, updated_at
|
|
1536
|
-
FROM upload_queue
|
|
1537
|
-
WHERE status = 'failed'
|
|
1538
|
-
ORDER BY updated_at DESC
|
|
1539
|
-
LIMIT 1`,
|
|
1540
|
-
)
|
|
1541
|
-
.get() as { last_error: string | null; updated_at: string } | null;
|
|
1542
|
-
return row ?? null;
|
|
1543
|
-
} catch {
|
|
1544
|
-
return null;
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
/**
|
|
1549
|
-
* Get the most recent sent queue item's timestamp.
|
|
1550
|
-
* Returns null if no sent items exist.
|
|
1551
|
-
*/
|
|
1552
|
-
export function getLastUploadSuccess(db: Database): { updated_at: string } | null {
|
|
1553
|
-
try {
|
|
1554
|
-
const row = db
|
|
1555
|
-
.query(
|
|
1556
|
-
`SELECT updated_at
|
|
1557
|
-
FROM upload_queue
|
|
1558
|
-
WHERE status = 'sent'
|
|
1559
|
-
ORDER BY updated_at DESC
|
|
1560
|
-
LIMIT 1`,
|
|
1561
|
-
)
|
|
1562
|
-
.get() as { updated_at: string } | null;
|
|
1563
|
-
return row ?? null;
|
|
1564
|
-
} catch {
|
|
1565
|
-
return null;
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
/**
|
|
1570
|
-
* Get the age in seconds of the oldest pending queue item.
|
|
1571
|
-
* Returns null if no pending items exist.
|
|
1572
|
-
*/
|
|
1573
|
-
export function getOldestPendingAge(db: Database): number | null {
|
|
1574
|
-
try {
|
|
1575
|
-
const row = db
|
|
1576
|
-
.query(
|
|
1577
|
-
`SELECT created_at
|
|
1578
|
-
FROM upload_queue
|
|
1579
|
-
WHERE status = 'pending'
|
|
1580
|
-
ORDER BY created_at ASC
|
|
1581
|
-
LIMIT 1`,
|
|
1582
|
-
)
|
|
1583
|
-
.get() as { created_at: string } | null;
|
|
1584
|
-
if (!row) return null;
|
|
1585
|
-
const ageMs = Date.now() - new Date(row.created_at).getTime();
|
|
1586
|
-
return Math.floor(ageMs / 1000);
|
|
1587
|
-
} catch {
|
|
1588
|
-
return null;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
// -- Execution metrics & commit tracking queries ------------------------------
|
|
1593
|
-
|
|
1594
|
-
/**
|
|
1595
|
-
* Aggregate execution_facts enrichment columns for a set of session IDs.
|
|
1596
|
-
*
|
|
1597
|
-
* Returns file change stats, cost totals, token breakdowns, artifact counts,
|
|
1598
|
-
* and session_type distribution across the provided sessions.
|
|
1599
|
-
*/
|
|
1600
|
-
export function getExecutionMetrics(db: Database, sessionIds: string[]): ExecutionMetrics {
|
|
1601
|
-
const empty: ExecutionMetrics = {
|
|
1602
|
-
avg_files_changed: 0,
|
|
1603
|
-
total_lines_added: 0,
|
|
1604
|
-
total_lines_removed: 0,
|
|
1605
|
-
total_cost_usd: 0,
|
|
1606
|
-
avg_cost_usd: 0,
|
|
1607
|
-
cached_input_tokens_total: 0,
|
|
1608
|
-
reasoning_output_tokens_total: 0,
|
|
1609
|
-
artifact_count: 0,
|
|
1610
|
-
session_type_distribution: {},
|
|
1611
|
-
};
|
|
1612
|
-
if (sessionIds.length === 0) return empty;
|
|
1613
|
-
|
|
1614
|
-
const placeholders = sessionIds.map(() => "?").join(",");
|
|
1615
|
-
const row = db
|
|
1616
|
-
.query(
|
|
1617
|
-
`SELECT
|
|
1618
|
-
COALESCE(AVG(files_changed), 0) AS avg_files_changed,
|
|
1619
|
-
COALESCE(SUM(lines_added), 0) AS total_lines_added,
|
|
1620
|
-
COALESCE(SUM(lines_removed), 0) AS total_lines_removed,
|
|
1621
|
-
COALESCE(SUM(cost_usd), 0) AS total_cost_usd,
|
|
1622
|
-
COALESCE(AVG(cost_usd), 0) AS avg_cost_usd,
|
|
1623
|
-
COALESCE(SUM(cached_input_tokens), 0) AS cached_input_tokens_total,
|
|
1624
|
-
COALESCE(SUM(reasoning_output_tokens), 0) AS reasoning_output_tokens_total,
|
|
1625
|
-
COALESCE(SUM(artifact_count), 0) AS artifact_count
|
|
1626
|
-
FROM execution_facts
|
|
1627
|
-
WHERE session_id IN (${placeholders})`,
|
|
1628
|
-
)
|
|
1629
|
-
.get(...sessionIds) as {
|
|
1630
|
-
avg_files_changed: number;
|
|
1631
|
-
total_lines_added: number;
|
|
1632
|
-
total_lines_removed: number;
|
|
1633
|
-
total_cost_usd: number;
|
|
1634
|
-
avg_cost_usd: number;
|
|
1635
|
-
cached_input_tokens_total: number;
|
|
1636
|
-
reasoning_output_tokens_total: number;
|
|
1637
|
-
artifact_count: number;
|
|
1638
|
-
} | null;
|
|
1639
|
-
|
|
1640
|
-
// Session type distribution
|
|
1641
|
-
const typeRows = db
|
|
1642
|
-
.query(
|
|
1643
|
-
`SELECT session_type, COUNT(*) AS count
|
|
1644
|
-
FROM execution_facts
|
|
1645
|
-
WHERE session_id IN (${placeholders}) AND session_type IS NOT NULL
|
|
1646
|
-
GROUP BY session_type`,
|
|
1647
|
-
)
|
|
1648
|
-
.all(...sessionIds) as Array<{ session_type: string; count: number }>;
|
|
1649
|
-
|
|
1650
|
-
const session_type_distribution: Record<string, number> = {};
|
|
1651
|
-
for (const tr of typeRows) {
|
|
1652
|
-
session_type_distribution[tr.session_type] = tr.count;
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
return {
|
|
1656
|
-
avg_files_changed: row?.avg_files_changed ?? 0,
|
|
1657
|
-
total_lines_added: row?.total_lines_added ?? 0,
|
|
1658
|
-
total_lines_removed: row?.total_lines_removed ?? 0,
|
|
1659
|
-
total_cost_usd: row?.total_cost_usd ?? 0,
|
|
1660
|
-
avg_cost_usd: row?.avg_cost_usd ?? 0,
|
|
1661
|
-
cached_input_tokens_total: row?.cached_input_tokens_total ?? 0,
|
|
1662
|
-
reasoning_output_tokens_total: row?.reasoning_output_tokens_total ?? 0,
|
|
1663
|
-
artifact_count: row?.artifact_count ?? 0,
|
|
1664
|
-
session_type_distribution,
|
|
1665
|
-
};
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
/**
|
|
1669
|
-
* Get all commits tracked for a given session.
|
|
1670
|
-
*/
|
|
1671
|
-
export function getSessionCommits(db: Database, sessionId: string): CommitRecord[] {
|
|
1672
|
-
return db
|
|
1673
|
-
.query(
|
|
1674
|
-
`SELECT commit_sha, commit_title, branch, repo_remote, timestamp
|
|
1675
|
-
FROM commit_tracking
|
|
1676
|
-
WHERE session_id = ?
|
|
1677
|
-
ORDER BY timestamp DESC`,
|
|
1678
|
-
)
|
|
1679
|
-
.all(sessionId) as CommitRecord[];
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
/**
|
|
1683
|
-
* Aggregate commit stats for a skill by joining commit_tracking to skill_invocations
|
|
1684
|
-
* via shared session_id.
|
|
1685
|
-
*/
|
|
1686
|
-
export function getSkillCommitSummary(db: Database, skillName: string): CommitSummary {
|
|
1687
|
-
const empty: CommitSummary = {
|
|
1688
|
-
total_commits: 0,
|
|
1689
|
-
unique_branches: 0,
|
|
1690
|
-
recent_commits: [],
|
|
1691
|
-
};
|
|
1692
|
-
|
|
1693
|
-
const statsRow = db
|
|
1694
|
-
.query(
|
|
1695
|
-
`WITH skill_sessions AS (
|
|
1696
|
-
SELECT DISTINCT session_id FROM skill_invocations WHERE skill_name = ?
|
|
1697
|
-
)
|
|
1698
|
-
SELECT
|
|
1699
|
-
COUNT(*) AS total_commits,
|
|
1700
|
-
COUNT(DISTINCT ct.branch) AS unique_branches
|
|
1701
|
-
FROM commit_tracking ct
|
|
1702
|
-
WHERE ct.session_id IN (SELECT session_id FROM skill_sessions)`,
|
|
1703
|
-
)
|
|
1704
|
-
.get(skillName) as { total_commits: number; unique_branches: number } | null;
|
|
1705
|
-
|
|
1706
|
-
if (!statsRow || statsRow.total_commits === 0) return empty;
|
|
1707
|
-
|
|
1708
|
-
const recentRows = db
|
|
1709
|
-
.query(
|
|
1710
|
-
`WITH skill_sessions AS (
|
|
1711
|
-
SELECT DISTINCT session_id FROM skill_invocations WHERE skill_name = ?
|
|
1712
|
-
)
|
|
1713
|
-
SELECT ct.commit_sha, ct.commit_title, ct.branch, ct.timestamp
|
|
1714
|
-
FROM commit_tracking ct
|
|
1715
|
-
WHERE ct.session_id IN (SELECT session_id FROM skill_sessions)
|
|
1716
|
-
ORDER BY ct.timestamp DESC
|
|
1717
|
-
LIMIT 20`,
|
|
1718
|
-
)
|
|
1719
|
-
.all(skillName) as Array<{
|
|
1720
|
-
commit_sha: string;
|
|
1721
|
-
commit_title: string | null;
|
|
1722
|
-
branch: string | null;
|
|
1723
|
-
timestamp: string;
|
|
1724
|
-
}>;
|
|
1725
|
-
|
|
1726
|
-
return {
|
|
1727
|
-
total_commits: statsRow.total_commits,
|
|
1728
|
-
unique_branches: statsRow.unique_branches,
|
|
1729
|
-
recent_commits: recentRows.map((r) => ({
|
|
1730
|
-
sha: r.commit_sha,
|
|
1731
|
-
title: r.commit_title ?? "",
|
|
1732
|
-
branch: r.branch ?? "",
|
|
1733
|
-
timestamp: r.timestamp,
|
|
1734
|
-
})),
|
|
1735
|
-
};
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
// -- Helpers ------------------------------------------------------------------
|
|
1739
|
-
|
|
1740
|
-
// -- Autonomy-first dashboard queries -----------------------------------------
|
|
1741
|
-
|
|
1742
|
-
export interface SkillTrustSummary {
|
|
1743
|
-
skill_name: string;
|
|
1744
|
-
total_checks: number;
|
|
1745
|
-
triggered_count: number;
|
|
1746
|
-
miss_rate: number;
|
|
1747
|
-
system_like_count: number;
|
|
1748
|
-
system_like_rate: number;
|
|
1749
|
-
prompt_link_rate: number;
|
|
1750
|
-
latest_action: string | null;
|
|
1751
|
-
pass_rate: number;
|
|
1752
|
-
last_seen: string | null;
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
export interface TrustedSkillObservationRow {
|
|
1756
|
-
skill_name: string;
|
|
1757
|
-
session_id: string;
|
|
1758
|
-
occurred_at: string | null;
|
|
1759
|
-
triggered: number;
|
|
1760
|
-
matched_prompt_id: string | null;
|
|
1761
|
-
confidence: number | null;
|
|
1762
|
-
invocation_mode: string | null;
|
|
1763
|
-
query_text: string;
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
export function queryTrustedSkillObservationRows(db: Database): TrustedSkillObservationRow[] {
|
|
1767
|
-
const SYSTEM_LIKE_PREFIXES = ["<system_instruction>", "<system-instruction>", "<command-name>"];
|
|
1768
|
-
const INTERNAL_EVAL_MARKERS = [
|
|
1769
|
-
"you are an evaluation assistant",
|
|
1770
|
-
"you are a skill description optimizer",
|
|
1771
|
-
"would each query trigger this skill",
|
|
1772
|
-
"propose an improved description",
|
|
1773
|
-
"failure patterns:",
|
|
1774
|
-
"output only valid json",
|
|
1775
|
-
];
|
|
1776
|
-
const isSystemLike = (text: string | null | undefined): boolean => {
|
|
1777
|
-
if (!text) return false;
|
|
1778
|
-
const trimmed = text.trimStart();
|
|
1779
|
-
return SYSTEM_LIKE_PREFIXES.some((p) => trimmed.startsWith(p));
|
|
1780
|
-
};
|
|
1781
|
-
const isInternalSelftunePrompt = (
|
|
1782
|
-
text: string | null | undefined,
|
|
1783
|
-
promptKind: string | null | undefined,
|
|
1784
|
-
): boolean => {
|
|
1785
|
-
if (!text) return false;
|
|
1786
|
-
const lowered = text.toLowerCase();
|
|
1787
|
-
return (
|
|
1788
|
-
promptKind === "meta" && INTERNAL_EVAL_MARKERS.some((marker) => lowered.includes(marker))
|
|
1789
|
-
);
|
|
1790
|
-
};
|
|
1791
|
-
const isPollutingPrompt = (
|
|
1792
|
-
text: string | null | undefined,
|
|
1793
|
-
promptKind: string | null | undefined,
|
|
1794
|
-
): boolean => isSystemLike(text) || isInternalSelftunePrompt(text, promptKind);
|
|
1795
|
-
const classifyObservationKind = (
|
|
1796
|
-
skillInvocationId: string,
|
|
1797
|
-
captureMode: string | null,
|
|
1798
|
-
triggered: number,
|
|
1799
|
-
rawSourceRefJson: string | null,
|
|
1800
|
-
): "canonical" | "repaired_trigger" | "repaired_contextual_miss" | "legacy_materialized" => {
|
|
1801
|
-
if (skillInvocationId.includes(":su:")) return "legacy_materialized";
|
|
1802
|
-
if (captureMode === "repair") {
|
|
1803
|
-
const rawSourceRef = safeParseJson(rawSourceRefJson) as {
|
|
1804
|
-
metadata?: { miss_type?: string };
|
|
1805
|
-
} | null;
|
|
1806
|
-
if (triggered === 0 && rawSourceRef?.metadata?.miss_type === "contextual_read") {
|
|
1807
|
-
return "repaired_contextual_miss";
|
|
1808
|
-
}
|
|
1809
|
-
return "repaired_trigger";
|
|
1810
|
-
}
|
|
1811
|
-
return "canonical";
|
|
1812
|
-
};
|
|
1813
|
-
const normalizeQueryForGrouping = (query: string) =>
|
|
1814
|
-
query.replace(/\s+/g, " ").trim().toLowerCase();
|
|
1815
|
-
|
|
1816
|
-
const rows = db
|
|
1817
|
-
.query(
|
|
1818
|
-
`SELECT
|
|
1819
|
-
si.skill_name,
|
|
1820
|
-
si.session_id,
|
|
1821
|
-
si.occurred_at,
|
|
1822
|
-
si.triggered,
|
|
1823
|
-
si.matched_prompt_id,
|
|
1824
|
-
si.confidence,
|
|
1825
|
-
si.invocation_mode,
|
|
1826
|
-
si.skill_invocation_id,
|
|
1827
|
-
si.capture_mode,
|
|
1828
|
-
si.raw_source_ref,
|
|
1829
|
-
si.query,
|
|
1830
|
-
p.prompt_text,
|
|
1831
|
-
p.prompt_kind
|
|
1832
|
-
FROM skill_invocations si
|
|
1833
|
-
LEFT JOIN prompts p ON si.matched_prompt_id = p.prompt_id`,
|
|
1834
|
-
)
|
|
1835
|
-
.all() as Array<{
|
|
1836
|
-
skill_name: string;
|
|
1837
|
-
session_id: string;
|
|
1838
|
-
occurred_at: string | null;
|
|
1839
|
-
triggered: number;
|
|
1840
|
-
matched_prompt_id: string | null;
|
|
1841
|
-
confidence: number | null;
|
|
1842
|
-
invocation_mode: string | null;
|
|
1843
|
-
skill_invocation_id: string;
|
|
1844
|
-
capture_mode: string | null;
|
|
1845
|
-
raw_source_ref: string | null;
|
|
1846
|
-
query: string | null;
|
|
1847
|
-
prompt_text: string | null;
|
|
1848
|
-
prompt_kind: string | null;
|
|
1849
|
-
}>;
|
|
1850
|
-
|
|
1851
|
-
const bySkill = new Map<
|
|
1852
|
-
string,
|
|
1853
|
-
Array<{
|
|
1854
|
-
skill_name: string;
|
|
1855
|
-
session_id: string;
|
|
1856
|
-
occurred_at: string | null;
|
|
1857
|
-
triggered: number;
|
|
1858
|
-
matched_prompt_id: string | null;
|
|
1859
|
-
confidence: number | null;
|
|
1860
|
-
invocation_mode: string | null;
|
|
1861
|
-
queryText: string;
|
|
1862
|
-
isPolluting: boolean;
|
|
1863
|
-
observation_kind:
|
|
1864
|
-
| "canonical"
|
|
1865
|
-
| "repaired_trigger"
|
|
1866
|
-
| "repaired_contextual_miss"
|
|
1867
|
-
| "legacy_materialized";
|
|
1868
|
-
groupKey: string;
|
|
1869
|
-
}>
|
|
1870
|
-
>();
|
|
1871
|
-
const trustedRows: Array<{
|
|
1872
|
-
skill_name: string;
|
|
1873
|
-
session_id: string;
|
|
1874
|
-
occurred_at: string | null;
|
|
1875
|
-
triggered: number;
|
|
1876
|
-
matched_prompt_id: string | null;
|
|
1877
|
-
confidence: number | null;
|
|
1878
|
-
invocation_mode: string | null;
|
|
1879
|
-
query_text: string;
|
|
1880
|
-
}> = [];
|
|
1881
|
-
|
|
1882
|
-
for (const row of rows) {
|
|
1883
|
-
const queryText = row.query || row.prompt_text || "";
|
|
1884
|
-
const pollutionText = row.prompt_text || row.query || "";
|
|
1885
|
-
const observation_kind = classifyObservationKind(
|
|
1886
|
-
row.skill_invocation_id,
|
|
1887
|
-
row.capture_mode,
|
|
1888
|
-
row.triggered,
|
|
1889
|
-
row.raw_source_ref,
|
|
1890
|
-
);
|
|
1891
|
-
if (isPollutingPrompt(pollutionText, row.prompt_kind)) continue;
|
|
1892
|
-
if (observation_kind === "legacy_materialized") continue;
|
|
1893
|
-
|
|
1894
|
-
const normalizedQuery = normalizeQueryForGrouping(queryText);
|
|
1895
|
-
const groupKey =
|
|
1896
|
-
normalizedQuery.length > 0
|
|
1897
|
-
? `${row.session_id}::${normalizedQuery}`
|
|
1898
|
-
: `${row.skill_invocation_id}`;
|
|
1899
|
-
const arr = bySkill.get(row.skill_name);
|
|
1900
|
-
const enriched = {
|
|
1901
|
-
skill_name: row.skill_name,
|
|
1902
|
-
session_id: row.session_id,
|
|
1903
|
-
occurred_at: row.occurred_at,
|
|
1904
|
-
triggered: row.triggered,
|
|
1905
|
-
matched_prompt_id: row.matched_prompt_id,
|
|
1906
|
-
confidence: row.confidence,
|
|
1907
|
-
invocation_mode: row.invocation_mode,
|
|
1908
|
-
queryText,
|
|
1909
|
-
isPolluting: false,
|
|
1910
|
-
observation_kind,
|
|
1911
|
-
groupKey,
|
|
1912
|
-
};
|
|
1913
|
-
if (arr) arr.push(enriched);
|
|
1914
|
-
else bySkill.set(row.skill_name, [enriched]);
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
for (const [, skillRows] of bySkill.entries()) {
|
|
1918
|
-
const grouped = new Map<string, typeof skillRows>();
|
|
1919
|
-
for (const row of skillRows) {
|
|
1920
|
-
const arr = grouped.get(row.groupKey);
|
|
1921
|
-
if (arr) arr.push(row);
|
|
1922
|
-
else grouped.set(row.groupKey, [row]);
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
const deduped = [...grouped.values()].map((group) => {
|
|
1926
|
-
const sorted = [...group].sort((a, b) => {
|
|
1927
|
-
const aScore =
|
|
1928
|
-
(a.triggered === 1 ? 100 : 0) +
|
|
1929
|
-
(a.observation_kind === "canonical" ? 20 : 0) +
|
|
1930
|
-
(a.observation_kind === "repaired_trigger" ? 15 : 0);
|
|
1931
|
-
const bScore =
|
|
1932
|
-
(b.triggered === 1 ? 100 : 0) +
|
|
1933
|
-
(b.observation_kind === "canonical" ? 20 : 0) +
|
|
1934
|
-
(b.observation_kind === "repaired_trigger" ? 15 : 0);
|
|
1935
|
-
if (aScore !== bScore) return bScore - aScore;
|
|
1936
|
-
return (b.occurred_at ?? "").localeCompare(a.occurred_at ?? "");
|
|
1937
|
-
});
|
|
1938
|
-
return sorted[0]!;
|
|
1939
|
-
});
|
|
1940
|
-
|
|
1941
|
-
trustedRows.push(
|
|
1942
|
-
...deduped.map((row) => ({
|
|
1943
|
-
skill_name: row.skill_name,
|
|
1944
|
-
session_id: row.session_id,
|
|
1945
|
-
occurred_at: row.occurred_at,
|
|
1946
|
-
triggered: row.triggered,
|
|
1947
|
-
matched_prompt_id: row.matched_prompt_id,
|
|
1948
|
-
confidence: row.confidence,
|
|
1949
|
-
invocation_mode: row.invocation_mode,
|
|
1950
|
-
query_text: row.queryText,
|
|
1951
|
-
})),
|
|
1952
|
-
);
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
return trustedRows;
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
export function getSkillTrustSummaries(db: Database): SkillTrustSummary[] {
|
|
1959
|
-
const rows = queryTrustedSkillObservationRows(db);
|
|
1960
|
-
|
|
1961
|
-
// Build latest_action map from evolution_audit
|
|
1962
|
-
const auditRows = db
|
|
1963
|
-
.query(
|
|
1964
|
-
`SELECT skill_name, action, timestamp
|
|
1965
|
-
FROM evolution_audit
|
|
1966
|
-
WHERE skill_name IS NOT NULL
|
|
1967
|
-
ORDER BY timestamp DESC`,
|
|
1968
|
-
)
|
|
1969
|
-
.all() as Array<{
|
|
1970
|
-
skill_name: string | null;
|
|
1971
|
-
action: string;
|
|
1972
|
-
timestamp: string;
|
|
1973
|
-
}>;
|
|
1974
|
-
|
|
1975
|
-
const latestActions = new Map<string, string>();
|
|
1976
|
-
for (const row of auditRows) {
|
|
1977
|
-
if (row.skill_name && !latestActions.has(row.skill_name)) {
|
|
1978
|
-
latestActions.set(row.skill_name, row.action);
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
const rowsBySkill = new Map<string, typeof rows>();
|
|
1983
|
-
for (const row of rows) {
|
|
1984
|
-
const arr = rowsBySkill.get(row.skill_name);
|
|
1985
|
-
if (arr) arr.push(row);
|
|
1986
|
-
else rowsBySkill.set(row.skill_name, [row]);
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
const summaries: SkillTrustSummary[] = [];
|
|
1990
|
-
for (const [skillName, skillRows] of rowsBySkill.entries()) {
|
|
1991
|
-
const total = skillRows.length;
|
|
1992
|
-
const triggered = skillRows.filter((row) => row.triggered === 1).length;
|
|
1993
|
-
const promptLinked = skillRows.filter((row) => row.matched_prompt_id != null).length;
|
|
1994
|
-
const lastSeen =
|
|
1995
|
-
skillRows
|
|
1996
|
-
.map((row) => row.occurred_at)
|
|
1997
|
-
.filter((value): value is string => value != null)
|
|
1998
|
-
.sort((a, b) => b.localeCompare(a))[0] ?? null;
|
|
1999
|
-
|
|
2000
|
-
summaries.push({
|
|
2001
|
-
skill_name: skillName,
|
|
2002
|
-
total_checks: total,
|
|
2003
|
-
triggered_count: triggered,
|
|
2004
|
-
miss_rate: total > 0 ? (total - triggered) / total : 0,
|
|
2005
|
-
system_like_count: 0,
|
|
2006
|
-
system_like_rate: 0,
|
|
2007
|
-
prompt_link_rate: total > 0 ? promptLinked / total : 0,
|
|
2008
|
-
latest_action: latestActions.get(skillName) ?? null,
|
|
2009
|
-
pass_rate: total > 0 ? triggered / total : 0,
|
|
2010
|
-
last_seen: lastSeen,
|
|
2011
|
-
});
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
return summaries;
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
export function getAttentionQueue(db: Database): AttentionItem[] {
|
|
2018
|
-
const summaries = getSkillTrustSummaries(db);
|
|
2019
|
-
const pending = getPendingProposals(db);
|
|
2020
|
-
const pendingSkills = new Set(pending.map((p) => p.skill_name).filter(Boolean));
|
|
2021
|
-
|
|
2022
|
-
const items: AttentionItem[] = [];
|
|
2023
|
-
|
|
2024
|
-
for (const s of summaries) {
|
|
2025
|
-
if (s.latest_action === "rolled_back") {
|
|
2026
|
-
items.push({
|
|
2027
|
-
skill_name: s.skill_name,
|
|
2028
|
-
category: "needs_review",
|
|
2029
|
-
severity: "critical",
|
|
2030
|
-
reason: "Rolled back after deployment",
|
|
2031
|
-
recommended_action: "Review rollback evidence and decide whether to re-evolve",
|
|
2032
|
-
timestamp: s.last_seen ?? "",
|
|
2033
|
-
});
|
|
2034
|
-
continue;
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
if (pendingSkills.has(s.skill_name)) {
|
|
2038
|
-
items.push({
|
|
2039
|
-
skill_name: s.skill_name,
|
|
2040
|
-
category: "needs_review",
|
|
2041
|
-
severity: "info",
|
|
2042
|
-
reason: "Proposal awaiting review",
|
|
2043
|
-
recommended_action: "Review and approve or reject the pending proposal",
|
|
2044
|
-
timestamp: s.last_seen ?? "",
|
|
2045
|
-
});
|
|
2046
|
-
continue;
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
if (s.total_checks < 5) continue;
|
|
2050
|
-
|
|
2051
|
-
if (s.miss_rate > 0.1) {
|
|
2052
|
-
items.push({
|
|
2053
|
-
skill_name: s.skill_name,
|
|
2054
|
-
category: "regression",
|
|
2055
|
-
severity: "warning",
|
|
2056
|
-
reason: `High miss rate (${Math.round(s.miss_rate * 100)}%)`,
|
|
2057
|
-
recommended_action: "Review missed invocations and consider evolving the skill description",
|
|
2058
|
-
timestamp: s.last_seen ?? "",
|
|
2059
|
-
});
|
|
2060
|
-
continue;
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
if (s.system_like_rate > 0.1) {
|
|
2064
|
-
items.push({
|
|
2065
|
-
skill_name: s.skill_name,
|
|
2066
|
-
category: "polluted",
|
|
2067
|
-
severity: "warning",
|
|
2068
|
-
reason: `Possible telemetry pollution (${Math.round(s.system_like_rate * 100)}% system-like)`,
|
|
2069
|
-
recommended_action: "Inspect prompts for system-injected noise",
|
|
2070
|
-
timestamp: s.last_seen ?? "",
|
|
2071
|
-
});
|
|
2072
|
-
continue;
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
return items;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
export function getRecentDecisions(db: Database, limit = 20): AutonomousDecision[] {
|
|
2080
|
-
const rows = db
|
|
2081
|
-
.query(
|
|
2082
|
-
`SELECT timestamp, proposal_id, skill_name, action, details, eval_snapshot_json
|
|
2083
|
-
FROM evolution_audit
|
|
2084
|
-
WHERE timestamp >= datetime('now', '-7 days')
|
|
2085
|
-
ORDER BY timestamp DESC
|
|
2086
|
-
LIMIT ?`,
|
|
2087
|
-
)
|
|
2088
|
-
.all(limit) as Array<{
|
|
2089
|
-
timestamp: string;
|
|
2090
|
-
proposal_id: string;
|
|
2091
|
-
skill_name: string | null;
|
|
2092
|
-
action: string;
|
|
2093
|
-
details: string;
|
|
2094
|
-
eval_snapshot_json: string | null;
|
|
2095
|
-
}>;
|
|
2096
|
-
|
|
2097
|
-
return rows
|
|
2098
|
-
.filter((row) => row.skill_name != null)
|
|
2099
|
-
.flatMap((row) => {
|
|
2100
|
-
const evalSnapshot = safeParseJson(row.eval_snapshot_json) as {
|
|
2101
|
-
regressions?: unknown[];
|
|
2102
|
-
} | null;
|
|
2103
|
-
|
|
2104
|
-
let kind: DecisionKind | null;
|
|
2105
|
-
switch (row.action) {
|
|
2106
|
-
case "proposed":
|
|
2107
|
-
case "created":
|
|
2108
|
-
kind = "proposal_created";
|
|
2109
|
-
break;
|
|
2110
|
-
case "rejected":
|
|
2111
|
-
kind = "proposal_rejected";
|
|
2112
|
-
break;
|
|
2113
|
-
case "validated":
|
|
2114
|
-
kind =
|
|
2115
|
-
evalSnapshot?.regressions && evalSnapshot.regressions.length > 0
|
|
2116
|
-
? "validation_failed"
|
|
2117
|
-
: "proposal_created"; // validated without regressions is still a creation step
|
|
2118
|
-
break;
|
|
2119
|
-
case "deployed":
|
|
2120
|
-
kind = "proposal_deployed";
|
|
2121
|
-
break;
|
|
2122
|
-
case "rolled_back":
|
|
2123
|
-
kind = "rollback_triggered";
|
|
2124
|
-
break;
|
|
2125
|
-
default:
|
|
2126
|
-
kind = null;
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
if (!kind) return [];
|
|
2130
|
-
|
|
2131
|
-
return [
|
|
2132
|
-
{
|
|
2133
|
-
timestamp: row.timestamp,
|
|
2134
|
-
kind,
|
|
2135
|
-
skill_name: row.skill_name!,
|
|
2136
|
-
proposal_id: row.proposal_id,
|
|
2137
|
-
summary: row.details ?? "",
|
|
2138
|
-
},
|
|
2139
|
-
];
|
|
2140
|
-
});
|
|
2141
|
-
}
|
|
2142
|
-
|
|
2143
|
-
// -- Helpers ------------------------------------------------------------------
|
|
2144
|
-
|
|
2145
|
-
export function safeParseJsonArray<T = string>(json: string | null): T[] {
|
|
2146
|
-
if (!json) return [];
|
|
2147
|
-
try {
|
|
2148
|
-
const parsed = JSON.parse(json);
|
|
2149
|
-
return Array.isArray(parsed) ? (parsed as T[]) : [];
|
|
2150
|
-
} catch {
|
|
2151
|
-
return [];
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
export function safeParseJson(json: string | null): Record<string, unknown> | null {
|
|
2156
|
-
if (!json) return null;
|
|
2157
|
-
try {
|
|
2158
|
-
return JSON.parse(json);
|
|
2159
|
-
} catch {
|
|
2160
|
-
return null;
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
1
|
+
export { getCronRunsByJob, getRecentCronRuns, type CronRun } from "./queries/cron.js";
|
|
2
|
+
export {
|
|
3
|
+
getAnalyticsPayload,
|
|
4
|
+
getOverviewPayload,
|
|
5
|
+
getOverviewPayloadPaginated,
|
|
6
|
+
getSkillReportPayload,
|
|
7
|
+
getSkillReportPayloadPaginated,
|
|
8
|
+
getSkillsList,
|
|
9
|
+
type OverviewPaginationOptions,
|
|
10
|
+
type SkillReportPaginationOptions,
|
|
11
|
+
} from "./queries/dashboard.js";
|
|
12
|
+
export {
|
|
13
|
+
getOrchestrateRuns,
|
|
14
|
+
getPendingProposals,
|
|
15
|
+
queryEvolutionAudit,
|
|
16
|
+
queryEvolutionEvidence,
|
|
17
|
+
} from "./queries/evolution.js";
|
|
18
|
+
export {
|
|
19
|
+
getExecutionMetrics,
|
|
20
|
+
getSessionCommits,
|
|
21
|
+
getSkillCommitSummary,
|
|
22
|
+
} from "./queries/execution.js";
|
|
23
|
+
export {
|
|
24
|
+
type GradeRegressionResult,
|
|
25
|
+
type GradingBaselineRow,
|
|
26
|
+
type RecentGradingResultRow,
|
|
27
|
+
queryGradeRegression,
|
|
28
|
+
queryGradingBaseline,
|
|
29
|
+
queryGradingResults,
|
|
30
|
+
queryImprovementSignals,
|
|
31
|
+
queryRecentGradingResults,
|
|
32
|
+
queryReplayEntryResults,
|
|
33
|
+
queryReplayRegressions,
|
|
34
|
+
} from "./queries/monitoring.js";
|
|
35
|
+
export {
|
|
36
|
+
type CreatorContributionRelayStats,
|
|
37
|
+
type CreatorContributionStagingRow,
|
|
38
|
+
getCreatorContributionRelayStats,
|
|
39
|
+
getCreatorContributionStagingCounts,
|
|
40
|
+
getLastUploadError,
|
|
41
|
+
getLastUploadSuccess,
|
|
42
|
+
getOldestPendingAge,
|
|
43
|
+
getPendingCreatorContributionRows,
|
|
44
|
+
queryCanonicalRecordsForStaging,
|
|
45
|
+
} from "./queries/staging.js";
|
|
46
|
+
export {
|
|
47
|
+
getAttentionQueue,
|
|
48
|
+
getRecentDecisions,
|
|
49
|
+
getSkillTrustSummaries,
|
|
50
|
+
queryTrustedSkillObservationRows,
|
|
51
|
+
type SkillTrustSummary,
|
|
52
|
+
type TrustedSkillObservationRow,
|
|
53
|
+
} from "./queries/trust.js";
|
|
54
|
+
export { safeParseJson, safeParseJsonArray } from "./queries/json.js";
|
|
55
|
+
export {
|
|
56
|
+
queryQueryLog,
|
|
57
|
+
querySessionTelemetry,
|
|
58
|
+
querySkillRecords,
|
|
59
|
+
querySkillUsageRecords,
|
|
60
|
+
} from "./queries/raw.js";
|