selftune 0.2.23 → 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.
Files changed (219) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +93 -15
  3. package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
  4. package/apps/local-dashboard/dist/assets/index-Dmx7LPVX.js +15 -0
  5. package/apps/local-dashboard/dist/assets/vendor-react-C5oyHiV1.js +11 -0
  6. package/apps/local-dashboard/dist/assets/{vendor-table-BIiI3YhS.js → vendor-table-Bc_bbKd8.js} +1 -1
  7. package/apps/local-dashboard/dist/assets/vendor-ui-B3BPIYy7.js +1 -0
  8. package/apps/local-dashboard/dist/index.html +5 -5
  9. package/cli/selftune/adapters/codex/install.ts +310 -78
  10. package/cli/selftune/adapters/opencode/install.ts +3 -4
  11. package/cli/selftune/alpha-upload/build-payloads.ts +3 -3
  12. package/cli/selftune/alpha-upload/stage-canonical.ts +17 -11
  13. package/cli/selftune/auto-update.ts +200 -8
  14. package/cli/selftune/canonical-export.ts +55 -25
  15. package/cli/selftune/command-surface.ts +397 -0
  16. package/cli/selftune/contribute/contribute.ts +64 -13
  17. package/cli/selftune/contribution-config.ts +57 -3
  18. package/cli/selftune/contribution-preferences.ts +117 -0
  19. package/cli/selftune/contribution-signals.ts +8 -4
  20. package/cli/selftune/contribution-staging.ts +13 -2
  21. package/cli/selftune/contributions.ts +55 -121
  22. package/cli/selftune/creator-contributions.ts +29 -10
  23. package/cli/selftune/cron/setup.ts +7 -3
  24. package/cli/selftune/dashboard-contract.ts +73 -0
  25. package/cli/selftune/dashboard-server.ts +168 -17
  26. package/cli/selftune/dashboard.ts +350 -17
  27. package/cli/selftune/eval/baseline.ts +21 -5
  28. package/cli/selftune/eval/execution-eval.ts +170 -0
  29. package/cli/selftune/eval/family-overlap.ts +2 -2
  30. package/cli/selftune/eval/hooks-to-evals.ts +228 -82
  31. package/cli/selftune/eval/import-skillsbench.ts +2 -2
  32. package/cli/selftune/eval/invocation-classifier.ts +56 -0
  33. package/cli/selftune/eval/synthetic-evals.ts +5 -3
  34. package/cli/selftune/eval/unit-test-cli.ts +7 -4
  35. package/cli/selftune/evolution/apply-proposal.ts +295 -0
  36. package/cli/selftune/evolution/engines/replay-engine.ts +79 -57
  37. package/cli/selftune/evolution/evolve-body.ts +100 -39
  38. package/cli/selftune/evolution/evolve.ts +244 -52
  39. package/cli/selftune/evolution/rollback.ts +0 -1
  40. package/cli/selftune/evolution/validate-body.ts +68 -42
  41. package/cli/selftune/evolution/validate-host-replay.ts +510 -60
  42. package/cli/selftune/evolution/validate-proposal.ts +11 -150
  43. package/cli/selftune/evolution/validate-routing.ts +43 -41
  44. package/cli/selftune/evolution/validation-contract.ts +91 -0
  45. package/cli/selftune/grading/auto-grade.ts +11 -7
  46. package/cli/selftune/grading/grade-session.ts +10 -16
  47. package/cli/selftune/index.ts +35 -10
  48. package/cli/selftune/ingestors/claude-replay.ts +15 -10
  49. package/cli/selftune/ingestors/codex-wrapper.ts +3 -3
  50. package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
  51. package/cli/selftune/ingestors/pi-ingest.ts +3 -2
  52. package/cli/selftune/init.ts +27 -3
  53. package/cli/selftune/localdb/direct-write.ts +35 -1
  54. package/cli/selftune/localdb/queries/cron.ts +34 -0
  55. package/cli/selftune/localdb/queries/dashboard.ts +834 -0
  56. package/cli/selftune/localdb/queries/evolution.ts +158 -0
  57. package/cli/selftune/localdb/queries/execution.ts +133 -0
  58. package/cli/selftune/localdb/queries/json.ts +18 -0
  59. package/cli/selftune/localdb/queries/monitoring.ts +263 -0
  60. package/cli/selftune/localdb/queries/raw.ts +95 -0
  61. package/cli/selftune/localdb/queries/staging.ts +270 -0
  62. package/cli/selftune/localdb/queries/trust.ts +392 -0
  63. package/cli/selftune/localdb/queries.ts +60 -2288
  64. package/cli/selftune/localdb/schema.ts +21 -0
  65. package/cli/selftune/monitoring/watch.ts +96 -29
  66. package/cli/selftune/normalization.ts +3 -0
  67. package/cli/selftune/observability.ts +4 -2
  68. package/cli/selftune/orchestrate/cli.ts +161 -0
  69. package/cli/selftune/orchestrate/execute.ts +295 -0
  70. package/cli/selftune/orchestrate/finalize.ts +157 -0
  71. package/cli/selftune/orchestrate/locks.ts +40 -0
  72. package/cli/selftune/orchestrate/plan.ts +131 -0
  73. package/cli/selftune/orchestrate/post-run.ts +59 -0
  74. package/cli/selftune/orchestrate/prepare.ts +334 -0
  75. package/cli/selftune/orchestrate/report.ts +182 -0
  76. package/cli/selftune/orchestrate/runtime.ts +120 -0
  77. package/cli/selftune/orchestrate/signals.ts +48 -0
  78. package/cli/selftune/orchestrate.ts +150 -1173
  79. package/cli/selftune/repair/skill-usage.ts +5 -2
  80. package/cli/selftune/routes/overview.ts +5 -2
  81. package/cli/selftune/routes/skill-report.ts +15 -2
  82. package/cli/selftune/schedule.ts +5 -5
  83. package/cli/selftune/status.ts +39 -2
  84. package/cli/selftune/testing-readiness.ts +597 -0
  85. package/cli/selftune/types.ts +44 -4
  86. package/cli/selftune/uninstall.ts +2 -1
  87. package/cli/selftune/utils/canonical-log.ts +1 -9
  88. package/cli/selftune/utils/cli-error.ts +9 -0
  89. package/cli/selftune/utils/llm-call.ts +126 -6
  90. package/cli/selftune/utils/skill-discovery.ts +2 -0
  91. package/cli/selftune/workflows/proposals.ts +184 -0
  92. package/cli/selftune/workflows/skill-scaffold.ts +241 -0
  93. package/cli/selftune/workflows/workflows.ts +100 -26
  94. package/node_modules/@selftune/telemetry-contract/fixtures/complete-push.ts +1 -1
  95. package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
  96. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
  97. package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
  98. package/node_modules/@selftune/telemetry-contract/src/schemas.ts +41 -1
  99. package/node_modules/@selftune/telemetry-contract/src/types.ts +103 -2
  100. package/package.json +25 -9
  101. package/packages/dashboard-core/AGENTS.md +18 -0
  102. package/packages/dashboard-core/README.md +30 -0
  103. package/packages/dashboard-core/index.ts +3 -0
  104. package/packages/dashboard-core/package.json +39 -0
  105. package/packages/dashboard-core/src/chrome/DashboardChrome.tsx +74 -0
  106. package/packages/dashboard-core/src/chrome/DashboardHeader.tsx +200 -0
  107. package/packages/dashboard-core/src/chrome/DashboardSidebar.tsx +219 -0
  108. package/packages/dashboard-core/src/chrome/RuntimeBadge.tsx +46 -0
  109. package/packages/dashboard-core/src/chrome/index.ts +14 -0
  110. package/packages/dashboard-core/src/chrome/types.ts +81 -0
  111. package/packages/dashboard-core/src/chrome/utils.ts +23 -0
  112. package/packages/dashboard-core/src/gates/FeatureGate.tsx +11 -0
  113. package/packages/dashboard-core/src/gates/LockedRoute.tsx +29 -0
  114. package/packages/dashboard-core/src/gates/UpgradeCard.tsx +89 -0
  115. package/packages/dashboard-core/src/gates/index.ts +3 -0
  116. package/packages/dashboard-core/src/host/DashboardHostProvider.tsx +62 -0
  117. package/packages/dashboard-core/src/host/adapter.ts +47 -0
  118. package/packages/dashboard-core/src/host/capabilities.ts +55 -0
  119. package/packages/dashboard-core/src/host/index.ts +3 -0
  120. package/packages/dashboard-core/src/models/analytics.ts +39 -0
  121. package/packages/dashboard-core/src/models/index.ts +4 -0
  122. package/packages/dashboard-core/src/models/overview.ts +98 -0
  123. package/packages/dashboard-core/src/models/runtime.ts +7 -0
  124. package/packages/dashboard-core/src/models/skills.ts +34 -0
  125. package/packages/dashboard-core/src/routes/index.ts +2 -0
  126. package/packages/dashboard-core/src/routes/manifest.test.ts +70 -0
  127. package/packages/dashboard-core/src/routes/manifest.ts +451 -0
  128. package/packages/dashboard-core/src/routes/types.ts +39 -0
  129. package/packages/dashboard-core/src/screens/analytics/AnalyticsScreen.tsx +278 -0
  130. package/packages/dashboard-core/src/screens/analytics/index.ts +1 -0
  131. package/packages/dashboard-core/src/screens/index.ts +37 -0
  132. package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.test.ts +101 -0
  133. package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.tsx +393 -0
  134. package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.test.tsx +113 -0
  135. package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.tsx +72 -0
  136. package/packages/dashboard-core/src/screens/overview/OverviewCoreSurface.tsx +71 -0
  137. package/packages/dashboard-core/src/screens/overview/OverviewOnboardingBanner.tsx +90 -0
  138. package/packages/dashboard-core/src/screens/overview/OverviewRunSummary.tsx +40 -0
  139. package/packages/dashboard-core/src/screens/overview/index.ts +16 -0
  140. package/packages/dashboard-core/src/screens/overview/types.ts +13 -0
  141. package/packages/dashboard-core/src/screens/skill-report/SkillReportDailyBreakdownSection.tsx +99 -0
  142. package/packages/dashboard-core/src/screens/skill-report/SkillReportDataQualityTabContent.tsx +35 -0
  143. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceRail.tsx +71 -0
  144. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceSection.tsx +63 -0
  145. package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceTabContent.tsx +25 -0
  146. package/packages/dashboard-core/src/screens/skill-report/SkillReportInvocationsSection.tsx +24 -0
  147. package/packages/dashboard-core/src/screens/skill-report/SkillReportMissedQueriesSection.tsx +79 -0
  148. package/packages/dashboard-core/src/screens/skill-report/SkillReportScaffold.tsx +150 -0
  149. package/packages/dashboard-core/src/screens/skill-report/SkillReportSections.test.tsx +224 -0
  150. package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.test.tsx +76 -0
  151. package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.tsx +88 -0
  152. package/packages/dashboard-core/src/screens/skill-report/SkillReportTrendSection.tsx +33 -0
  153. package/packages/dashboard-core/src/screens/skill-report/SkillReportTrustBadge.tsx +67 -0
  154. package/packages/dashboard-core/src/screens/skill-report/index.ts +45 -0
  155. package/packages/dashboard-core/src/screens/skills/SkillsLibraryScreen.tsx +162 -0
  156. package/packages/dashboard-core/src/screens/skills/index.ts +6 -0
  157. package/packages/telemetry-contract/fixtures/complete-push.ts +1 -1
  158. package/packages/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
  159. package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
  160. package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
  161. package/packages/telemetry-contract/src/schemas.ts +41 -1
  162. package/packages/telemetry-contract/src/types.ts +103 -2
  163. package/packages/ui/src/components/EvidenceViewer.tsx +80 -25
  164. package/packages/ui/src/components/OverviewPanels.tsx +67 -26
  165. package/packages/ui/src/primitives/tabs.tsx +7 -6
  166. package/packages/ui/src/types.ts +10 -0
  167. package/skill/SKILL.md +130 -332
  168. package/skill/agents/diagnosis-analyst.md +3 -3
  169. package/skill/agents/evolution-reviewer.md +3 -3
  170. package/skill/agents/integration-guide.md +3 -3
  171. package/skill/agents/pattern-analyst.md +2 -2
  172. package/skill/references/cli-quick-reference.md +89 -0
  173. package/skill/references/creator-playbook.md +131 -0
  174. package/skill/references/examples.md +48 -0
  175. package/skill/references/troubleshooting.md +47 -0
  176. package/skill/references/version-history.md +1 -1
  177. package/skill/selftune.contribute.json +11 -0
  178. package/skill/{Workflows → workflows}/Baseline.md +20 -1
  179. package/skill/{Workflows → workflows}/Contribute.md +23 -10
  180. package/skill/{Workflows → workflows}/Contributions.md +13 -5
  181. package/skill/workflows/CreateTestDeploy.md +170 -0
  182. package/skill/{Workflows → workflows}/CreatorContributions.md +18 -6
  183. package/skill/{Workflows → workflows}/Cron.md +1 -1
  184. package/skill/{Workflows → workflows}/Dashboard.md +20 -0
  185. package/skill/{Workflows → workflows}/Doctor.md +1 -1
  186. package/skill/{Workflows → workflows}/Evals.md +67 -2
  187. package/skill/{Workflows → workflows}/Evolve.md +119 -30
  188. package/skill/{Workflows → workflows}/EvolveBody.md +41 -1
  189. package/skill/{Workflows → workflows}/Grade.md +1 -1
  190. package/skill/{Workflows → workflows}/Initialize.md +8 -4
  191. package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
  192. package/skill/{Workflows → workflows}/Schedule.md +3 -3
  193. package/skill/workflows/SignalsDashboard.md +87 -0
  194. package/skill/{Workflows → workflows}/UnitTest.md +19 -0
  195. package/skill/{Workflows → workflows}/Watch.md +42 -2
  196. package/skill/{Workflows → workflows}/Workflows.md +39 -2
  197. package/apps/local-dashboard/dist/assets/index-CwOtTrUS.css +0 -1
  198. package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +0 -59
  199. package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
  200. package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +0 -12
  201. /package/skill/{Workflows → workflows}/AlphaUpload.md +0 -0
  202. /package/skill/{Workflows → workflows}/AutoActivation.md +0 -0
  203. /package/skill/{Workflows → workflows}/Badge.md +0 -0
  204. /package/skill/{Workflows → workflows}/Composability.md +0 -0
  205. /package/skill/{Workflows → workflows}/EvolutionMemory.md +0 -0
  206. /package/skill/{Workflows → workflows}/ExportCanonical.md +0 -0
  207. /package/skill/{Workflows → workflows}/Hook.md +0 -0
  208. /package/skill/{Workflows → workflows}/ImportSkillsBench.md +0 -0
  209. /package/skill/{Workflows → workflows}/Ingest.md +0 -0
  210. /package/skill/{Workflows → workflows}/PlatformHooks.md +0 -0
  211. /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
  212. /package/skill/{Workflows → workflows}/Recover.md +0 -0
  213. /package/skill/{Workflows → workflows}/Registry.md +0 -0
  214. /package/skill/{Workflows → workflows}/RepairSkillUsage.md +0 -0
  215. /package/skill/{Workflows → workflows}/Replay.md +0 -0
  216. /package/skill/{Workflows → workflows}/Rollback.md +0 -0
  217. /package/skill/{Workflows → workflows}/Sync.md +0 -0
  218. /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
  219. /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
