selftune 0.1.4 → 0.2.1

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 (153) hide show
  1. package/.claude/agents/diagnosis-analyst.md +156 -0
  2. package/.claude/agents/evolution-reviewer.md +180 -0
  3. package/.claude/agents/integration-guide.md +212 -0
  4. package/.claude/agents/pattern-analyst.md +160 -0
  5. package/CHANGELOG.md +46 -1
  6. package/README.md +105 -257
  7. package/apps/local-dashboard/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  8. package/apps/local-dashboard/dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  9. package/apps/local-dashboard/dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  10. package/apps/local-dashboard/dist/assets/index-C4EOTFZ2.js +15 -0
  11. package/apps/local-dashboard/dist/assets/index-bl-Webyd.css +1 -0
  12. package/apps/local-dashboard/dist/assets/vendor-react-U7zYD9Rg.js +60 -0
  13. package/apps/local-dashboard/dist/assets/vendor-table-B7VF2Ipl.js +26 -0
  14. package/apps/local-dashboard/dist/assets/vendor-ui-D7_zX_qy.js +346 -0
  15. package/apps/local-dashboard/dist/favicon.png +0 -0
  16. package/apps/local-dashboard/dist/index.html +17 -0
  17. package/apps/local-dashboard/dist/logo.png +0 -0
  18. package/apps/local-dashboard/dist/logo.svg +9 -0
  19. package/assets/BeforeAfter.gif +0 -0
  20. package/assets/FeedbackLoop.gif +0 -0
  21. package/assets/logo.svg +9 -0
  22. package/assets/skill-health-badge.svg +20 -0
  23. package/cli/selftune/activation-rules.ts +171 -0
  24. package/cli/selftune/badge/badge-data.ts +108 -0
  25. package/cli/selftune/badge/badge-svg.ts +212 -0
  26. package/cli/selftune/badge/badge.ts +99 -0
  27. package/cli/selftune/canonical-export.ts +183 -0
  28. package/cli/selftune/constants.ts +103 -1
  29. package/cli/selftune/contribute/bundle.ts +314 -0
  30. package/cli/selftune/contribute/contribute.ts +214 -0
  31. package/cli/selftune/contribute/sanitize.ts +162 -0
  32. package/cli/selftune/cron/setup.ts +266 -0
  33. package/cli/selftune/dashboard-contract.ts +202 -0
  34. package/cli/selftune/dashboard-server.ts +1049 -0
  35. package/cli/selftune/dashboard.ts +43 -156
  36. package/cli/selftune/eval/baseline.ts +248 -0
  37. package/cli/selftune/eval/composability-v2.ts +273 -0
  38. package/cli/selftune/eval/composability.ts +117 -0
  39. package/cli/selftune/eval/generate-unit-tests.ts +143 -0
  40. package/cli/selftune/eval/hooks-to-evals.ts +101 -16
  41. package/cli/selftune/eval/import-skillsbench.ts +221 -0
  42. package/cli/selftune/eval/synthetic-evals.ts +172 -0
  43. package/cli/selftune/eval/unit-test-cli.ts +152 -0
  44. package/cli/selftune/eval/unit-test.ts +196 -0
  45. package/cli/selftune/evolution/deploy-proposal.ts +142 -1
  46. package/cli/selftune/evolution/evidence.ts +26 -0
  47. package/cli/selftune/evolution/evolve-body.ts +586 -0
  48. package/cli/selftune/evolution/evolve.ts +825 -116
  49. package/cli/selftune/evolution/extract-patterns.ts +105 -16
  50. package/cli/selftune/evolution/pareto.ts +314 -0
  51. package/cli/selftune/evolution/propose-body.ts +171 -0
  52. package/cli/selftune/evolution/propose-description.ts +100 -2
  53. package/cli/selftune/evolution/propose-routing.ts +166 -0
  54. package/cli/selftune/evolution/refine-body.ts +141 -0
  55. package/cli/selftune/evolution/rollback.ts +21 -4
  56. package/cli/selftune/evolution/validate-body.ts +254 -0
  57. package/cli/selftune/evolution/validate-proposal.ts +257 -35
  58. package/cli/selftune/evolution/validate-routing.ts +177 -0
  59. package/cli/selftune/grading/auto-grade.ts +200 -0
  60. package/cli/selftune/grading/grade-session.ts +513 -42
  61. package/cli/selftune/grading/pre-gates.ts +104 -0
  62. package/cli/selftune/grading/results.ts +42 -0
  63. package/cli/selftune/hooks/auto-activate.ts +185 -0
  64. package/cli/selftune/hooks/evolution-guard.ts +165 -0
  65. package/cli/selftune/hooks/prompt-log.ts +172 -2
  66. package/cli/selftune/hooks/session-stop.ts +123 -3
  67. package/cli/selftune/hooks/skill-change-guard.ts +112 -0
  68. package/cli/selftune/hooks/skill-eval.ts +119 -3
  69. package/cli/selftune/index.ts +415 -48
  70. package/cli/selftune/ingestors/claude-replay.ts +377 -0
  71. package/cli/selftune/ingestors/codex-rollout.ts +345 -46
  72. package/cli/selftune/ingestors/codex-wrapper.ts +207 -39
  73. package/cli/selftune/ingestors/openclaw-ingest.ts +573 -0
  74. package/cli/selftune/ingestors/opencode-ingest.ts +193 -17
  75. package/cli/selftune/init.ts +376 -16
  76. package/cli/selftune/last.ts +14 -5
  77. package/cli/selftune/localdb/db.ts +63 -0
  78. package/cli/selftune/localdb/materialize.ts +428 -0
  79. package/cli/selftune/localdb/queries.ts +376 -0
  80. package/cli/selftune/localdb/schema.ts +204 -0
  81. package/cli/selftune/memory/writer.ts +447 -0
  82. package/cli/selftune/monitoring/watch.ts +90 -16
  83. package/cli/selftune/normalization.ts +682 -0
  84. package/cli/selftune/observability.ts +19 -44
  85. package/cli/selftune/orchestrate.ts +1073 -0
  86. package/cli/selftune/quickstart.ts +203 -0
  87. package/cli/selftune/repair/skill-usage.ts +576 -0
  88. package/cli/selftune/schedule.ts +561 -0
  89. package/cli/selftune/status.ts +59 -33
  90. package/cli/selftune/sync.ts +627 -0
  91. package/cli/selftune/types.ts +525 -5
  92. package/cli/selftune/utils/canonical-log.ts +45 -0
  93. package/cli/selftune/utils/frontmatter.ts +217 -0
  94. package/cli/selftune/utils/hooks.ts +41 -0
  95. package/cli/selftune/utils/html.ts +27 -0
  96. package/cli/selftune/utils/llm-call.ts +103 -19
  97. package/cli/selftune/utils/math.ts +10 -0
  98. package/cli/selftune/utils/query-filter.ts +139 -0
  99. package/cli/selftune/utils/skill-discovery.ts +340 -0
  100. package/cli/selftune/utils/skill-log.ts +68 -0
  101. package/cli/selftune/utils/skill-usage-confidence.ts +18 -0
  102. package/cli/selftune/utils/transcript.ts +307 -26
  103. package/cli/selftune/utils/trigger-check.ts +89 -0
  104. package/cli/selftune/utils/tui.ts +156 -0
  105. package/cli/selftune/workflows/discover.ts +254 -0
  106. package/cli/selftune/workflows/skill-md-writer.ts +288 -0
  107. package/cli/selftune/workflows/workflows.ts +188 -0
  108. package/package.json +28 -11
  109. package/packages/telemetry-contract/README.md +11 -0
  110. package/packages/telemetry-contract/fixtures/golden.json +87 -0
  111. package/packages/telemetry-contract/fixtures/golden.test.ts +42 -0
  112. package/packages/telemetry-contract/index.ts +1 -0
  113. package/packages/telemetry-contract/package.json +19 -0
  114. package/packages/telemetry-contract/src/index.ts +2 -0
  115. package/packages/telemetry-contract/src/types.ts +163 -0
  116. package/packages/telemetry-contract/src/validators.ts +109 -0
  117. package/skill/SKILL.md +180 -33
  118. package/skill/Workflows/AutoActivation.md +145 -0
  119. package/skill/Workflows/Badge.md +124 -0
  120. package/skill/Workflows/Baseline.md +144 -0
  121. package/skill/Workflows/Composability.md +107 -0
  122. package/skill/Workflows/Contribute.md +94 -0
  123. package/skill/Workflows/Cron.md +132 -0
  124. package/skill/Workflows/Dashboard.md +214 -0
  125. package/skill/Workflows/Doctor.md +63 -14
  126. package/skill/Workflows/Evals.md +110 -18
  127. package/skill/Workflows/EvolutionMemory.md +154 -0
  128. package/skill/Workflows/Evolve.md +181 -21
  129. package/skill/Workflows/EvolveBody.md +159 -0
  130. package/skill/Workflows/Grade.md +36 -31
  131. package/skill/Workflows/ImportSkillsBench.md +117 -0
  132. package/skill/Workflows/Ingest.md +142 -21
  133. package/skill/Workflows/Initialize.md +91 -23
  134. package/skill/Workflows/Orchestrate.md +139 -0
  135. package/skill/Workflows/Replay.md +91 -0
  136. package/skill/Workflows/Rollback.md +23 -4
  137. package/skill/Workflows/Schedule.md +61 -0
  138. package/skill/Workflows/Sync.md +88 -0
  139. package/skill/Workflows/UnitTest.md +150 -0
  140. package/skill/Workflows/Watch.md +33 -1
  141. package/skill/Workflows/Workflows.md +129 -0
  142. package/skill/assets/activation-rules-default.json +26 -0
  143. package/skill/assets/multi-skill-settings.json +63 -0
  144. package/skill/assets/single-skill-settings.json +57 -0
  145. package/skill/references/invocation-taxonomy.md +2 -2
  146. package/skill/references/logs.md +164 -2
  147. package/skill/references/setup-patterns.md +65 -0
  148. package/skill/references/version-history.md +40 -0
  149. package/skill/settings_snippet.json +23 -0
  150. package/templates/activation-rules-default.json +27 -0
  151. package/templates/multi-skill-settings.json +64 -0
  152. package/templates/single-skill-settings.json +58 -0
  153. package/dashboard/index.html +0 -1119
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Query helpers for the selftune local SQLite materialized view store.
3
+ *
4
+ * These return payload shapes that match what the dashboard and report
5
+ * pages need, so the HTTP layer can serve them directly.
6
+ */
7
+
8
+ import type { Database } from "bun:sqlite";
9
+ import type {
10
+ OrchestrateRunReport,
11
+ OverviewPayload,
12
+ PendingProposal,
13
+ SkillReportPayload,
14
+ SkillSummary,
15
+ } from "../dashboard-contract.js";
16
+
17
+ /**
18
+ * Build the overview payload from SQLite, suitable for the dashboard main page.
19
+ */
20
+ export function getOverviewPayload(db: Database): OverviewPayload {
21
+ // Telemetry summary (bounded to most recent 1000)
22
+ const telemetryRows = db
23
+ .query(
24
+ `SELECT timestamp, session_id, skills_triggered_json, errors_encountered, total_tool_calls
25
+ FROM session_telemetry
26
+ ORDER BY timestamp DESC
27
+ LIMIT 1000`,
28
+ )
29
+ .all() as Array<{
30
+ timestamp: string;
31
+ session_id: string;
32
+ skills_triggered_json: string | null;
33
+ errors_encountered: number;
34
+ total_tool_calls: number;
35
+ }>;
36
+
37
+ const telemetry = telemetryRows.map((row) => ({
38
+ timestamp: row.timestamp,
39
+ session_id: row.session_id,
40
+ skills_triggered: safeParseJsonArray<string>(row.skills_triggered_json),
41
+ errors_encountered: row.errors_encountered,
42
+ total_tool_calls: row.total_tool_calls,
43
+ }));
44
+
45
+ // Skill usage (bounded to most recent 2000)
46
+ const skillRows = db
47
+ .query(
48
+ `SELECT timestamp, session_id, skill_name, skill_path, query, triggered, source
49
+ FROM skill_usage
50
+ ORDER BY timestamp DESC
51
+ LIMIT 2000`,
52
+ )
53
+ .all() as Array<{
54
+ timestamp: string;
55
+ session_id: string;
56
+ skill_name: string;
57
+ skill_path: string;
58
+ query: string;
59
+ triggered: number;
60
+ source: string | null;
61
+ }>;
62
+
63
+ const skills = skillRows.map((row) => ({
64
+ timestamp: row.timestamp,
65
+ session_id: row.session_id,
66
+ skill_name: row.skill_name,
67
+ skill_path: row.skill_path,
68
+ query: row.query,
69
+ triggered: row.triggered === 1,
70
+ source: row.source,
71
+ }));
72
+
73
+ // Evolution audit (bounded to most recent 500)
74
+ const evolution = db
75
+ .query(
76
+ `SELECT timestamp, proposal_id, action, details
77
+ FROM evolution_audit
78
+ ORDER BY timestamp DESC
79
+ LIMIT 500`,
80
+ )
81
+ .all() as Array<{
82
+ timestamp: string;
83
+ proposal_id: string;
84
+ action: string;
85
+ details: string;
86
+ }>;
87
+
88
+ // Counts (single query instead of 6 separate ones)
89
+ const counts = db
90
+ .query(
91
+ `SELECT
92
+ (SELECT COUNT(*) FROM session_telemetry) as telemetry,
93
+ (SELECT COUNT(*) FROM skill_usage) as skills,
94
+ (SELECT COUNT(*) FROM evolution_audit) as evolution,
95
+ (SELECT COUNT(*) FROM evolution_evidence) as evidence,
96
+ (SELECT COUNT(*) FROM sessions) as sessions,
97
+ (SELECT COUNT(*) FROM prompts) as prompts`,
98
+ )
99
+ .get() as {
100
+ telemetry: number;
101
+ skills: number;
102
+ evolution: number;
103
+ evidence: number;
104
+ sessions: number;
105
+ prompts: number;
106
+ };
107
+
108
+ // Unmatched queries: skill_usage entries where triggered = 0 and no other
109
+ // record for the same query text triggered
110
+ const unmatchedRows = db
111
+ .query(
112
+ `SELECT su.timestamp, su.session_id, su.query
113
+ FROM skill_usage su
114
+ WHERE su.triggered = 0
115
+ AND NOT EXISTS (
116
+ SELECT 1 FROM skill_usage su2
117
+ WHERE su2.query = su.query AND su2.triggered = 1
118
+ )
119
+ ORDER BY su.timestamp DESC
120
+ LIMIT 500`,
121
+ )
122
+ .all() as Array<{ timestamp: string; session_id: string; query: string }>;
123
+
124
+ // Pending proposals: created/validated but no terminal action (deduped in SQL)
125
+ const pending_proposals = getPendingProposals(db);
126
+
127
+ return {
128
+ telemetry,
129
+ skills,
130
+ evolution,
131
+ counts,
132
+ unmatched_queries: unmatchedRows,
133
+ pending_proposals,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Build the skill report payload for a specific skill.
139
+ */
140
+ export function getSkillReportPayload(db: Database, skillName: string): SkillReportPayload {
141
+ // Usage stats
142
+ const usageRow = db
143
+ .query(
144
+ `SELECT
145
+ COUNT(*) as total_checks,
146
+ SUM(CASE WHEN triggered = 1 THEN 1 ELSE 0 END) as triggered_count
147
+ FROM skill_usage
148
+ WHERE skill_name = ?`,
149
+ )
150
+ .get(skillName) as { total_checks: number; triggered_count: number };
151
+
152
+ const total = usageRow.total_checks;
153
+ const triggered = usageRow.triggered_count;
154
+ const passRate = total > 0 ? triggered / total : 0;
155
+
156
+ // Recent invocations (last 100)
157
+ const invocationRows = db
158
+ .query(
159
+ `SELECT timestamp, session_id, query, triggered, source
160
+ FROM skill_usage
161
+ WHERE skill_name = ?
162
+ ORDER BY timestamp DESC
163
+ LIMIT 100`,
164
+ )
165
+ .all(skillName) as Array<{
166
+ timestamp: string;
167
+ session_id: string;
168
+ query: string;
169
+ triggered: number;
170
+ source: string | null;
171
+ }>;
172
+
173
+ const recent_invocations = invocationRows.map((row) => ({
174
+ timestamp: row.timestamp,
175
+ session_id: row.session_id,
176
+ query: row.query,
177
+ triggered: row.triggered === 1,
178
+ source: row.source,
179
+ }));
180
+
181
+ // Evolution evidence (bounded to most recent 200)
182
+ const evidenceRows = db
183
+ .query(
184
+ `SELECT proposal_id, target, stage, timestamp, rationale, confidence,
185
+ original_text, proposed_text, validation_json, details, eval_set_json
186
+ FROM evolution_evidence
187
+ WHERE skill_name = ?
188
+ ORDER BY timestamp DESC
189
+ LIMIT 200`,
190
+ )
191
+ .all(skillName) as Array<{
192
+ proposal_id: string;
193
+ target: string;
194
+ stage: string;
195
+ timestamp: string;
196
+ rationale: string | null;
197
+ confidence: number | null;
198
+ original_text: string | null;
199
+ proposed_text: string | null;
200
+ validation_json: string | null;
201
+ details: string | null;
202
+ eval_set_json: string | null;
203
+ }>;
204
+
205
+ const evidence = evidenceRows.map((row) => ({
206
+ proposal_id: row.proposal_id,
207
+ target: row.target,
208
+ stage: row.stage,
209
+ timestamp: row.timestamp,
210
+ rationale: row.rationale,
211
+ confidence: row.confidence,
212
+ original_text: row.original_text,
213
+ proposed_text: row.proposed_text,
214
+ validation: safeParseJson(row.validation_json),
215
+ details: row.details,
216
+ eval_set: safeParseJsonArray<Record<string, unknown>>(row.eval_set_json),
217
+ }));
218
+
219
+ // Unique sessions count
220
+ const sessionsRow = db
221
+ .query(`SELECT COUNT(DISTINCT session_id) as c FROM skill_usage WHERE skill_name = ?`)
222
+ .get(skillName) as { c: number };
223
+
224
+ return {
225
+ skill_name: skillName,
226
+ usage: {
227
+ total_checks: total,
228
+ triggered_count: triggered,
229
+ pass_rate: passRate,
230
+ },
231
+ recent_invocations,
232
+ evidence,
233
+ sessions_with_skill: sessionsRow.c,
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Get a summary list of all skills with aggregated stats.
239
+ */
240
+ export function getSkillsList(db: Database): SkillSummary[] {
241
+ const rows = db
242
+ .query(
243
+ `SELECT
244
+ su.skill_name,
245
+ (SELECT s2.skill_scope FROM skill_usage s2
246
+ WHERE s2.skill_name = su.skill_name AND s2.skill_scope IS NOT NULL
247
+ ORDER BY s2.timestamp DESC LIMIT 1) as skill_scope,
248
+ COUNT(*) as total_checks,
249
+ SUM(CASE WHEN su.triggered = 1 THEN 1 ELSE 0 END) as triggered_count,
250
+ COUNT(DISTINCT su.session_id) as unique_sessions,
251
+ MAX(su.timestamp) as last_seen
252
+ FROM skill_usage su
253
+ GROUP BY su.skill_name
254
+ ORDER BY total_checks DESC`,
255
+ )
256
+ .all() as Array<{
257
+ skill_name: string;
258
+ skill_scope: string | null;
259
+ total_checks: number;
260
+ triggered_count: number;
261
+ unique_sessions: number;
262
+ last_seen: string | null;
263
+ }>;
264
+
265
+ // Get set of skill names with evidence
266
+ const evidenceSkills = new Set(
267
+ (
268
+ db.query(`SELECT DISTINCT skill_name FROM evolution_evidence`).all() as Array<{
269
+ skill_name: string;
270
+ }>
271
+ ).map((r) => r.skill_name),
272
+ );
273
+
274
+ return rows.map((row) => ({
275
+ skill_name: row.skill_name,
276
+ skill_scope: row.skill_scope,
277
+ total_checks: row.total_checks,
278
+ triggered_count: row.triggered_count,
279
+ pass_rate: row.total_checks > 0 ? row.triggered_count / row.total_checks : 0,
280
+ unique_sessions: row.unique_sessions,
281
+ last_seen: row.last_seen,
282
+ has_evidence: evidenceSkills.has(row.skill_name),
283
+ }));
284
+ }
285
+
286
+ /**
287
+ * Get pending proposals (created/validated with no terminal action).
288
+ * Optionally filtered by skill_name.
289
+ */
290
+ export function getPendingProposals(db: Database, skillName?: string): PendingProposal[] {
291
+ const whereClause = skillName ? "WHERE ea.skill_name = ? AND" : "WHERE";
292
+ const params = skillName ? [skillName] : [];
293
+ return db
294
+ .query(
295
+ `WITH latest AS (
296
+ SELECT ea.proposal_id, ea.action, ea.timestamp, ea.details, ea.skill_name,
297
+ ROW_NUMBER() OVER (PARTITION BY ea.proposal_id ORDER BY ea.timestamp DESC, ea.id DESC) AS rn
298
+ FROM evolution_audit ea
299
+ LEFT JOIN evolution_audit ea2
300
+ ON ea2.proposal_id = ea.proposal_id
301
+ AND ea2.action IN ('deployed', 'rejected', 'rolled_back')
302
+ ${whereClause} ea.action IN ('created', 'validated')
303
+ AND ea2.id IS NULL
304
+ )
305
+ SELECT proposal_id, action, timestamp, details, skill_name
306
+ FROM latest
307
+ WHERE rn = 1
308
+ ORDER BY timestamp DESC`,
309
+ )
310
+ .all(...params) as PendingProposal[];
311
+ }
312
+
313
+ /**
314
+ * Get recent orchestrate run reports (most recent first).
315
+ */
316
+ export function getOrchestrateRuns(db: Database, limit = 20): OrchestrateRunReport[] {
317
+ const rows = db
318
+ .query(
319
+ `SELECT run_id, timestamp, elapsed_ms, dry_run, approval_mode,
320
+ total_skills, evaluated, evolved, deployed, watched, skipped,
321
+ skill_actions_json
322
+ FROM orchestrate_runs
323
+ ORDER BY timestamp DESC
324
+ LIMIT ?`,
325
+ )
326
+ .all(limit) as Array<{
327
+ run_id: string;
328
+ timestamp: string;
329
+ elapsed_ms: number;
330
+ dry_run: number;
331
+ approval_mode: string;
332
+ total_skills: number;
333
+ evaluated: number;
334
+ evolved: number;
335
+ deployed: number;
336
+ watched: number;
337
+ skipped: number;
338
+ skill_actions_json: string;
339
+ }>;
340
+
341
+ return rows.map((r) => ({
342
+ run_id: r.run_id,
343
+ timestamp: r.timestamp,
344
+ elapsed_ms: r.elapsed_ms,
345
+ dry_run: r.dry_run === 1,
346
+ approval_mode: r.approval_mode as "auto" | "review",
347
+ total_skills: r.total_skills,
348
+ evaluated: r.evaluated,
349
+ evolved: r.evolved,
350
+ deployed: r.deployed,
351
+ watched: r.watched,
352
+ skipped: r.skipped,
353
+ skill_actions: safeParseJsonArray(r.skill_actions_json),
354
+ }));
355
+ }
356
+
357
+ // -- Helpers ------------------------------------------------------------------
358
+
359
+ function safeParseJsonArray<T = string>(json: string | null): T[] {
360
+ if (!json) return [];
361
+ try {
362
+ const parsed = JSON.parse(json);
363
+ return Array.isArray(parsed) ? (parsed as T[]) : [];
364
+ } catch {
365
+ return [];
366
+ }
367
+ }
368
+
369
+ function safeParseJson(json: string | null): Record<string, unknown> | null {
370
+ if (!json) return null;
371
+ try {
372
+ return JSON.parse(json);
373
+ } catch {
374
+ return null;
375
+ }
376
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * SQLite schema definitions for the selftune local materialized view store.
3
+ *
4
+ * Tables mirror the canonical telemetry contract + local JSONL log shapes,
5
+ * providing indexed access for dashboard and report queries.
6
+ */
7
+
8
+ // -- Canonical telemetry tables -----------------------------------------------
9
+
10
+ export const CREATE_SESSIONS = `
11
+ CREATE TABLE IF NOT EXISTS sessions (
12
+ session_id TEXT PRIMARY KEY,
13
+ started_at TEXT,
14
+ ended_at TEXT,
15
+ platform TEXT,
16
+ model TEXT,
17
+ completion_status TEXT,
18
+ source_session_kind TEXT,
19
+ agent_cli TEXT,
20
+ workspace_path TEXT,
21
+ repo_remote TEXT,
22
+ branch TEXT,
23
+ schema_version TEXT,
24
+ normalized_at TEXT
25
+ )`;
26
+
27
+ export const CREATE_PROMPTS = `
28
+ CREATE TABLE IF NOT EXISTS prompts (
29
+ prompt_id TEXT PRIMARY KEY,
30
+ session_id TEXT NOT NULL,
31
+ occurred_at TEXT,
32
+ prompt_kind TEXT,
33
+ is_actionable INTEGER,
34
+ prompt_index INTEGER,
35
+ prompt_text TEXT,
36
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id)
37
+ )`;
38
+
39
+ export const CREATE_SKILL_INVOCATIONS = `
40
+ CREATE TABLE IF NOT EXISTS skill_invocations (
41
+ skill_invocation_id TEXT PRIMARY KEY,
42
+ session_id TEXT NOT NULL,
43
+ occurred_at TEXT,
44
+ skill_name TEXT NOT NULL,
45
+ invocation_mode TEXT,
46
+ triggered INTEGER,
47
+ confidence REAL,
48
+ tool_name TEXT,
49
+ matched_prompt_id TEXT,
50
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id)
51
+ )`;
52
+
53
+ export const CREATE_EXECUTION_FACTS = `
54
+ CREATE TABLE IF NOT EXISTS execution_facts (
55
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ session_id TEXT NOT NULL,
57
+ occurred_at TEXT,
58
+ prompt_id TEXT,
59
+ tool_calls_json TEXT,
60
+ total_tool_calls INTEGER,
61
+ assistant_turns INTEGER,
62
+ errors_encountered INTEGER,
63
+ input_tokens INTEGER,
64
+ output_tokens INTEGER,
65
+ duration_ms INTEGER,
66
+ completion_status TEXT,
67
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id)
68
+ )`;
69
+
70
+ // -- Evolution tables ---------------------------------------------------------
71
+
72
+ export const CREATE_EVOLUTION_EVIDENCE = `
73
+ CREATE TABLE IF NOT EXISTS evolution_evidence (
74
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
75
+ timestamp TEXT NOT NULL,
76
+ proposal_id TEXT NOT NULL,
77
+ skill_name TEXT NOT NULL,
78
+ skill_path TEXT,
79
+ target TEXT,
80
+ stage TEXT,
81
+ rationale TEXT,
82
+ confidence REAL,
83
+ details TEXT,
84
+ original_text TEXT,
85
+ proposed_text TEXT,
86
+ eval_set_json TEXT,
87
+ validation_json TEXT
88
+ )`;
89
+
90
+ export const CREATE_EVOLUTION_AUDIT = `
91
+ CREATE TABLE IF NOT EXISTS evolution_audit (
92
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
93
+ timestamp TEXT NOT NULL,
94
+ proposal_id TEXT NOT NULL,
95
+ skill_name TEXT,
96
+ action TEXT NOT NULL,
97
+ details TEXT,
98
+ eval_snapshot_json TEXT
99
+ )`;
100
+
101
+ // -- Local telemetry tables (from JSONL logs) ---------------------------------
102
+
103
+ export const CREATE_SESSION_TELEMETRY = `
104
+ CREATE TABLE IF NOT EXISTS session_telemetry (
105
+ session_id TEXT PRIMARY KEY,
106
+ timestamp TEXT NOT NULL,
107
+ cwd TEXT,
108
+ transcript_path TEXT,
109
+ tool_calls_json TEXT,
110
+ total_tool_calls INTEGER,
111
+ bash_commands_json TEXT,
112
+ skills_triggered_json TEXT,
113
+ skills_invoked_json TEXT,
114
+ assistant_turns INTEGER,
115
+ errors_encountered INTEGER,
116
+ transcript_chars INTEGER,
117
+ last_user_query TEXT,
118
+ source TEXT,
119
+ input_tokens INTEGER,
120
+ output_tokens INTEGER
121
+ )`;
122
+
123
+ export const CREATE_SKILL_USAGE = `
124
+ CREATE TABLE IF NOT EXISTS skill_usage (
125
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
126
+ timestamp TEXT NOT NULL,
127
+ session_id TEXT NOT NULL,
128
+ skill_name TEXT NOT NULL,
129
+ skill_path TEXT,
130
+ skill_scope TEXT,
131
+ query TEXT,
132
+ triggered INTEGER,
133
+ source TEXT
134
+ )`;
135
+
136
+ // -- Orchestrate run reports --------------------------------------------------
137
+
138
+ export const CREATE_ORCHESTRATE_RUNS = `
139
+ CREATE TABLE IF NOT EXISTS orchestrate_runs (
140
+ run_id TEXT PRIMARY KEY,
141
+ timestamp TEXT NOT NULL,
142
+ elapsed_ms INTEGER NOT NULL,
143
+ dry_run INTEGER NOT NULL,
144
+ approval_mode TEXT NOT NULL,
145
+ total_skills INTEGER NOT NULL,
146
+ evaluated INTEGER NOT NULL,
147
+ evolved INTEGER NOT NULL,
148
+ deployed INTEGER NOT NULL,
149
+ watched INTEGER NOT NULL,
150
+ skipped INTEGER NOT NULL,
151
+ skill_actions_json TEXT NOT NULL
152
+ )`;
153
+
154
+ // -- Metadata table -----------------------------------------------------------
155
+
156
+ export const CREATE_META = `
157
+ CREATE TABLE IF NOT EXISTS _meta (
158
+ key TEXT PRIMARY KEY,
159
+ value TEXT
160
+ )`;
161
+
162
+ // -- Indexes ------------------------------------------------------------------
163
+
164
+ export const CREATE_INDEXES = [
165
+ // -- Lookup indexes ---------------------------------------------------------
166
+ `CREATE INDEX IF NOT EXISTS idx_prompts_session ON prompts(session_id)`,
167
+ `CREATE INDEX IF NOT EXISTS idx_prompts_occurred ON prompts(occurred_at)`,
168
+ `CREATE INDEX IF NOT EXISTS idx_skill_inv_session ON skill_invocations(session_id)`,
169
+ `CREATE INDEX IF NOT EXISTS idx_skill_inv_name ON skill_invocations(skill_name)`,
170
+ `CREATE INDEX IF NOT EXISTS idx_exec_facts_session ON execution_facts(session_id)`,
171
+ `CREATE INDEX IF NOT EXISTS idx_evo_evidence_proposal ON evolution_evidence(proposal_id)`,
172
+ `CREATE INDEX IF NOT EXISTS idx_evo_evidence_skill ON evolution_evidence(skill_name)`,
173
+ `CREATE INDEX IF NOT EXISTS idx_evo_evidence_ts ON evolution_evidence(timestamp)`,
174
+ `CREATE INDEX IF NOT EXISTS idx_evo_audit_proposal ON evolution_audit(proposal_id)`,
175
+ `CREATE INDEX IF NOT EXISTS idx_evo_audit_ts ON evolution_audit(timestamp)`,
176
+ `CREATE INDEX IF NOT EXISTS idx_evo_audit_action ON evolution_audit(action)`,
177
+ `CREATE INDEX IF NOT EXISTS idx_session_tel_ts ON session_telemetry(timestamp)`,
178
+ `CREATE INDEX IF NOT EXISTS idx_skill_usage_session ON skill_usage(session_id)`,
179
+ `CREATE INDEX IF NOT EXISTS idx_skill_usage_name ON skill_usage(skill_name)`,
180
+ `CREATE INDEX IF NOT EXISTS idx_skill_usage_ts ON skill_usage(timestamp)`,
181
+ `CREATE INDEX IF NOT EXISTS idx_skill_usage_query_triggered ON skill_usage(query, triggered)`,
182
+ `CREATE INDEX IF NOT EXISTS idx_skill_usage_scope ON skill_usage(skill_name, skill_scope, timestamp)`,
183
+ // -- Dedup UNIQUE indexes (used by INSERT OR IGNORE in materializer) --------
184
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_skill_usage_dedup ON skill_usage(session_id, skill_name, query, timestamp, triggered)`,
185
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_evo_audit_dedup ON evolution_audit(proposal_id, action, timestamp)`,
186
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_evo_evidence_dedup ON evolution_evidence(proposal_id, stage, timestamp)`,
187
+ // -- Orchestrate run indexes -----------------------------------------------
188
+ `CREATE INDEX IF NOT EXISTS idx_orchestrate_runs_ts ON orchestrate_runs(timestamp)`,
189
+ ];
190
+
191
+ /** All DDL statements in creation order. */
192
+ export const ALL_DDL = [
193
+ CREATE_SESSIONS,
194
+ CREATE_PROMPTS,
195
+ CREATE_SKILL_INVOCATIONS,
196
+ CREATE_EXECUTION_FACTS,
197
+ CREATE_EVOLUTION_EVIDENCE,
198
+ CREATE_EVOLUTION_AUDIT,
199
+ CREATE_SESSION_TELEMETRY,
200
+ CREATE_SKILL_USAGE,
201
+ CREATE_ORCHESTRATE_RUNS,
202
+ CREATE_META,
203
+ ...CREATE_INDEXES,
204
+ ];