selftune 0.2.23 → 0.2.25
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 +93 -15
- package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
- package/apps/local-dashboard/dist/assets/index-Dhgv5BQO.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/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/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 +73 -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/replay-engine.ts +79 -57
- package/cli/selftune/evolution/evolve-body.ts +100 -39
- package/cli/selftune/evolution/evolve.ts +244 -52
- package/cli/selftune/evolution/rollback.ts +0 -1
- package/cli/selftune/evolution/validate-body.ts +68 -42
- 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 +43 -41
- 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/index.ts +35 -10
- 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 +3 -2
- package/cli/selftune/init.ts +27 -3
- package/cli/selftune/localdb/direct-write.ts +35 -1
- 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 -2288
- package/cli/selftune/localdb/schema.ts +21 -0
- package/cli/selftune/monitoring/watch.ts +96 -29
- package/cli/selftune/normalization.ts +3 -0
- package/cli/selftune/observability.ts +4 -2
- 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 +150 -1173
- package/cli/selftune/repair/skill-usage.ts +5 -2
- 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 +39 -2
- package/cli/selftune/testing-readiness.ts +597 -0
- package/cli/selftune/types.ts +44 -4
- 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/llm-call.ts +126 -6
- package/cli/selftune/utils/skill-discovery.ts +2 -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 +1 -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 +1 -1
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +41 -1
- package/node_modules/@selftune/telemetry-contract/src/types.ts +103 -2
- 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 +1 -1
- package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/packages/telemetry-contract/src/schemas.ts +41 -1
- package/packages/telemetry-contract/src/types.ts +103 -2
- package/packages/ui/src/components/EvidenceViewer.tsx +80 -25
- package/packages/ui/src/components/OverviewPanels.tsx +67 -26
- package/packages/ui/src/primitives/tabs.tsx +7 -6
- package/packages/ui/src/types.ts +10 -0
- package/skill/SKILL.md +130 -332
- 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}/Initialize.md +8 -4
- package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
- package/skill/{Workflows → workflows}/Schedule.md +3 -3
- package/skill/workflows/SignalsDashboard.md +87 -0
- 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-CwOtTrUS.css +0 -1
- package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +0 -59
- package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
- package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +0 -12
- /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}/Ingest.md +0 -0
- /package/skill/{Workflows → workflows}/PlatformHooks.md +0 -0
- /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
- /package/skill/{Workflows → workflows}/Recover.md +0 -0
- /package/skill/{Workflows → workflows}/Registry.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}/Sync.md +0 -0
- /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
- /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AnalyticsResponse,
|
|
5
|
+
OverviewPaginatedPayload,
|
|
6
|
+
OverviewPayload,
|
|
7
|
+
PaginatedResult,
|
|
8
|
+
PaginationCursor,
|
|
9
|
+
RecentActivityItem,
|
|
10
|
+
SkillReportPaginatedPayload,
|
|
11
|
+
SkillReportPayload,
|
|
12
|
+
SkillTestingReadiness,
|
|
13
|
+
SkillSummary,
|
|
14
|
+
SkillUsageRecord,
|
|
15
|
+
TelemetryRecord,
|
|
16
|
+
} from "../../dashboard-contract.js";
|
|
17
|
+
import { queryEvolutionEvidence, getPendingProposals } from "./evolution.js";
|
|
18
|
+
import { safeParseJsonArray } from "./json.js";
|
|
19
|
+
import { queryTrustedSkillObservationRows } from "./trust.js";
|
|
20
|
+
import { listSkillTestingReadiness } from "../../testing-readiness.js";
|
|
21
|
+
import { classifySkillPath } from "../../utils/skill-discovery.js";
|
|
22
|
+
|
|
23
|
+
function mapOverviewEvolutionEntry(row: {
|
|
24
|
+
timestamp: string;
|
|
25
|
+
proposal_id: string;
|
|
26
|
+
skill_name: string | null;
|
|
27
|
+
action: string;
|
|
28
|
+
details: string;
|
|
29
|
+
}): OverviewPayload["evolution"][number] {
|
|
30
|
+
return {
|
|
31
|
+
timestamp: row.timestamp,
|
|
32
|
+
proposal_id: row.proposal_id,
|
|
33
|
+
skill_name: row.skill_name ?? undefined,
|
|
34
|
+
action: row.action,
|
|
35
|
+
details: row.details,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function mapEvidenceEntry(
|
|
40
|
+
row: ReturnType<typeof queryEvolutionEvidence>[number],
|
|
41
|
+
): SkillReportPayload["evidence"][number] {
|
|
42
|
+
return {
|
|
43
|
+
proposal_id: row.proposal_id,
|
|
44
|
+
target: row.target,
|
|
45
|
+
stage: row.stage,
|
|
46
|
+
timestamp: row.timestamp,
|
|
47
|
+
rationale: row.rationale ?? null,
|
|
48
|
+
confidence: row.confidence ?? null,
|
|
49
|
+
original_text: row.original_text ?? null,
|
|
50
|
+
proposed_text: row.proposed_text ?? null,
|
|
51
|
+
validation: row.validation ?? null,
|
|
52
|
+
details: row.details ?? null,
|
|
53
|
+
eval_set: row.eval_set ?? [],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getOverviewPayload(db: Database): OverviewPayload {
|
|
58
|
+
const telemetryRows = db
|
|
59
|
+
.query(
|
|
60
|
+
`SELECT timestamp, session_id, skills_triggered_json, errors_encountered, total_tool_calls
|
|
61
|
+
FROM session_telemetry
|
|
62
|
+
ORDER BY timestamp DESC
|
|
63
|
+
LIMIT 1000`,
|
|
64
|
+
)
|
|
65
|
+
.all() as Array<{
|
|
66
|
+
timestamp: string;
|
|
67
|
+
session_id: string;
|
|
68
|
+
skills_triggered_json: string | null;
|
|
69
|
+
errors_encountered: number;
|
|
70
|
+
total_tool_calls: number;
|
|
71
|
+
}>;
|
|
72
|
+
|
|
73
|
+
const telemetry = telemetryRows.map((row) => ({
|
|
74
|
+
timestamp: row.timestamp,
|
|
75
|
+
session_id: row.session_id,
|
|
76
|
+
skills_triggered: safeParseJsonArray<string>(row.skills_triggered_json),
|
|
77
|
+
errors_encountered: row.errors_encountered,
|
|
78
|
+
total_tool_calls: row.total_tool_calls,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
const skillRows = db
|
|
82
|
+
.query(
|
|
83
|
+
`SELECT occurred_at, session_id, skill_name, skill_path, query, triggered, source
|
|
84
|
+
FROM skill_invocations
|
|
85
|
+
ORDER BY occurred_at DESC
|
|
86
|
+
LIMIT 2000`,
|
|
87
|
+
)
|
|
88
|
+
.all() as Array<{
|
|
89
|
+
occurred_at: string;
|
|
90
|
+
session_id: string;
|
|
91
|
+
skill_name: string;
|
|
92
|
+
skill_path: string;
|
|
93
|
+
query: string;
|
|
94
|
+
triggered: number;
|
|
95
|
+
source: string | null;
|
|
96
|
+
}>;
|
|
97
|
+
|
|
98
|
+
const skills = skillRows.map((row) => ({
|
|
99
|
+
timestamp: row.occurred_at,
|
|
100
|
+
session_id: row.session_id,
|
|
101
|
+
skill_name: row.skill_name,
|
|
102
|
+
skill_path: row.skill_path,
|
|
103
|
+
query: row.query,
|
|
104
|
+
triggered: row.triggered === 1,
|
|
105
|
+
source: row.source,
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
const evolutionRows = db
|
|
109
|
+
.query(
|
|
110
|
+
`SELECT timestamp, proposal_id, skill_name, action, details
|
|
111
|
+
FROM evolution_audit
|
|
112
|
+
ORDER BY timestamp DESC
|
|
113
|
+
LIMIT 500`,
|
|
114
|
+
)
|
|
115
|
+
.all() as Array<{
|
|
116
|
+
timestamp: string;
|
|
117
|
+
proposal_id: string;
|
|
118
|
+
skill_name: string | null;
|
|
119
|
+
action: string;
|
|
120
|
+
details: string;
|
|
121
|
+
}>;
|
|
122
|
+
const evolution = evolutionRows.map(mapOverviewEvolutionEntry);
|
|
123
|
+
|
|
124
|
+
const counts = db
|
|
125
|
+
.query(
|
|
126
|
+
`SELECT
|
|
127
|
+
(SELECT COUNT(*) FROM session_telemetry) as telemetry,
|
|
128
|
+
(SELECT COUNT(*) FROM skill_invocations) as skills,
|
|
129
|
+
(SELECT COUNT(*) FROM evolution_audit) as evolution,
|
|
130
|
+
(SELECT COUNT(*) FROM evolution_evidence) as evidence,
|
|
131
|
+
(SELECT COUNT(*) FROM sessions) as sessions,
|
|
132
|
+
(SELECT COUNT(*) FROM prompts) as prompts`,
|
|
133
|
+
)
|
|
134
|
+
.get() as {
|
|
135
|
+
telemetry: number;
|
|
136
|
+
skills: number;
|
|
137
|
+
evolution: number;
|
|
138
|
+
evidence: number;
|
|
139
|
+
sessions: number;
|
|
140
|
+
prompts: number;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const unmatchedRows = db
|
|
144
|
+
.query(
|
|
145
|
+
`SELECT si.occurred_at AS timestamp, si.session_id, si.query
|
|
146
|
+
FROM skill_invocations si
|
|
147
|
+
WHERE si.triggered = 0
|
|
148
|
+
AND NOT EXISTS (
|
|
149
|
+
SELECT 1 FROM skill_invocations si2
|
|
150
|
+
WHERE si2.query = si.query AND si2.triggered = 1
|
|
151
|
+
)
|
|
152
|
+
ORDER BY si.occurred_at DESC
|
|
153
|
+
LIMIT 500`,
|
|
154
|
+
)
|
|
155
|
+
.all() as Array<{ timestamp: string; session_id: string; query: string }>;
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
telemetry,
|
|
159
|
+
skills,
|
|
160
|
+
evolution,
|
|
161
|
+
counts,
|
|
162
|
+
unmatched_queries: unmatchedRows,
|
|
163
|
+
pending_proposals: getPendingProposals(db),
|
|
164
|
+
active_sessions: getActiveSessionCount(db),
|
|
165
|
+
recent_activity: getRecentActivity(db),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function getSkillReportPayload(db: Database, skillName: string): SkillReportPayload {
|
|
170
|
+
const usageRow = db
|
|
171
|
+
.query(
|
|
172
|
+
`SELECT
|
|
173
|
+
COUNT(*) as total_checks,
|
|
174
|
+
SUM(CASE WHEN triggered = 1 THEN 1 ELSE 0 END) as triggered_count
|
|
175
|
+
FROM skill_invocations
|
|
176
|
+
WHERE skill_name = ?`,
|
|
177
|
+
)
|
|
178
|
+
.get(skillName) as { total_checks: number; triggered_count: number };
|
|
179
|
+
|
|
180
|
+
const total = usageRow.total_checks;
|
|
181
|
+
const triggered = usageRow.triggered_count;
|
|
182
|
+
const passRate = total > 0 ? triggered / total : 0;
|
|
183
|
+
|
|
184
|
+
const invocationRows = db
|
|
185
|
+
.query(
|
|
186
|
+
`SELECT occurred_at, session_id, query, triggered, source
|
|
187
|
+
FROM skill_invocations
|
|
188
|
+
WHERE skill_name = ?
|
|
189
|
+
ORDER BY occurred_at DESC
|
|
190
|
+
LIMIT 100`,
|
|
191
|
+
)
|
|
192
|
+
.all(skillName) as Array<{
|
|
193
|
+
occurred_at: string;
|
|
194
|
+
session_id: string;
|
|
195
|
+
query: string;
|
|
196
|
+
triggered: number;
|
|
197
|
+
source: string | null;
|
|
198
|
+
}>;
|
|
199
|
+
|
|
200
|
+
const recent_invocations = invocationRows.map((row) => ({
|
|
201
|
+
timestamp: row.occurred_at,
|
|
202
|
+
session_id: row.session_id,
|
|
203
|
+
query: row.query,
|
|
204
|
+
triggered: row.triggered === 1,
|
|
205
|
+
source: row.source,
|
|
206
|
+
}));
|
|
207
|
+
|
|
208
|
+
const evidence = queryEvolutionEvidence(db, skillName).map(mapEvidenceEntry);
|
|
209
|
+
|
|
210
|
+
const sessionsRow = db
|
|
211
|
+
.query(`SELECT COUNT(DISTINCT session_id) as c FROM skill_invocations WHERE skill_name = ?`)
|
|
212
|
+
.get(skillName) as { c: number };
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
skill_name: skillName,
|
|
216
|
+
usage: {
|
|
217
|
+
total_checks: total,
|
|
218
|
+
triggered_count: triggered,
|
|
219
|
+
pass_rate: passRate,
|
|
220
|
+
},
|
|
221
|
+
recent_invocations,
|
|
222
|
+
evidence,
|
|
223
|
+
sessions_with_skill: sessionsRow.c,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface OverviewPaginationOptions {
|
|
228
|
+
telemetry_cursor?: PaginationCursor | null;
|
|
229
|
+
telemetry_limit?: number;
|
|
230
|
+
skills_cursor?: PaginationCursor | null;
|
|
231
|
+
skills_limit?: number;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface SkillReportPaginationOptions {
|
|
235
|
+
invocations_cursor?: PaginationCursor | null;
|
|
236
|
+
invocations_limit?: number;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function getOverviewPayloadPaginated(
|
|
240
|
+
db: Database,
|
|
241
|
+
opts: OverviewPaginationOptions = {},
|
|
242
|
+
): OverviewPaginatedPayload {
|
|
243
|
+
const telemetryLimit = opts.telemetry_limit ?? 1000;
|
|
244
|
+
const skillsLimit = opts.skills_limit ?? 2000;
|
|
245
|
+
|
|
246
|
+
const telemetry_page = paginateTelemetry(db, telemetryLimit, opts.telemetry_cursor ?? null);
|
|
247
|
+
const skills_page = paginateSkillInvocations(db, skillsLimit, opts.skills_cursor ?? null);
|
|
248
|
+
|
|
249
|
+
const evolutionRows = db
|
|
250
|
+
.query(
|
|
251
|
+
`SELECT timestamp, proposal_id, skill_name, action, details
|
|
252
|
+
FROM evolution_audit
|
|
253
|
+
ORDER BY timestamp DESC
|
|
254
|
+
LIMIT 500`,
|
|
255
|
+
)
|
|
256
|
+
.all() as Array<{
|
|
257
|
+
timestamp: string;
|
|
258
|
+
proposal_id: string;
|
|
259
|
+
skill_name: string | null;
|
|
260
|
+
action: string;
|
|
261
|
+
details: string;
|
|
262
|
+
}>;
|
|
263
|
+
const evolution = evolutionRows.map(mapOverviewEvolutionEntry);
|
|
264
|
+
|
|
265
|
+
const counts = db
|
|
266
|
+
.query(
|
|
267
|
+
`SELECT
|
|
268
|
+
(SELECT COUNT(*) FROM session_telemetry) as telemetry,
|
|
269
|
+
(SELECT COUNT(*) FROM skill_invocations) as skills,
|
|
270
|
+
(SELECT COUNT(*) FROM evolution_audit) as evolution,
|
|
271
|
+
(SELECT COUNT(*) FROM evolution_evidence) as evidence,
|
|
272
|
+
(SELECT COUNT(*) FROM sessions) as sessions,
|
|
273
|
+
(SELECT COUNT(*) FROM prompts) as prompts`,
|
|
274
|
+
)
|
|
275
|
+
.get() as {
|
|
276
|
+
telemetry: number;
|
|
277
|
+
skills: number;
|
|
278
|
+
evolution: number;
|
|
279
|
+
evidence: number;
|
|
280
|
+
sessions: number;
|
|
281
|
+
prompts: number;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const unmatchedRows = db
|
|
285
|
+
.query(
|
|
286
|
+
`SELECT si.occurred_at AS timestamp, si.session_id, si.query
|
|
287
|
+
FROM skill_invocations si
|
|
288
|
+
WHERE si.triggered = 0
|
|
289
|
+
AND NOT EXISTS (
|
|
290
|
+
SELECT 1 FROM skill_invocations si2
|
|
291
|
+
WHERE si2.query = si.query AND si2.triggered = 1
|
|
292
|
+
)
|
|
293
|
+
ORDER BY si.occurred_at DESC
|
|
294
|
+
LIMIT 500`,
|
|
295
|
+
)
|
|
296
|
+
.all() as Array<{ timestamp: string; session_id: string; query: string }>;
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
telemetry_page,
|
|
300
|
+
skills_page,
|
|
301
|
+
evolution,
|
|
302
|
+
counts,
|
|
303
|
+
unmatched_queries: unmatchedRows,
|
|
304
|
+
pending_proposals: getPendingProposals(db),
|
|
305
|
+
active_sessions: getActiveSessionCount(db),
|
|
306
|
+
recent_activity: getRecentActivity(db),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function getSkillReportPayloadPaginated(
|
|
311
|
+
db: Database,
|
|
312
|
+
skillName: string,
|
|
313
|
+
opts: SkillReportPaginationOptions = {},
|
|
314
|
+
): SkillReportPaginatedPayload {
|
|
315
|
+
const invocationsLimit = opts.invocations_limit ?? 100;
|
|
316
|
+
const usageRow = db
|
|
317
|
+
.query(
|
|
318
|
+
`SELECT
|
|
319
|
+
COUNT(*) as total_checks,
|
|
320
|
+
SUM(CASE WHEN triggered = 1 THEN 1 ELSE 0 END) as triggered_count
|
|
321
|
+
FROM skill_invocations
|
|
322
|
+
WHERE skill_name = ?`,
|
|
323
|
+
)
|
|
324
|
+
.get(skillName) as { total_checks: number; triggered_count: number };
|
|
325
|
+
|
|
326
|
+
const total = usageRow.total_checks;
|
|
327
|
+
const triggered = usageRow.triggered_count;
|
|
328
|
+
const passRate = total > 0 ? triggered / total : 0;
|
|
329
|
+
|
|
330
|
+
const invocations_page = paginateSkillReportInvocations(
|
|
331
|
+
db,
|
|
332
|
+
skillName,
|
|
333
|
+
invocationsLimit,
|
|
334
|
+
opts.invocations_cursor ?? null,
|
|
335
|
+
);
|
|
336
|
+
const evidence = queryEvolutionEvidence(db, skillName).map(mapEvidenceEntry);
|
|
337
|
+
|
|
338
|
+
const sessionsRow = db
|
|
339
|
+
.query(`SELECT COUNT(DISTINCT session_id) as c FROM skill_invocations WHERE skill_name = ?`)
|
|
340
|
+
.get(skillName) as { c: number };
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
skill_name: skillName,
|
|
344
|
+
usage: {
|
|
345
|
+
total_checks: total,
|
|
346
|
+
triggered_count: triggered,
|
|
347
|
+
pass_rate: passRate,
|
|
348
|
+
},
|
|
349
|
+
invocations_page,
|
|
350
|
+
evidence,
|
|
351
|
+
sessions_with_skill: sessionsRow.c,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function paginateTelemetry(
|
|
356
|
+
db: Database,
|
|
357
|
+
limit: number,
|
|
358
|
+
cursor: PaginationCursor | null,
|
|
359
|
+
): PaginatedResult<TelemetryRecord> {
|
|
360
|
+
const fetchLimit = limit + 1;
|
|
361
|
+
|
|
362
|
+
let rows: Array<{
|
|
363
|
+
timestamp: string;
|
|
364
|
+
session_id: string;
|
|
365
|
+
skills_triggered_json: string | null;
|
|
366
|
+
errors_encountered: number;
|
|
367
|
+
total_tool_calls: number;
|
|
368
|
+
}>;
|
|
369
|
+
|
|
370
|
+
if (cursor) {
|
|
371
|
+
rows = db
|
|
372
|
+
.query(
|
|
373
|
+
`SELECT timestamp, session_id, skills_triggered_json, errors_encountered, total_tool_calls
|
|
374
|
+
FROM session_telemetry
|
|
375
|
+
WHERE (timestamp < ? OR (timestamp = ? AND session_id < ?))
|
|
376
|
+
ORDER BY timestamp DESC, session_id DESC
|
|
377
|
+
LIMIT ?`,
|
|
378
|
+
)
|
|
379
|
+
.all(cursor.timestamp, cursor.timestamp, String(cursor.id), fetchLimit) as typeof rows;
|
|
380
|
+
} else {
|
|
381
|
+
rows = db
|
|
382
|
+
.query(
|
|
383
|
+
`SELECT timestamp, session_id, skills_triggered_json, errors_encountered, total_tool_calls
|
|
384
|
+
FROM session_telemetry
|
|
385
|
+
ORDER BY timestamp DESC, session_id DESC
|
|
386
|
+
LIMIT ?`,
|
|
387
|
+
)
|
|
388
|
+
.all(fetchLimit) as typeof rows;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const hasMore = rows.length > limit;
|
|
392
|
+
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
393
|
+
const items: TelemetryRecord[] = pageRows.map((row) => ({
|
|
394
|
+
timestamp: row.timestamp,
|
|
395
|
+
session_id: row.session_id,
|
|
396
|
+
skills_triggered: safeParseJsonArray<string>(row.skills_triggered_json),
|
|
397
|
+
errors_encountered: row.errors_encountered,
|
|
398
|
+
total_tool_calls: row.total_tool_calls,
|
|
399
|
+
}));
|
|
400
|
+
|
|
401
|
+
const lastItem = pageRows[pageRows.length - 1];
|
|
402
|
+
const next_cursor: PaginationCursor | null =
|
|
403
|
+
hasMore && lastItem ? { timestamp: lastItem.timestamp, id: lastItem.session_id } : null;
|
|
404
|
+
|
|
405
|
+
return { items, next_cursor, has_more: hasMore };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function paginateSkillInvocations(
|
|
409
|
+
db: Database,
|
|
410
|
+
limit: number,
|
|
411
|
+
cursor: PaginationCursor | null,
|
|
412
|
+
): PaginatedResult<SkillUsageRecord> {
|
|
413
|
+
const fetchLimit = limit + 1;
|
|
414
|
+
|
|
415
|
+
let rows: Array<{
|
|
416
|
+
occurred_at: string;
|
|
417
|
+
session_id: string;
|
|
418
|
+
skill_name: string;
|
|
419
|
+
skill_path: string;
|
|
420
|
+
query: string;
|
|
421
|
+
triggered: number;
|
|
422
|
+
source: string | null;
|
|
423
|
+
skill_invocation_id: string;
|
|
424
|
+
}>;
|
|
425
|
+
|
|
426
|
+
if (cursor) {
|
|
427
|
+
rows = db
|
|
428
|
+
.query(
|
|
429
|
+
`SELECT occurred_at, session_id, skill_name, skill_path, query, triggered, source, skill_invocation_id
|
|
430
|
+
FROM skill_invocations
|
|
431
|
+
WHERE (occurred_at < ? OR (occurred_at = ? AND skill_invocation_id < ?))
|
|
432
|
+
ORDER BY occurred_at DESC, skill_invocation_id DESC
|
|
433
|
+
LIMIT ?`,
|
|
434
|
+
)
|
|
435
|
+
.all(cursor.timestamp, cursor.timestamp, String(cursor.id), fetchLimit) as typeof rows;
|
|
436
|
+
} else {
|
|
437
|
+
rows = db
|
|
438
|
+
.query(
|
|
439
|
+
`SELECT occurred_at, session_id, skill_name, skill_path, query, triggered, source, skill_invocation_id
|
|
440
|
+
FROM skill_invocations
|
|
441
|
+
ORDER BY occurred_at DESC, skill_invocation_id DESC
|
|
442
|
+
LIMIT ?`,
|
|
443
|
+
)
|
|
444
|
+
.all(fetchLimit) as typeof rows;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const hasMore = rows.length > limit;
|
|
448
|
+
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
449
|
+
const items: SkillUsageRecord[] = pageRows.map((row) => ({
|
|
450
|
+
timestamp: row.occurred_at,
|
|
451
|
+
session_id: row.session_id,
|
|
452
|
+
skill_name: row.skill_name,
|
|
453
|
+
skill_path: row.skill_path,
|
|
454
|
+
query: row.query,
|
|
455
|
+
triggered: row.triggered === 1,
|
|
456
|
+
source: row.source,
|
|
457
|
+
}));
|
|
458
|
+
|
|
459
|
+
const lastRow = pageRows[pageRows.length - 1];
|
|
460
|
+
const next_cursor: PaginationCursor | null =
|
|
461
|
+
hasMore && lastRow ? { timestamp: lastRow.occurred_at, id: lastRow.skill_invocation_id } : null;
|
|
462
|
+
|
|
463
|
+
return { items, next_cursor, has_more: hasMore };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function paginateSkillReportInvocations(
|
|
467
|
+
db: Database,
|
|
468
|
+
skillName: string,
|
|
469
|
+
limit: number,
|
|
470
|
+
cursor: PaginationCursor | null,
|
|
471
|
+
): PaginatedResult<{
|
|
472
|
+
timestamp: string;
|
|
473
|
+
session_id: string;
|
|
474
|
+
query: string;
|
|
475
|
+
triggered: boolean;
|
|
476
|
+
source: string | null;
|
|
477
|
+
}> {
|
|
478
|
+
const fetchLimit = limit + 1;
|
|
479
|
+
|
|
480
|
+
let rows: Array<{
|
|
481
|
+
occurred_at: string;
|
|
482
|
+
session_id: string;
|
|
483
|
+
query: string;
|
|
484
|
+
triggered: number;
|
|
485
|
+
source: string | null;
|
|
486
|
+
skill_invocation_id: string;
|
|
487
|
+
}>;
|
|
488
|
+
|
|
489
|
+
if (cursor) {
|
|
490
|
+
rows = db
|
|
491
|
+
.query(
|
|
492
|
+
`SELECT si.occurred_at, si.session_id, COALESCE(si.query, p.prompt_text) as query,
|
|
493
|
+
si.triggered, si.source, si.skill_invocation_id
|
|
494
|
+
FROM skill_invocations si
|
|
495
|
+
LEFT JOIN prompts p ON si.matched_prompt_id = p.prompt_id
|
|
496
|
+
WHERE si.skill_name = ?
|
|
497
|
+
AND (si.occurred_at < ? OR (si.occurred_at = ? AND si.skill_invocation_id < ?))
|
|
498
|
+
ORDER BY si.occurred_at DESC, si.skill_invocation_id DESC
|
|
499
|
+
LIMIT ?`,
|
|
500
|
+
)
|
|
501
|
+
.all(
|
|
502
|
+
skillName,
|
|
503
|
+
cursor.timestamp,
|
|
504
|
+
cursor.timestamp,
|
|
505
|
+
String(cursor.id),
|
|
506
|
+
fetchLimit,
|
|
507
|
+
) as typeof rows;
|
|
508
|
+
} else {
|
|
509
|
+
rows = db
|
|
510
|
+
.query(
|
|
511
|
+
`SELECT si.occurred_at, si.session_id, COALESCE(si.query, p.prompt_text) as query,
|
|
512
|
+
si.triggered, si.source, si.skill_invocation_id
|
|
513
|
+
FROM skill_invocations si
|
|
514
|
+
LEFT JOIN prompts p ON si.matched_prompt_id = p.prompt_id
|
|
515
|
+
WHERE si.skill_name = ?
|
|
516
|
+
ORDER BY si.occurred_at DESC, si.skill_invocation_id DESC
|
|
517
|
+
LIMIT ?`,
|
|
518
|
+
)
|
|
519
|
+
.all(skillName, fetchLimit) as typeof rows;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const hasMore = rows.length > limit;
|
|
523
|
+
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
524
|
+
const items = pageRows.map((row) => ({
|
|
525
|
+
timestamp: row.occurred_at,
|
|
526
|
+
session_id: row.session_id,
|
|
527
|
+
query: row.query ?? "",
|
|
528
|
+
triggered: row.triggered === 1,
|
|
529
|
+
source: row.source,
|
|
530
|
+
}));
|
|
531
|
+
|
|
532
|
+
const lastRow = pageRows[pageRows.length - 1];
|
|
533
|
+
const next_cursor: PaginationCursor | null =
|
|
534
|
+
hasMore && lastRow ? { timestamp: lastRow.occurred_at, id: lastRow.skill_invocation_id } : null;
|
|
535
|
+
|
|
536
|
+
return { items, next_cursor, has_more: hasMore };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export function getSkillsList(
|
|
540
|
+
db: Database,
|
|
541
|
+
testingReadinessRows?: SkillTestingReadiness[],
|
|
542
|
+
): SkillSummary[] {
|
|
543
|
+
const trustedRows = queryTrustedSkillObservationRows(db);
|
|
544
|
+
const bySkill = new Map<
|
|
545
|
+
string,
|
|
546
|
+
Array<{
|
|
547
|
+
skill_name: string;
|
|
548
|
+
session_id: string;
|
|
549
|
+
occurred_at: string | null;
|
|
550
|
+
triggered: number;
|
|
551
|
+
matched_prompt_id: string | null;
|
|
552
|
+
confidence: number | null;
|
|
553
|
+
}>
|
|
554
|
+
>();
|
|
555
|
+
|
|
556
|
+
for (const row of trustedRows) {
|
|
557
|
+
const base = {
|
|
558
|
+
skill_name: row.skill_name,
|
|
559
|
+
session_id: row.session_id,
|
|
560
|
+
occurred_at: row.occurred_at,
|
|
561
|
+
triggered: row.triggered,
|
|
562
|
+
matched_prompt_id: row.matched_prompt_id,
|
|
563
|
+
confidence: row.confidence,
|
|
564
|
+
};
|
|
565
|
+
const existing = bySkill.get(row.skill_name);
|
|
566
|
+
if (existing) existing.push(base);
|
|
567
|
+
else bySkill.set(row.skill_name, [base]);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const evidenceSkills = new Set(
|
|
571
|
+
(
|
|
572
|
+
db.query(`SELECT DISTINCT skill_name FROM evolution_evidence`).all() as Array<{
|
|
573
|
+
skill_name: string;
|
|
574
|
+
}>
|
|
575
|
+
).map((row) => row.skill_name),
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
const skillScopeRows = db
|
|
579
|
+
.query(
|
|
580
|
+
`SELECT
|
|
581
|
+
si.skill_name,
|
|
582
|
+
COALESCE(
|
|
583
|
+
(SELECT s2.skill_scope FROM skill_invocations s2
|
|
584
|
+
WHERE s2.skill_name = si.skill_name AND s2.skill_scope IS NOT NULL
|
|
585
|
+
ORDER BY s2.occurred_at DESC LIMIT 1),
|
|
586
|
+
(SELECT su.skill_scope FROM skill_usage su
|
|
587
|
+
WHERE su.skill_name = si.skill_name AND su.skill_scope IS NOT NULL
|
|
588
|
+
ORDER BY su.timestamp DESC LIMIT 1)
|
|
589
|
+
) as skill_scope
|
|
590
|
+
FROM skill_invocations si
|
|
591
|
+
GROUP BY si.skill_name`,
|
|
592
|
+
)
|
|
593
|
+
.all() as Array<{ skill_name: string; skill_scope: string | null }>;
|
|
594
|
+
const scopeBySkill = new Map(skillScopeRows.map((row) => [row.skill_name, row.skill_scope]));
|
|
595
|
+
const testingReadiness = testingReadinessRows ?? listSkillTestingReadiness(db);
|
|
596
|
+
const testingReadinessBySkill = new Map(
|
|
597
|
+
testingReadiness.map((row) => [row.skill_name, row] as const),
|
|
598
|
+
);
|
|
599
|
+
const knownSkills = new Set<string>(bySkill.keys());
|
|
600
|
+
|
|
601
|
+
return [...knownSkills]
|
|
602
|
+
.map((skillName) => {
|
|
603
|
+
const rows = bySkill.get(skillName) ?? [];
|
|
604
|
+
const totalChecks = rows.length;
|
|
605
|
+
const triggeredCount = rows.filter((row) => row.triggered === 1).length;
|
|
606
|
+
const uniqueSessions = new Set(rows.map((row) => row.session_id)).size;
|
|
607
|
+
const lastSeen =
|
|
608
|
+
rows
|
|
609
|
+
.map((row) => row.occurred_at)
|
|
610
|
+
.filter((value): value is string => value != null)
|
|
611
|
+
.sort((a, b) => b.localeCompare(a))[0] ?? null;
|
|
612
|
+
const withConfidence = rows.filter((row) => row.confidence != null);
|
|
613
|
+
const routingConfidence =
|
|
614
|
+
withConfidence.length > 0
|
|
615
|
+
? withConfidence.reduce((sum, row) => sum + (row.confidence ?? 0), 0) /
|
|
616
|
+
withConfidence.length
|
|
617
|
+
: null;
|
|
618
|
+
const readiness = testingReadinessBySkill.get(skillName);
|
|
619
|
+
const fallbackScope =
|
|
620
|
+
readiness?.skill_path != null ? classifySkillPath(readiness.skill_path).skill_scope : null;
|
|
621
|
+
|
|
622
|
+
return {
|
|
623
|
+
skill_name: skillName,
|
|
624
|
+
skill_scope:
|
|
625
|
+
scopeBySkill.get(skillName) ??
|
|
626
|
+
(fallbackScope && fallbackScope !== "unknown" ? fallbackScope : null),
|
|
627
|
+
total_checks: totalChecks,
|
|
628
|
+
triggered_count: triggeredCount,
|
|
629
|
+
pass_rate: totalChecks > 0 ? triggeredCount / totalChecks : 0,
|
|
630
|
+
unique_sessions: uniqueSessions,
|
|
631
|
+
last_seen: lastSeen,
|
|
632
|
+
has_evidence: evidenceSkills.has(skillName),
|
|
633
|
+
routing_confidence: routingConfidence,
|
|
634
|
+
confidence_coverage: totalChecks > 0 ? withConfidence.length / totalChecks : 0,
|
|
635
|
+
testing_readiness: readiness,
|
|
636
|
+
};
|
|
637
|
+
})
|
|
638
|
+
.sort(
|
|
639
|
+
(a, b) =>
|
|
640
|
+
b.total_checks - a.total_checks ||
|
|
641
|
+
(b.last_seen ?? "").localeCompare(a.last_seen ?? "") ||
|
|
642
|
+
a.skill_name.localeCompare(b.skill_name),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function getAnalyticsPayload(db: Database): AnalyticsResponse {
|
|
647
|
+
const trustedRows = queryTrustedSkillObservationRows(db);
|
|
648
|
+
const today = new Date();
|
|
649
|
+
const dateKey = (value: string | null): string | null => {
|
|
650
|
+
if (!value) return null;
|
|
651
|
+
const parsed = new Date(value);
|
|
652
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString().slice(0, 10);
|
|
653
|
+
};
|
|
654
|
+
const cutoffDate = (days: number): string => {
|
|
655
|
+
const cutoff = new Date(today);
|
|
656
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - days);
|
|
657
|
+
return cutoff.toISOString().slice(0, 10);
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const passRateTrendByDate = new Map<string, { triggered: number; total: number }>();
|
|
661
|
+
for (const row of trustedRows) {
|
|
662
|
+
const occurredDate = dateKey(row.occurred_at);
|
|
663
|
+
if (!occurredDate || occurredDate < cutoffDate(90)) continue;
|
|
664
|
+
const counts = passRateTrendByDate.get(occurredDate) ?? { triggered: 0, total: 0 };
|
|
665
|
+
counts.total += 1;
|
|
666
|
+
if (row.triggered === 1) counts.triggered += 1;
|
|
667
|
+
passRateTrendByDate.set(occurredDate, counts);
|
|
668
|
+
}
|
|
669
|
+
const passRateTrendRows = [...passRateTrendByDate.entries()]
|
|
670
|
+
.map(([date, counts]) => ({
|
|
671
|
+
date,
|
|
672
|
+
pass_rate: counts.total > 0 ? counts.triggered / counts.total : 0,
|
|
673
|
+
total_checks: counts.total,
|
|
674
|
+
}))
|
|
675
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
676
|
+
|
|
677
|
+
const skillRankingMap = new Map<string, { triggered_count: number; total_checks: number }>();
|
|
678
|
+
for (const row of trustedRows) {
|
|
679
|
+
const counts = skillRankingMap.get(row.skill_name) ?? { triggered_count: 0, total_checks: 0 };
|
|
680
|
+
counts.total_checks += 1;
|
|
681
|
+
if (row.triggered === 1) counts.triggered_count += 1;
|
|
682
|
+
skillRankingMap.set(row.skill_name, counts);
|
|
683
|
+
}
|
|
684
|
+
const skillRankingRows = [...skillRankingMap.entries()]
|
|
685
|
+
.map(([skill_name, counts]) => ({
|
|
686
|
+
skill_name,
|
|
687
|
+
pass_rate: counts.total_checks > 0 ? counts.triggered_count / counts.total_checks : 0,
|
|
688
|
+
total_checks: counts.total_checks,
|
|
689
|
+
triggered_count: counts.triggered_count,
|
|
690
|
+
}))
|
|
691
|
+
.sort(
|
|
692
|
+
(a, b) =>
|
|
693
|
+
b.pass_rate - a.pass_rate ||
|
|
694
|
+
b.total_checks - a.total_checks ||
|
|
695
|
+
a.skill_name.localeCompare(b.skill_name),
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
const dailyActivityByDate = new Map<string, number>();
|
|
699
|
+
for (const row of trustedRows) {
|
|
700
|
+
const occurredDate = dateKey(row.occurred_at);
|
|
701
|
+
if (!occurredDate || occurredDate < cutoffDate(84)) continue;
|
|
702
|
+
dailyActivityByDate.set(occurredDate, (dailyActivityByDate.get(occurredDate) ?? 0) + 1);
|
|
703
|
+
}
|
|
704
|
+
const dailyActivityRows = [...dailyActivityByDate.entries()]
|
|
705
|
+
.map(([date, checks]) => ({ date, checks }))
|
|
706
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
707
|
+
|
|
708
|
+
const deployedRows = db
|
|
709
|
+
.query(
|
|
710
|
+
`SELECT ea.skill_name, ea.proposal_id, ea.timestamp as deployed_at
|
|
711
|
+
FROM evolution_audit ea
|
|
712
|
+
WHERE ea.action = 'deployed' AND ea.skill_name IS NOT NULL
|
|
713
|
+
ORDER BY ea.timestamp DESC`,
|
|
714
|
+
)
|
|
715
|
+
.all() as Array<{ skill_name: string; proposal_id: string; deployed_at: string }>;
|
|
716
|
+
|
|
717
|
+
const evolution_impact: AnalyticsResponse["evolution_impact"] = [];
|
|
718
|
+
for (const deploy of deployedRows) {
|
|
719
|
+
const beforeRows = trustedRows.filter(
|
|
720
|
+
(row) => row.skill_name === deploy.skill_name && (row.occurred_at ?? "") < deploy.deployed_at,
|
|
721
|
+
);
|
|
722
|
+
const afterRows = trustedRows.filter(
|
|
723
|
+
(row) =>
|
|
724
|
+
row.skill_name === deploy.skill_name && (row.occurred_at ?? "") >= deploy.deployed_at,
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
evolution_impact.push({
|
|
728
|
+
skill_name: deploy.skill_name,
|
|
729
|
+
proposal_id: deploy.proposal_id,
|
|
730
|
+
deployed_at: deploy.deployed_at,
|
|
731
|
+
pass_rate_before:
|
|
732
|
+
beforeRows.length > 0
|
|
733
|
+
? beforeRows.filter((row) => row.triggered === 1).length / beforeRows.length
|
|
734
|
+
: 0,
|
|
735
|
+
pass_rate_after:
|
|
736
|
+
afterRows.length > 0
|
|
737
|
+
? afterRows.filter((row) => row.triggered === 1).length / afterRows.length
|
|
738
|
+
: 0,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const totalEvolutionsRow = db
|
|
743
|
+
.query(`SELECT COUNT(*) as c FROM evolution_audit WHERE action = 'deployed'`)
|
|
744
|
+
.get() as { c: number } | null;
|
|
745
|
+
const checks30dRows = trustedRows.filter((row) => {
|
|
746
|
+
const occurredDate = dateKey(row.occurred_at);
|
|
747
|
+
return occurredDate != null && occurredDate >= cutoffDate(30);
|
|
748
|
+
});
|
|
749
|
+
const activeSkills30d = new Set(checks30dRows.map((row) => row.skill_name));
|
|
750
|
+
|
|
751
|
+
let avgImprovement = 0;
|
|
752
|
+
if (evolution_impact.length > 0) {
|
|
753
|
+
const totalImprovement = evolution_impact.reduce(
|
|
754
|
+
(sum, impact) => sum + (impact.pass_rate_after - impact.pass_rate_before),
|
|
755
|
+
0,
|
|
756
|
+
);
|
|
757
|
+
avgImprovement = totalImprovement / evolution_impact.length;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return {
|
|
761
|
+
pass_rate_trend: passRateTrendRows.map((row) => ({
|
|
762
|
+
date: row.date,
|
|
763
|
+
pass_rate: row.pass_rate,
|
|
764
|
+
total_checks: row.total_checks,
|
|
765
|
+
})),
|
|
766
|
+
skill_rankings: skillRankingRows.map((row) => ({
|
|
767
|
+
skill_name: row.skill_name,
|
|
768
|
+
pass_rate: row.pass_rate,
|
|
769
|
+
total_checks: row.total_checks,
|
|
770
|
+
triggered_count: row.triggered_count,
|
|
771
|
+
})),
|
|
772
|
+
daily_activity: dailyActivityRows.map((row) => ({
|
|
773
|
+
date: row.date,
|
|
774
|
+
checks: row.checks,
|
|
775
|
+
})),
|
|
776
|
+
evolution_impact,
|
|
777
|
+
summary: {
|
|
778
|
+
total_evolutions: totalEvolutionsRow?.c ?? 0,
|
|
779
|
+
avg_improvement: avgImprovement,
|
|
780
|
+
total_checks_30d: checks30dRows.length,
|
|
781
|
+
active_skills: activeSkills30d.size,
|
|
782
|
+
},
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export function getActiveSessionCount(db: Database): number {
|
|
787
|
+
const row = db
|
|
788
|
+
.query(
|
|
789
|
+
`SELECT COUNT(DISTINCT q.session_id) as count
|
|
790
|
+
FROM queries q
|
|
791
|
+
WHERE NOT EXISTS (
|
|
792
|
+
SELECT 1 FROM session_telemetry st WHERE st.session_id = q.session_id
|
|
793
|
+
)`,
|
|
794
|
+
)
|
|
795
|
+
.get() as { count: number };
|
|
796
|
+
return row.count;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export function getRecentActivity(db: Database, limit = 20): RecentActivityItem[] {
|
|
800
|
+
const rows = db
|
|
801
|
+
.query(
|
|
802
|
+
`SELECT occurred_at, session_id, skill_name, query, triggered
|
|
803
|
+
FROM skill_invocations
|
|
804
|
+
ORDER BY occurred_at DESC
|
|
805
|
+
LIMIT ?`,
|
|
806
|
+
)
|
|
807
|
+
.all(limit) as Array<{
|
|
808
|
+
occurred_at: string;
|
|
809
|
+
session_id: string;
|
|
810
|
+
skill_name: string;
|
|
811
|
+
query: string;
|
|
812
|
+
triggered: number;
|
|
813
|
+
}>;
|
|
814
|
+
|
|
815
|
+
if (rows.length === 0) return [];
|
|
816
|
+
|
|
817
|
+
const uniqueSessionIds = [...new Set(rows.map((row) => row.session_id))];
|
|
818
|
+
const placeholders = uniqueSessionIds.map(() => "?").join(",");
|
|
819
|
+
const completedRows = db
|
|
820
|
+
.query(
|
|
821
|
+
`SELECT DISTINCT session_id FROM session_telemetry WHERE session_id IN (${placeholders})`,
|
|
822
|
+
)
|
|
823
|
+
.all(...uniqueSessionIds) as Array<{ session_id: string }>;
|
|
824
|
+
const completedSessions = new Set(completedRows.map((row) => row.session_id));
|
|
825
|
+
|
|
826
|
+
return rows.map((row) => ({
|
|
827
|
+
timestamp: row.occurred_at,
|
|
828
|
+
session_id: row.session_id,
|
|
829
|
+
skill_name: row.skill_name,
|
|
830
|
+
query: row.query ?? "",
|
|
831
|
+
triggered: row.triggered === 1,
|
|
832
|
+
is_live: !completedSessions.has(row.session_id),
|
|
833
|
+
}));
|
|
834
|
+
}
|