@@ -0,0 +1,158 @@
1
+ import type { Database } from "bun:sqlite";
2
+
3
+ import type { OrchestrateRunReport, PendingProposal } from "../../dashboard-contract.js";
4
+ import { safeParseJson, safeParseJsonArray } from "./json.js";
5
+
6
+ export function queryEvolutionAudit(
7
+ db: Database,
8
+ skillName?: string,
9
+ ): Array<{
10
+ timestamp: string;
11
+ proposal_id: string;
12
+ skill_name?: string;
13
+ action: string;
14
+ details: string;
15
+ eval_snapshot?: Record<string, unknown>;
16
+ validation_mode?: string;
17
+ validation_agent?: string;
18
+ validation_fixture_id?: string;
19
+ validation_evidence_ref?: string;
20
+ }> {
21
+ const sql = skillName
22
+ ? `SELECT * FROM evolution_audit
23
+ WHERE skill_name = ?
24
+ OR (skill_name IS NULL AND proposal_id LIKE 'evo-' || ? || '-%')
25
+ ORDER BY timestamp DESC`
26
+ : `SELECT * FROM evolution_audit ORDER BY timestamp DESC`;
27
+ const rows = (skillName ? db.query(sql).all(skillName, skillName) : db.query(sql).all()) as Array<
28
+ Record<string, unknown>
29
+ >;
30
+
31
+ return rows.map((row) => ({
32
+ timestamp: row.timestamp as string,
33
+ proposal_id: row.proposal_id as string,
34
+ skill_name: typeof row.skill_name === "string" ? row.skill_name : undefined,
35
+ action: row.action as string,
36
+ details: row.details as string,
37
+ eval_snapshot: row.eval_snapshot_json
38
+ ? (safeParseJson(row.eval_snapshot_json as string) as Record<string, unknown>)
39
+ : undefined,
40
+ validation_mode: typeof row.validation_mode === "string" ? row.validation_mode : undefined,
41
+ validation_agent: typeof row.validation_agent === "string" ? row.validation_agent : undefined,
42
+ validation_fixture_id:
43
+ typeof row.validation_fixture_id === "string" ? row.validation_fixture_id : undefined,
44
+ validation_evidence_ref:
45
+ typeof row.validation_evidence_ref === "string" ? row.validation_evidence_ref : undefined,
46
+ }));
47
+ }
48
+
49
+ export function queryEvolutionEvidence(
50
+ db: Database,
51
+ skillName?: string,
52
+ ): Array<{
53
+ timestamp: string;
54
+ proposal_id: string;
55
+ skill_name: string;
56
+ skill_path: string;
57
+ target: string;
58
+ stage: string;
59
+ rationale?: string;
60
+ confidence?: number;
61
+ details?: string;
62
+ original_text?: string;
63
+ proposed_text?: string;
64
+ eval_set?: Record<string, unknown>[];
65
+ validation?: Record<string, unknown>;
66
+ }> {
67
+ const sql = skillName
68
+ ? `SELECT * FROM evolution_evidence WHERE skill_name = ? ORDER BY timestamp DESC`
69
+ : `SELECT * FROM evolution_evidence ORDER BY timestamp DESC`;
70
+ const rows = (skillName ? db.query(sql).all(skillName) : db.query(sql).all()) as Array<
71
+ Record<string, unknown>
72
+ >;
73
+
74
+ return rows.map((row) => ({
75
+ timestamp: row.timestamp as string,
76
+ proposal_id: row.proposal_id as string,
77
+ skill_name: row.skill_name as string,
78
+ skill_path: row.skill_path as string,
79
+ target: row.target as string,
80
+ stage: row.stage as string,
81
+ rationale: row.rationale as string | undefined,
82
+ confidence: row.confidence as number | undefined,
83
+ details: row.details as string | undefined,
84
+ original_text: row.original_text as string | undefined,
85
+ proposed_text: row.proposed_text as string | undefined,
86
+ eval_set: row.eval_set_json
87
+ ? safeParseJsonArray<Record<string, unknown>>(row.eval_set_json as string)
88
+ : undefined,
89
+ validation: row.validation_json
90
+ ? (safeParseJson(row.validation_json as string) as Record<string, unknown>)
91
+ : undefined,
92
+ }));
93
+ }
94
+
95
+ export function getPendingProposals(db: Database, skillName?: string): PendingProposal[] {
96
+ const whereClause = skillName ? "WHERE ea.skill_name = ? AND" : "WHERE";
97
+ const params = skillName ? [skillName] : [];
98
+
99
+ return db
100
+ .query(
101
+ `WITH latest AS (
102
+ SELECT ea.proposal_id, ea.action, ea.timestamp, ea.details, ea.skill_name,
103
+ ROW_NUMBER() OVER (PARTITION BY ea.proposal_id ORDER BY ea.timestamp DESC, ea.id DESC) AS rn
104
+ FROM evolution_audit ea
105
+ LEFT JOIN evolution_audit ea2
106
+ ON ea2.proposal_id = ea.proposal_id
107
+ AND ea2.action IN ('deployed', 'rejected', 'rolled_back')
108
+ ${whereClause} ea.action IN ('created', 'validated')
109
+ AND ea2.id IS NULL
110
+ )
111
+ SELECT proposal_id, action, timestamp, details, skill_name
112
+ FROM latest
113
+ WHERE rn = 1
114
+ ORDER BY timestamp DESC`,
115
+ )
116
+ .all(...params) as PendingProposal[];
117
+ }
118
+
119
+ export function getOrchestrateRuns(db: Database, limit = 20): OrchestrateRunReport[] {
120
+ const rows = db
121
+ .query(
122
+ `SELECT run_id, timestamp, elapsed_ms, dry_run, approval_mode,
123
+ total_skills, evaluated, evolved, deployed, watched, skipped,
124
+ skill_actions_json
125
+ FROM orchestrate_runs
126
+ ORDER BY timestamp DESC
127
+ LIMIT ?`,
128
+ )
129
+ .all(limit) as Array<{
130
+ run_id: string;
131
+ timestamp: string;
132
+ elapsed_ms: number;
133
+ dry_run: number;
134
+ approval_mode: string;
135
+ total_skills: number;
136
+ evaluated: number;
137
+ evolved: number;
138
+ deployed: number;
139
+ watched: number;
140
+ skipped: number;
141
+ skill_actions_json: string;
142
+ }>;
143
+
144
+ return rows.map((row) => ({
145
+ run_id: row.run_id,
146
+ timestamp: row.timestamp,
147
+ elapsed_ms: row.elapsed_ms,
148
+ dry_run: row.dry_run === 1,
149
+ approval_mode: row.approval_mode as "auto" | "review",
150
+ total_skills: row.total_skills,
151
+ evaluated: row.evaluated,
152
+ evolved: row.evolved,
153
+ deployed: row.deployed,
154
+ watched: row.watched,
155
+ skipped: row.skipped,
156
+ skill_actions: safeParseJsonArray(row.skill_actions_json),
157
+ }));
158
+ }
@@ -0,0 +1,133 @@
1
+ import type { Database } from "bun:sqlite";
2
+
3
+ import type { CommitRecord, CommitSummary, ExecutionMetrics } from "../../dashboard-contract.js";
4
+
5
+ export function getExecutionMetrics(db: Database, sessionIds: string[]): ExecutionMetrics {
6
+ const empty: ExecutionMetrics = {
7
+ avg_files_changed: 0,
8
+ total_lines_added: 0,
9
+ total_lines_removed: 0,
10
+ total_cost_usd: 0,
11
+ avg_cost_usd: 0,
12
+ cached_input_tokens_total: 0,
13
+ reasoning_output_tokens_total: 0,
14
+ artifact_count: 0,
15
+ session_type_distribution: {},
16
+ };
17
+ if (sessionIds.length === 0) return empty;
18
+
19
+ const placeholders = sessionIds.map(() => "?").join(",");
20
+ const row = db
21
+ .query(
22
+ `SELECT
23
+ COALESCE(AVG(files_changed), 0) AS avg_files_changed,
24
+ COALESCE(SUM(lines_added), 0) AS total_lines_added,
25
+ COALESCE(SUM(lines_removed), 0) AS total_lines_removed,
26
+ COALESCE(SUM(cost_usd), 0) AS total_cost_usd,
27
+ COALESCE(AVG(cost_usd), 0) AS avg_cost_usd,
28
+ COALESCE(SUM(cached_input_tokens), 0) AS cached_input_tokens_total,
29
+ COALESCE(SUM(reasoning_output_tokens), 0) AS reasoning_output_tokens_total,
30
+ COALESCE(SUM(artifact_count), 0) AS artifact_count
31
+ FROM execution_facts
32
+ WHERE session_id IN (${placeholders})`,
33
+ )
34
+ .get(...sessionIds) as {
35
+ avg_files_changed: number;
36
+ total_lines_added: number;
37
+ total_lines_removed: number;
38
+ total_cost_usd: number;
39
+ avg_cost_usd: number;
40
+ cached_input_tokens_total: number;
41
+ reasoning_output_tokens_total: number;
42
+ artifact_count: number;
43
+ } | null;
44
+
45
+ const typeRows = db
46
+ .query(
47
+ `SELECT session_type, COUNT(*) AS count
48
+ FROM execution_facts
49
+ WHERE session_id IN (${placeholders}) AND session_type IS NOT NULL
50
+ GROUP BY session_type`,
51
+ )
52
+ .all(...sessionIds) as Array<{ session_type: string; count: number }>;
53
+
54
+ const session_type_distribution: Record<string, number> = {};
55
+ for (const rowEntry of typeRows) {
56
+ session_type_distribution[rowEntry.session_type] = rowEntry.count;
57
+ }
58
+
59
+ return {
60
+ avg_files_changed: row?.avg_files_changed ?? 0,
61
+ total_lines_added: row?.total_lines_added ?? 0,
62
+ total_lines_removed: row?.total_lines_removed ?? 0,
63
+ total_cost_usd: row?.total_cost_usd ?? 0,
64
+ avg_cost_usd: row?.avg_cost_usd ?? 0,
65
+ cached_input_tokens_total: row?.cached_input_tokens_total ?? 0,
66
+ reasoning_output_tokens_total: row?.reasoning_output_tokens_total ?? 0,
67
+ artifact_count: row?.artifact_count ?? 0,
68
+ session_type_distribution,
69
+ };
70
+ }
71
+
72
+ export function getSessionCommits(db: Database, sessionId: string): CommitRecord[] {
73
+ return db
74
+ .query(
75
+ `SELECT commit_sha, commit_title, branch, repo_remote, timestamp
76
+ FROM commit_tracking
77
+ WHERE session_id = ?
78
+ ORDER BY timestamp DESC`,
79
+ )
80
+ .all(sessionId) as CommitRecord[];
81
+ }
82
+
83
+ export function getSkillCommitSummary(db: Database, skillName: string): CommitSummary {
84
+ const empty: CommitSummary = {
85
+ total_commits: 0,
86
+ unique_branches: 0,
87
+ recent_commits: [],
88
+ };
89
+
90
+ const statsRow = db
91
+ .query(
92
+ `WITH skill_sessions AS (
93
+ SELECT DISTINCT session_id FROM skill_invocations WHERE skill_name = ?
94
+ )
95
+ SELECT
96
+ COUNT(*) AS total_commits,
97
+ COUNT(DISTINCT ct.branch) AS unique_branches
98
+ FROM commit_tracking ct
99
+ WHERE ct.session_id IN (SELECT session_id FROM skill_sessions)`,
100
+ )
101
+ .get(skillName) as { total_commits: number; unique_branches: number } | null;
102
+
103
+ if (!statsRow || statsRow.total_commits === 0) return empty;
104
+
105
+ const recentRows = db
106
+ .query(
107
+ `WITH skill_sessions AS (
108
+ SELECT DISTINCT session_id FROM skill_invocations WHERE skill_name = ?
109
+ )
110
+ SELECT ct.commit_sha, ct.commit_title, ct.branch, ct.timestamp
111
+ FROM commit_tracking ct
112
+ WHERE ct.session_id IN (SELECT session_id FROM skill_sessions)
113
+ ORDER BY ct.timestamp DESC
114
+ LIMIT 20`,
115
+ )
116
+ .all(skillName) as Array<{
117
+ commit_sha: string;
118
+ commit_title: string | null;
119
+ branch: string | null;
120
+ timestamp: string;
121
+ }>;
122
+
123
+ return {
124
+ total_commits: statsRow.total_commits,
125
+ unique_branches: statsRow.unique_branches,
126
+ recent_commits: recentRows.map((row) => ({
127
+ sha: row.commit_sha,
128
+ title: row.commit_title ?? "",
129
+ branch: row.branch ?? "",
130
+ timestamp: row.timestamp,
131
+ })),
132
+ };
133
+ }
@@ -0,0 +1,18 @@
1
+ export function safeParseJsonArray<T = string>(json: string | null): T[] {
2
+ if (!json) return [];
3
+ try {
4
+ const parsed = JSON.parse(json);
5
+ return Array.isArray(parsed) ? (parsed as T[]) : [];
6
+ } catch {
7
+ return [];
8
+ }
9
+ }
10
+
11
+ export function safeParseJson(json: string | null): Record<string, unknown> | null {
12
+ if (!json) return null;
13
+ try {
14
+ return JSON.parse(json);
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
@@ -0,0 +1,263 @@
1
+ import type { Database } from "bun:sqlite";
2
+
3
+ export function queryImprovementSignals(
4
+ db: Database,
5
+ consumedOnly?: boolean,
6
+ ): Array<{
7
+ timestamp: string;
8
+ session_id: string;
9
+ query: string;
10
+ signal_type: string;
11
+ mentioned_skill?: string;
12
+ consumed: boolean;
13
+ consumed_at?: string;
14
+ consumed_by_run?: string;
15
+ }> {
16
+ const where =
17
+ consumedOnly === undefined ? "" : consumedOnly ? " WHERE consumed = 1" : " WHERE consumed = 0";
18
+ const rows = db
19
+ .query(`SELECT * FROM improvement_signals${where} ORDER BY timestamp DESC`)
20
+ .all() as Array<Record<string, unknown>>;
21
+ return rows.map((row) => ({
22
+ timestamp: row.timestamp as string,
23
+ session_id: row.session_id as string,
24
+ query: row.query as string,
25
+ signal_type: row.signal_type as string,
26
+ mentioned_skill: row.mentioned_skill as string | undefined,
27
+ consumed: (row.consumed as number) === 1,
28
+ consumed_at: row.consumed_at as string | undefined,
29
+ consumed_by_run: row.consumed_by_run as string | undefined,
30
+ }));
31
+ }
32
+
33
+ export function queryGradingResults(db: Database): Array<{
34
+ grading_id: string;
35
+ session_id: string;
36
+ skill_name: string;
37
+ transcript_path: string | null;
38
+ graded_at: string;
39
+ pass_rate: number | null;
40
+ mean_score: number | null;
41
+ score_std_dev: number | null;
42
+ passed_count: number | null;
43
+ failed_count: number | null;
44
+ total_count: number | null;
45
+ expectations_json: string | null;
46
+ claims_json: string | null;
47
+ eval_feedback_json: string | null;
48
+ failure_feedback_json: string | null;
49
+ execution_metrics_json: string | null;
50
+ }> {
51
+ return db
52
+ .query(
53
+ `SELECT grading_id, session_id, skill_name, transcript_path, graded_at,
54
+ pass_rate, mean_score, score_std_dev, passed_count, failed_count, total_count,
55
+ expectations_json, claims_json, eval_feedback_json, failure_feedback_json,
56
+ execution_metrics_json
57
+ FROM grading_results
58
+ ORDER BY graded_at DESC`,
59
+ )
60
+ .all() as Array<{
61
+ grading_id: string;
62
+ session_id: string;
63
+ skill_name: string;
64
+ transcript_path: string | null;
65
+ graded_at: string;
66
+ pass_rate: number | null;
67
+ mean_score: number | null;
68
+ score_std_dev: number | null;
69
+ passed_count: number | null;
70
+ failed_count: number | null;
71
+ total_count: number | null;
72
+ expectations_json: string | null;
73
+ claims_json: string | null;
74
+ eval_feedback_json: string | null;
75
+ failure_feedback_json: string | null;
76
+ execution_metrics_json: string | null;
77
+ }>;
78
+ }
79
+
80
+ export function queryReplayEntryResults(
81
+ db: Database,
82
+ proposalId: string,
83
+ phase?: string,
84
+ ): Array<{
85
+ id: number;
86
+ proposal_id: string;
87
+ skill_name: string;
88
+ validation_mode: string;
89
+ phase: string;
90
+ query: string;
91
+ should_trigger: boolean;
92
+ triggered: boolean;
93
+ passed: boolean;
94
+ evidence: string | null;
95
+ }> {
96
+ const sql = phase
97
+ ? `SELECT id, proposal_id, skill_name, validation_mode, phase, query,
98
+ should_trigger, triggered, passed, evidence
99
+ FROM replay_entry_results
100
+ WHERE proposal_id = ? AND phase = ?
101
+ ORDER BY id`
102
+ : `SELECT id, proposal_id, skill_name, validation_mode, phase, query,
103
+ should_trigger, triggered, passed, evidence
104
+ FROM replay_entry_results
105
+ WHERE proposal_id = ?
106
+ ORDER BY id`;
107
+
108
+ const rows = phase
109
+ ? (db.query(sql).all(proposalId, phase) as Array<Record<string, unknown>>)
110
+ : (db.query(sql).all(proposalId) as Array<Record<string, unknown>>);
111
+
112
+ return rows.map((row) => ({
113
+ id: row.id as number,
114
+ proposal_id: row.proposal_id as string,
115
+ skill_name: row.skill_name as string,
116
+ validation_mode: row.validation_mode as string,
117
+ phase: row.phase as string,
118
+ query: row.query as string,
119
+ should_trigger: (row.should_trigger as number) === 1,
120
+ triggered: (row.triggered as number) === 1,
121
+ passed: (row.passed as number) === 1,
122
+ evidence: row.evidence as string | null,
123
+ }));
124
+ }
125
+
126
+ export function queryReplayRegressions(
127
+ db: Database,
128
+ proposalId: string,
129
+ ): Array<{
130
+ query: string;
131
+ skill_name: string;
132
+ before_passed: boolean;
133
+ after_passed: boolean;
134
+ }> {
135
+ const rows = db
136
+ .query(
137
+ `SELECT b.query, b.skill_name,
138
+ b.passed AS before_passed,
139
+ a.passed AS after_passed
140
+ FROM replay_entry_results b
141
+ JOIN replay_entry_results a
142
+ ON b.proposal_id = a.proposal_id
143
+ AND b.query = a.query
144
+ AND b.skill_name = a.skill_name
145
+ WHERE b.proposal_id = ?
146
+ AND b.phase = 'before'
147
+ AND a.phase = 'after'
148
+ AND b.passed = 1
149
+ AND a.passed = 0
150
+ ORDER BY b.query`,
151
+ )
152
+ .all(proposalId) as Array<Record<string, unknown>>;
153
+
154
+ return rows.map((row) => ({
155
+ query: row.query as string,
156
+ skill_name: row.skill_name as string,
157
+ before_passed: (row.before_passed as number) === 1,
158
+ after_passed: (row.after_passed as number) === 1,
159
+ }));
160
+ }
161
+
162
+ export interface GradingBaselineRow {
163
+ id: number;
164
+ skill_name: string;
165
+ proposal_id: string | null;
166
+ measured_at: string;
167
+ pass_rate: number;
168
+ mean_score: number | null;
169
+ sample_size: number;
170
+ grading_results_json: string | null;
171
+ }
172
+
173
+ export interface GradeRegressionResult {
174
+ before: GradingBaselineRow;
175
+ after: GradingBaselineRow;
176
+ delta_pass_rate: number;
177
+ delta_mean_score: number | null;
178
+ regressed: boolean;
179
+ }
180
+
181
+ export function queryGradingBaseline(
182
+ db: Database,
183
+ skillName: string,
184
+ proposalId?: string,
185
+ ): GradingBaselineRow | null {
186
+ if (proposalId !== undefined) {
187
+ return (
188
+ (db
189
+ .query(
190
+ `SELECT * FROM grading_baselines
191
+ WHERE skill_name = ? AND proposal_id = ?
192
+ ORDER BY measured_at DESC
193
+ LIMIT 1`,
194
+ )
195
+ .get(skillName, proposalId) as GradingBaselineRow | null) ?? null
196
+ );
197
+ }
198
+
199
+ return (
200
+ (db
201
+ .query(
202
+ `SELECT * FROM grading_baselines
203
+ WHERE skill_name = ? AND proposal_id IS NULL
204
+ ORDER BY measured_at DESC
205
+ LIMIT 1`,
206
+ )
207
+ .get(skillName) as GradingBaselineRow | null) ?? null
208
+ );
209
+ }
210
+
211
+ export function queryGradeRegression(
212
+ db: Database,
213
+ skillName: string,
214
+ afterProposalId: string,
215
+ beforeProposalId?: string,
216
+ ): GradeRegressionResult | null {
217
+ const before = queryGradingBaseline(db, skillName, beforeProposalId);
218
+ const after = queryGradingBaseline(db, skillName, afterProposalId);
219
+ if (!before || !after) return null;
220
+
221
+ const deltaPR = after.pass_rate - before.pass_rate;
222
+ const deltaMS =
223
+ after.mean_score != null && before.mean_score != null
224
+ ? after.mean_score - before.mean_score
225
+ : null;
226
+
227
+ return {
228
+ before,
229
+ after,
230
+ delta_pass_rate: deltaPR,
231
+ delta_mean_score: deltaMS,
232
+ regressed: deltaPR < 0,
233
+ };
234
+ }
235
+
236
+ export interface RecentGradingResultRow {
237
+ grading_id: string;
238
+ session_id: string;
239
+ skill_name: string;
240
+ graded_at: string;
241
+ pass_rate: number | null;
242
+ mean_score: number | null;
243
+ total_count: number | null;
244
+ passed_count: number | null;
245
+ failed_count: number | null;
246
+ }
247
+
248
+ export function queryRecentGradingResults(
249
+ db: Database,
250
+ skillName: string,
251
+ limit: number = 20,
252
+ ): RecentGradingResultRow[] {
253
+ return db
254
+ .query(
255
+ `SELECT grading_id, session_id, skill_name, graded_at,
256
+ pass_rate, mean_score, total_count, passed_count, failed_count
257
+ FROM grading_results
258
+ WHERE skill_name = ?
259
+ ORDER BY graded_at DESC
260
+ LIMIT ?`,
261
+ )
262
+ .all(skillName, limit) as RecentGradingResultRow[];
263
+ }
@@ -0,0 +1,95 @@
1
+ import type { Database } from "bun:sqlite";
2
+
3
+ import type { SkillUsageRecord } from "../../types.js";
4
+ import { safeParseJson, safeParseJsonArray } from "./json.js";
5
+
6
+ export function querySessionTelemetry(
7
+ db: Database,
8
+ limit?: number,
9
+ ): Array<{
10
+ timestamp: string;
11
+ session_id: string;
12
+ cwd: string;
13
+ transcript_path: string;
14
+ tool_calls: Record<string, number>;
15
+ total_tool_calls: number;
16
+ bash_commands: string[];
17
+ skills_triggered: string[];
18
+ skills_invoked?: string[];
19
+ assistant_turns: number;
20
+ errors_encountered: number;
21
+ transcript_chars: number;
22
+ last_user_query: string;
23
+ source?: string;
24
+ input_tokens?: number;
25
+ output_tokens?: number;
26
+ }> {
27
+ const sql =
28
+ limit != null
29
+ ? `SELECT * FROM session_telemetry ORDER BY timestamp DESC LIMIT ${limit}`
30
+ : `SELECT * FROM session_telemetry ORDER BY timestamp DESC`;
31
+ const rows = db.query(sql).all() as Array<Record<string, unknown>>;
32
+ return rows.map((row) => ({
33
+ timestamp: row.timestamp as string,
34
+ session_id: row.session_id as string,
35
+ cwd: row.cwd as string,
36
+ transcript_path: row.transcript_path as string,
37
+ tool_calls: (safeParseJson(row.tool_calls_json as string) as Record<string, number>) ?? {},
38
+ total_tool_calls: row.total_tool_calls as number,
39
+ bash_commands: safeParseJsonArray<string>(row.bash_commands_json as string),
40
+ skills_triggered: safeParseJsonArray<string>(row.skills_triggered_json as string),
41
+ skills_invoked: row.skills_invoked_json
42
+ ? safeParseJsonArray<string>(row.skills_invoked_json as string)
43
+ : undefined,
44
+ assistant_turns: row.assistant_turns as number,
45
+ errors_encountered: row.errors_encountered as number,
46
+ transcript_chars: (row.transcript_chars as number) ?? 0,
47
+ last_user_query: (row.last_user_query as string) ?? "",
48
+ source: row.source as string | undefined,
49
+ input_tokens: row.input_tokens as number | undefined,
50
+ output_tokens: row.output_tokens as number | undefined,
51
+ }));
52
+ }
53
+
54
+ export function querySkillRecords(db: Database, limit?: number): SkillUsageRecord[] {
55
+ const sql =
56
+ limit != null
57
+ ? `SELECT occurred_at, session_id, skill_name, skill_path, skill_scope, query, triggered, source
58
+ FROM skill_invocations ORDER BY occurred_at DESC LIMIT ${limit}`
59
+ : `SELECT occurred_at, session_id, skill_name, skill_path, skill_scope, query, triggered, source
60
+ FROM skill_invocations ORDER BY occurred_at DESC`;
61
+ const rows = db.query(sql).all() as Array<Record<string, unknown>>;
62
+ return rows.map((row) => ({
63
+ timestamp: row.occurred_at as string,
64
+ session_id: row.session_id as string,
65
+ skill_name: row.skill_name as string,
66
+ skill_path: row.skill_path as string,
67
+ skill_scope: row.skill_scope as SkillUsageRecord["skill_scope"],
68
+ query: row.query as string,
69
+ triggered: (row.triggered as number) === 1,
70
+ source: row.source as string | undefined,
71
+ }));
72
+ }
73
+
74
+ export const querySkillUsageRecords = querySkillRecords;
75
+
76
+ export function queryQueryLog(
77
+ db: Database,
78
+ limit?: number,
79
+ ): Array<{
80
+ timestamp: string;
81
+ session_id: string;
82
+ query: string;
83
+ source?: string;
84
+ }> {
85
+ const sql =
86
+ limit != null
87
+ ? `SELECT timestamp, session_id, query, source FROM queries ORDER BY timestamp DESC LIMIT ${limit}`
88
+ : `SELECT timestamp, session_id, query, source FROM queries ORDER BY timestamp DESC`;
89
+ return db.query(sql).all() as Array<{
90
+ timestamp: string;
91
+ session_id: string;
92
+ query: string;
93
+ source?: string;
94
+ }>;
95
+ }