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,428 @@
1
+ /**
2
+ * Materializer: reads JSONL source-of-truth logs and inserts structured
3
+ * records into the local SQLite database.
4
+ *
5
+ * Supports two modes:
6
+ * - Full rebuild: drops all data and re-inserts from scratch
7
+ * - Incremental: only inserts records newer than last materialization
8
+ */
9
+
10
+ import type { Database } from "bun:sqlite";
11
+ import type {
12
+ CanonicalExecutionFactRecord,
13
+ CanonicalPromptRecord,
14
+ CanonicalRecord,
15
+ CanonicalSessionRecord,
16
+ CanonicalSkillInvocationRecord,
17
+ } from "@selftune/telemetry-contract";
18
+ import {
19
+ CANONICAL_LOG,
20
+ EVOLUTION_AUDIT_LOG,
21
+ EVOLUTION_EVIDENCE_LOG,
22
+ ORCHESTRATE_RUN_LOG,
23
+ TELEMETRY_LOG,
24
+ } from "../constants.js";
25
+ import type { OrchestrateRunReport } from "../dashboard-contract.js";
26
+ import { readEvidenceTrail } from "../evolution/evidence.js";
27
+ import type {
28
+ EvolutionAuditEntry,
29
+ EvolutionEvidenceEntry,
30
+ SessionTelemetryRecord,
31
+ SkillUsageRecord,
32
+ } from "../types.js";
33
+ import { readCanonicalRecords } from "../utils/canonical-log.js";
34
+ import { readJsonl } from "../utils/jsonl.js";
35
+ import { readEffectiveSkillUsageRecords } from "../utils/skill-log.js";
36
+ import { getMeta, setMeta } from "./db.js";
37
+
38
+ /** Meta key tracking last materialization timestamp. */
39
+ const META_LAST_MATERIALIZED = "last_materialized_at";
40
+
41
+ /**
42
+ * Full rebuild: drop all data tables, then re-insert everything.
43
+ */
44
+ export function materializeFull(db: Database, options?: MaterializeOptions): MaterializeResult {
45
+ const tables = [
46
+ "skill_usage",
47
+ "session_telemetry",
48
+ "evolution_audit",
49
+ "evolution_evidence",
50
+ "execution_facts",
51
+ "skill_invocations",
52
+ "prompts",
53
+ "sessions",
54
+ "orchestrate_runs",
55
+ ];
56
+ for (const table of tables) {
57
+ db.run(`DELETE FROM ${table}`);
58
+ }
59
+
60
+ return materializeIncremental(db, { ...options, since: null });
61
+ }
62
+
63
+ export interface MaterializeOptions {
64
+ canonicalLogPath?: string;
65
+ telemetryLogPath?: string;
66
+ evolutionAuditPath?: string;
67
+ evolutionEvidencePath?: string;
68
+ orchestrateRunLogPath?: string;
69
+ since?: string | null;
70
+ }
71
+
72
+ export interface MaterializeResult {
73
+ sessions: number;
74
+ prompts: number;
75
+ skillInvocations: number;
76
+ executionFacts: number;
77
+ sessionTelemetry: number;
78
+ skillUsage: number;
79
+ evolutionAudit: number;
80
+ evolutionEvidence: number;
81
+ orchestrateRuns: number;
82
+ }
83
+
84
+ /**
85
+ * Incremental materialization: only insert records newer than last run.
86
+ * Uses INSERT OR IGNORE for idempotency on primary-keyed tables,
87
+ * and UNIQUE indexes for deduplication on append-only tables.
88
+ */
89
+ export function materializeIncremental(
90
+ db: Database,
91
+ options?: MaterializeOptions,
92
+ ): MaterializeResult {
93
+ const since = options?.since !== undefined ? options.since : getMeta(db, META_LAST_MATERIALIZED);
94
+ const now = new Date().toISOString();
95
+
96
+ const result: MaterializeResult = {
97
+ sessions: 0,
98
+ prompts: 0,
99
+ skillInvocations: 0,
100
+ executionFacts: 0,
101
+ sessionTelemetry: 0,
102
+ skillUsage: 0,
103
+ evolutionAudit: 0,
104
+ evolutionEvidence: 0,
105
+ orchestrateRuns: 0,
106
+ };
107
+
108
+ // -- Read all data BEFORE opening the transaction ---------------------------
109
+ // This keeps file I/O out of the write lock for better concurrency.
110
+
111
+ const canonical = readCanonicalRecords(options?.canonicalLogPath ?? CANONICAL_LOG);
112
+ const filteredCanonical = since ? canonical.filter((r) => r.normalized_at > since) : canonical;
113
+
114
+ // Pre-partition canonical records by kind (single pass instead of 4x full scan)
115
+ const byKind = new Map<string, CanonicalRecord[]>();
116
+ for (const r of filteredCanonical) {
117
+ const arr = byKind.get(r.record_kind);
118
+ if (arr) arr.push(r);
119
+ else byKind.set(r.record_kind, [r]);
120
+ }
121
+
122
+ const telemetry = readJsonl<SessionTelemetryRecord>(options?.telemetryLogPath ?? TELEMETRY_LOG);
123
+ const filteredTelemetry = since ? telemetry.filter((r) => r.timestamp > since) : telemetry;
124
+
125
+ const skills = readEffectiveSkillUsageRecords();
126
+ const filteredSkills = since ? skills.filter((r) => r.timestamp > since) : skills;
127
+
128
+ const audit = readJsonl<EvolutionAuditEntry>(options?.evolutionAuditPath ?? EVOLUTION_AUDIT_LOG);
129
+ const filteredAudit = since ? audit.filter((r) => r.timestamp > since) : audit;
130
+
131
+ const evidence = readEvidenceTrail(
132
+ undefined,
133
+ options?.evolutionEvidencePath ?? EVOLUTION_EVIDENCE_LOG,
134
+ );
135
+ const filteredEvidence = since ? evidence.filter((r) => r.timestamp > since) : evidence;
136
+
137
+ const orchestrateRuns = readJsonl<OrchestrateRunReport>(
138
+ options?.orchestrateRunLogPath ?? ORCHESTRATE_RUN_LOG,
139
+ );
140
+ const filteredOrchestrateRuns = since
141
+ ? orchestrateRuns.filter((r) => r.timestamp > since)
142
+ : orchestrateRuns;
143
+
144
+ // -- Insert everything inside a single transaction --------------------------
145
+ db.run("BEGIN TRANSACTION");
146
+ try {
147
+ result.sessions = insertSessions(db, byKind.get("session") ?? []);
148
+ result.prompts = insertPrompts(db, byKind.get("prompt") ?? []);
149
+ result.skillInvocations = insertSkillInvocations(db, byKind.get("skill_invocation") ?? []);
150
+ result.executionFacts = insertExecutionFacts(db, byKind.get("execution_fact") ?? []);
151
+ result.sessionTelemetry = insertSessionTelemetry(db, filteredTelemetry);
152
+ result.skillUsage = insertSkillUsage(db, filteredSkills);
153
+ result.evolutionAudit = insertEvolutionAudit(db, filteredAudit);
154
+ result.evolutionEvidence = insertEvolutionEvidence(db, filteredEvidence);
155
+ result.orchestrateRuns = insertOrchestrateRuns(db, filteredOrchestrateRuns);
156
+
157
+ setMeta(db, META_LAST_MATERIALIZED, now);
158
+ db.run("COMMIT");
159
+ } catch (err) {
160
+ db.run("ROLLBACK");
161
+ throw err;
162
+ }
163
+
164
+ return result;
165
+ }
166
+
167
+ // -- Insert helpers -----------------------------------------------------------
168
+
169
+ function insertSessions(db: Database, records: CanonicalRecord[]): number {
170
+ const stmt = db.prepare(`
171
+ INSERT OR IGNORE INTO sessions
172
+ (session_id, started_at, ended_at, platform, model, completion_status,
173
+ source_session_kind, agent_cli, workspace_path, repo_remote, branch,
174
+ schema_version, normalized_at)
175
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
176
+ `);
177
+
178
+ let count = 0;
179
+ for (const r of records) {
180
+ const s = r as CanonicalSessionRecord;
181
+ stmt.run(
182
+ s.session_id,
183
+ s.started_at ?? null,
184
+ s.ended_at ?? null,
185
+ s.platform,
186
+ s.model ?? null,
187
+ s.completion_status ?? null,
188
+ s.source_session_kind ?? null,
189
+ s.agent_cli ?? null,
190
+ s.workspace_path ?? null,
191
+ s.repo_remote ?? null,
192
+ s.branch ?? null,
193
+ s.schema_version,
194
+ s.normalized_at,
195
+ );
196
+ count++;
197
+ }
198
+ return count;
199
+ }
200
+
201
+ function insertPrompts(db: Database, records: CanonicalRecord[]): number {
202
+ const stmt = db.prepare(`
203
+ INSERT OR IGNORE INTO prompts
204
+ (prompt_id, session_id, occurred_at, prompt_kind, is_actionable, prompt_index, prompt_text)
205
+ VALUES (?, ?, ?, ?, ?, ?, ?)
206
+ `);
207
+
208
+ let count = 0;
209
+ for (const r of records) {
210
+ const p = r as CanonicalPromptRecord;
211
+ stmt.run(
212
+ p.prompt_id,
213
+ p.session_id,
214
+ p.occurred_at,
215
+ p.prompt_kind,
216
+ p.is_actionable ? 1 : 0,
217
+ p.prompt_index ?? null,
218
+ p.prompt_text,
219
+ );
220
+ count++;
221
+ }
222
+ return count;
223
+ }
224
+
225
+ function insertSkillInvocations(db: Database, records: CanonicalRecord[]): number {
226
+ const stmt = db.prepare(`
227
+ INSERT OR IGNORE INTO skill_invocations
228
+ (skill_invocation_id, session_id, occurred_at, skill_name, invocation_mode,
229
+ triggered, confidence, tool_name, matched_prompt_id)
230
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
231
+ `);
232
+
233
+ let count = 0;
234
+ for (const r of records) {
235
+ const si = r as CanonicalSkillInvocationRecord;
236
+ stmt.run(
237
+ si.skill_invocation_id,
238
+ si.session_id,
239
+ si.occurred_at,
240
+ si.skill_name,
241
+ si.invocation_mode,
242
+ si.triggered ? 1 : 0,
243
+ si.confidence,
244
+ si.tool_name ?? null,
245
+ si.matched_prompt_id ?? null,
246
+ );
247
+ count++;
248
+ }
249
+ return count;
250
+ }
251
+
252
+ function insertExecutionFacts(db: Database, records: CanonicalRecord[]): number {
253
+ const stmt = db.prepare(`
254
+ INSERT INTO execution_facts
255
+ (session_id, occurred_at, prompt_id, tool_calls_json, total_tool_calls,
256
+ assistant_turns, errors_encountered, input_tokens, output_tokens,
257
+ duration_ms, completion_status)
258
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
259
+ `);
260
+
261
+ let count = 0;
262
+ for (const r of records) {
263
+ const ef = r as CanonicalExecutionFactRecord;
264
+ stmt.run(
265
+ ef.session_id,
266
+ ef.occurred_at,
267
+ ef.prompt_id ?? null,
268
+ JSON.stringify(ef.tool_calls_json),
269
+ ef.total_tool_calls,
270
+ ef.assistant_turns,
271
+ ef.errors_encountered,
272
+ ef.input_tokens ?? null,
273
+ ef.output_tokens ?? null,
274
+ ef.duration_ms ?? null,
275
+ ef.completion_status ?? null,
276
+ );
277
+ count++;
278
+ }
279
+ return count;
280
+ }
281
+
282
+ function insertSessionTelemetry(db: Database, records: SessionTelemetryRecord[]): number {
283
+ const stmt = db.prepare(`
284
+ INSERT OR IGNORE INTO session_telemetry
285
+ (session_id, timestamp, cwd, transcript_path, tool_calls_json,
286
+ total_tool_calls, bash_commands_json, skills_triggered_json,
287
+ skills_invoked_json, assistant_turns, errors_encountered,
288
+ transcript_chars, last_user_query, source, input_tokens, output_tokens)
289
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
290
+ `);
291
+
292
+ let count = 0;
293
+ for (const r of records) {
294
+ stmt.run(
295
+ r.session_id,
296
+ r.timestamp,
297
+ r.cwd,
298
+ r.transcript_path,
299
+ JSON.stringify(r.tool_calls),
300
+ r.total_tool_calls,
301
+ JSON.stringify(r.bash_commands),
302
+ JSON.stringify(r.skills_triggered),
303
+ r.skills_invoked ? JSON.stringify(r.skills_invoked) : null,
304
+ r.assistant_turns,
305
+ r.errors_encountered,
306
+ r.transcript_chars,
307
+ r.last_user_query,
308
+ r.source ?? null,
309
+ r.input_tokens ?? null,
310
+ r.output_tokens ?? null,
311
+ );
312
+ count++;
313
+ }
314
+ return count;
315
+ }
316
+
317
+ function insertSkillUsage(db: Database, records: SkillUsageRecord[]): number {
318
+ // Uses INSERT OR IGNORE with a UNIQUE index on the dedup composite key
319
+ // (idx_skill_usage_dedup defined in schema.ts).
320
+ const stmt = db.prepare(`
321
+ INSERT OR IGNORE INTO skill_usage
322
+ (timestamp, session_id, skill_name, skill_path, skill_scope, query, triggered, source)
323
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
324
+ `);
325
+
326
+ let count = 0;
327
+ for (const r of records) {
328
+ stmt.run(
329
+ r.timestamp,
330
+ r.session_id,
331
+ r.skill_name,
332
+ r.skill_path,
333
+ r.skill_scope ?? null,
334
+ r.query,
335
+ r.triggered ? 1 : 0,
336
+ r.source ?? null,
337
+ );
338
+ count++;
339
+ }
340
+ return count;
341
+ }
342
+
343
+ function insertEvolutionAudit(db: Database, records: EvolutionAuditEntry[]): number {
344
+ // Uses INSERT OR IGNORE with a UNIQUE index on the dedup composite key
345
+ // (idx_evo_audit_dedup defined in schema.ts).
346
+ const stmt = db.prepare(`
347
+ INSERT OR IGNORE INTO evolution_audit
348
+ (timestamp, proposal_id, skill_name, action, details, eval_snapshot_json)
349
+ VALUES (?, ?, ?, ?, ?, ?)
350
+ `);
351
+
352
+ let count = 0;
353
+ for (const r of records) {
354
+ stmt.run(
355
+ r.timestamp,
356
+ r.proposal_id,
357
+ r.skill_name ?? null,
358
+ r.action,
359
+ r.details,
360
+ r.eval_snapshot ? JSON.stringify(r.eval_snapshot) : null,
361
+ );
362
+ count++;
363
+ }
364
+ return count;
365
+ }
366
+
367
+ function insertEvolutionEvidence(db: Database, records: EvolutionEvidenceEntry[]): number {
368
+ // Uses INSERT OR IGNORE with a UNIQUE index on the dedup composite key
369
+ // (idx_evo_evidence_dedup defined in schema.ts).
370
+ const stmt = db.prepare(`
371
+ INSERT OR IGNORE INTO evolution_evidence
372
+ (timestamp, proposal_id, skill_name, skill_path, target, stage,
373
+ rationale, confidence, details, original_text, proposed_text,
374
+ eval_set_json, validation_json)
375
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
376
+ `);
377
+
378
+ let count = 0;
379
+ for (const r of records) {
380
+ stmt.run(
381
+ r.timestamp,
382
+ r.proposal_id,
383
+ r.skill_name,
384
+ r.skill_path,
385
+ r.target,
386
+ r.stage,
387
+ r.rationale ?? null,
388
+ r.confidence ?? null,
389
+ r.details ?? null,
390
+ r.original_text ?? null,
391
+ r.proposed_text ?? null,
392
+ r.eval_set ? JSON.stringify(r.eval_set) : null,
393
+ r.validation ? JSON.stringify(r.validation) : null,
394
+ );
395
+ count++;
396
+ }
397
+ return count;
398
+ }
399
+
400
+ function insertOrchestrateRuns(db: Database, records: OrchestrateRunReport[]): number {
401
+ const stmt = db.prepare(`
402
+ INSERT OR IGNORE INTO orchestrate_runs
403
+ (run_id, timestamp, elapsed_ms, dry_run, approval_mode,
404
+ total_skills, evaluated, evolved, deployed, watched, skipped,
405
+ skill_actions_json)
406
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
407
+ `);
408
+
409
+ let count = 0;
410
+ for (const r of records) {
411
+ stmt.run(
412
+ r.run_id,
413
+ r.timestamp,
414
+ r.elapsed_ms,
415
+ r.dry_run ? 1 : 0,
416
+ r.approval_mode,
417
+ r.total_skills,
418
+ r.evaluated,
419
+ r.evolved,
420
+ r.deployed,
421
+ r.watched,
422
+ r.skipped,
423
+ JSON.stringify(r.skill_actions),
424
+ );
425
+ count++;
426
+ }
427
+ return count;
428
+ }