selftune 0.2.6 → 0.2.9

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 (119) hide show
  1. package/README.md +1 -0
  2. package/apps/local-dashboard/dist/assets/index-Bs3Y4ixf.css +1 -0
  3. package/apps/local-dashboard/dist/assets/index-C4UYGWKr.js +15 -0
  4. package/apps/local-dashboard/dist/assets/vendor-react-BQH_6WrG.js +60 -0
  5. package/apps/local-dashboard/dist/assets/{vendor-table-B7VF2Ipl.js → vendor-table-dK1QMLq9.js} +1 -1
  6. package/apps/local-dashboard/dist/assets/{vendor-ui-r2k_Ku_V.js → vendor-ui-CO2mrx6e.js} +60 -65
  7. package/apps/local-dashboard/dist/index.html +5 -5
  8. package/cli/selftune/activation-rules.ts +57 -18
  9. package/cli/selftune/agent-guidance.ts +96 -0
  10. package/cli/selftune/alpha-identity.ts +156 -0
  11. package/cli/selftune/alpha-upload/build-payloads.ts +151 -0
  12. package/cli/selftune/alpha-upload/client.ts +113 -0
  13. package/cli/selftune/alpha-upload/flush.ts +191 -0
  14. package/cli/selftune/alpha-upload/index.ts +194 -0
  15. package/cli/selftune/alpha-upload/queue.ts +252 -0
  16. package/cli/selftune/alpha-upload/stage-canonical.ts +251 -0
  17. package/cli/selftune/alpha-upload-contract.ts +52 -0
  18. package/cli/selftune/auth/device-code.ts +110 -0
  19. package/cli/selftune/auto-update.ts +130 -0
  20. package/cli/selftune/badge/badge.ts +19 -9
  21. package/cli/selftune/canonical-export.ts +16 -3
  22. package/cli/selftune/constants.ts +28 -8
  23. package/cli/selftune/contribute/bundle.ts +33 -5
  24. package/cli/selftune/dashboard-contract.ts +32 -1
  25. package/cli/selftune/dashboard-server.ts +215 -693
  26. package/cli/selftune/dashboard.ts +1 -1
  27. package/cli/selftune/eval/baseline.ts +11 -7
  28. package/cli/selftune/eval/hooks-to-evals.ts +39 -15
  29. package/cli/selftune/eval/synthetic-evals.ts +54 -1
  30. package/cli/selftune/evolution/audit.ts +24 -19
  31. package/cli/selftune/evolution/constitutional.ts +176 -0
  32. package/cli/selftune/evolution/evidence.ts +18 -13
  33. package/cli/selftune/evolution/evolve-body.ts +104 -7
  34. package/cli/selftune/evolution/evolve.ts +195 -22
  35. package/cli/selftune/evolution/propose-body.ts +18 -1
  36. package/cli/selftune/evolution/propose-description.ts +27 -2
  37. package/cli/selftune/evolution/rollback.ts +11 -15
  38. package/cli/selftune/export.ts +84 -0
  39. package/cli/selftune/grading/auto-grade.ts +14 -4
  40. package/cli/selftune/grading/grade-session.ts +17 -6
  41. package/cli/selftune/hooks/auto-activate.ts +5 -0
  42. package/cli/selftune/hooks/evolution-guard.ts +25 -11
  43. package/cli/selftune/hooks/prompt-log.ts +23 -9
  44. package/cli/selftune/hooks/session-stop.ts +78 -15
  45. package/cli/selftune/hooks/skill-eval.ts +189 -10
  46. package/cli/selftune/index.ts +274 -2
  47. package/cli/selftune/ingestors/claude-replay.ts +48 -21
  48. package/cli/selftune/init.ts +260 -49
  49. package/cli/selftune/last.ts +7 -7
  50. package/cli/selftune/localdb/db.ts +90 -10
  51. package/cli/selftune/localdb/direct-write.ts +573 -0
  52. package/cli/selftune/localdb/materialize.ts +296 -42
  53. package/cli/selftune/localdb/queries.ts +482 -32
  54. package/cli/selftune/localdb/schema.ts +153 -1
  55. package/cli/selftune/monitoring/watch.ts +27 -8
  56. package/cli/selftune/normalization.ts +88 -15
  57. package/cli/selftune/observability.ts +257 -5
  58. package/cli/selftune/orchestrate.ts +176 -53
  59. package/cli/selftune/quickstart.ts +34 -10
  60. package/cli/selftune/repair/skill-usage.ts +15 -2
  61. package/cli/selftune/routes/actions.ts +77 -0
  62. package/cli/selftune/routes/badge.ts +66 -0
  63. package/cli/selftune/routes/doctor.ts +12 -0
  64. package/cli/selftune/routes/index.ts +14 -0
  65. package/cli/selftune/routes/orchestrate-runs.ts +13 -0
  66. package/cli/selftune/routes/overview.ts +14 -0
  67. package/cli/selftune/routes/report.ts +293 -0
  68. package/cli/selftune/routes/skill-report.ts +230 -0
  69. package/cli/selftune/status.ts +203 -7
  70. package/cli/selftune/sync.ts +14 -1
  71. package/cli/selftune/types.ts +52 -2
  72. package/cli/selftune/utils/jsonl.ts +58 -1
  73. package/cli/selftune/utils/selftune-meta.ts +38 -0
  74. package/cli/selftune/utils/skill-log.ts +30 -4
  75. package/cli/selftune/utils/transcript.ts +15 -0
  76. package/cli/selftune/workflows/workflows.ts +7 -6
  77. package/package.json +11 -6
  78. package/packages/telemetry-contract/fixtures/complete-push.ts +184 -0
  79. package/packages/telemetry-contract/fixtures/evidence-only-push.ts +58 -0
  80. package/packages/telemetry-contract/fixtures/golden.json +1 -0
  81. package/packages/telemetry-contract/fixtures/index.ts +4 -0
  82. package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +40 -0
  83. package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +79 -0
  84. package/packages/telemetry-contract/package.json +6 -1
  85. package/packages/telemetry-contract/src/schemas.ts +196 -0
  86. package/packages/telemetry-contract/src/types.ts +3 -1
  87. package/packages/telemetry-contract/src/validators.ts +3 -1
  88. package/packages/telemetry-contract/tests/compatibility.test.ts +144 -0
  89. package/packages/ui/package.json +4 -0
  90. package/packages/ui/src/components/ActivityTimeline.tsx +61 -29
  91. package/packages/ui/src/components/section-cards.tsx +31 -14
  92. package/packages/ui/src/types.ts +1 -0
  93. package/skill/SKILL.md +214 -174
  94. package/skill/Workflows/AlphaUpload.md +45 -0
  95. package/skill/Workflows/Baseline.md +18 -12
  96. package/skill/Workflows/Composability.md +3 -3
  97. package/skill/Workflows/Dashboard.md +39 -91
  98. package/skill/Workflows/Doctor.md +93 -66
  99. package/skill/Workflows/Evals.md +49 -40
  100. package/skill/Workflows/Evolve.md +76 -28
  101. package/skill/Workflows/EvolveBody.md +37 -38
  102. package/skill/Workflows/Initialize.md +145 -26
  103. package/skill/Workflows/Orchestrate.md +11 -2
  104. package/skill/Workflows/Sync.md +23 -0
  105. package/skill/Workflows/Watch.md +2 -5
  106. package/skill/agents/diagnosis-analyst.md +163 -0
  107. package/skill/agents/evolution-reviewer.md +149 -0
  108. package/skill/agents/integration-guide.md +154 -0
  109. package/skill/agents/pattern-analyst.md +149 -0
  110. package/skill/assets/multi-skill-settings.json +1 -1
  111. package/skill/assets/single-skill-settings.json +1 -1
  112. package/skill/references/interactive-config.md +39 -0
  113. package/skill/references/invocation-taxonomy.md +34 -0
  114. package/skill/references/logs.md +15 -1
  115. package/skill/references/setup-patterns.md +3 -3
  116. package/skill/settings_snippet.json +1 -1
  117. package/apps/local-dashboard/dist/assets/index-C75H1Q3n.css +0 -1
  118. package/apps/local-dashboard/dist/assets/index-axE4kz3Q.js +0 -15
  119. package/apps/local-dashboard/dist/assets/vendor-react-U7zYD9Rg.js +0 -60
@@ -21,7 +21,10 @@ CREATE TABLE IF NOT EXISTS sessions (
21
21
  repo_remote TEXT,
22
22
  branch TEXT,
23
23
  schema_version TEXT,
24
- normalized_at TEXT
24
+ normalized_at TEXT,
25
+ normalizer_version TEXT,
26
+ capture_mode TEXT,
27
+ raw_source_ref TEXT
25
28
  )`;
26
29
 
27
30
  export const CREATE_PROMPTS = `
@@ -33,6 +36,12 @@ CREATE TABLE IF NOT EXISTS prompts (
33
36
  is_actionable INTEGER,
34
37
  prompt_index INTEGER,
35
38
  prompt_text TEXT,
39
+ schema_version TEXT,
40
+ platform TEXT,
41
+ normalized_at TEXT,
42
+ normalizer_version TEXT,
43
+ capture_mode TEXT,
44
+ raw_source_ref TEXT,
36
45
  FOREIGN KEY (session_id) REFERENCES sessions(session_id)
37
46
  )`;
38
47
 
@@ -47,6 +56,17 @@ CREATE TABLE IF NOT EXISTS skill_invocations (
47
56
  confidence REAL,
48
57
  tool_name TEXT,
49
58
  matched_prompt_id TEXT,
59
+ agent_type TEXT,
60
+ query TEXT,
61
+ skill_path TEXT,
62
+ skill_scope TEXT,
63
+ source TEXT,
64
+ schema_version TEXT,
65
+ platform TEXT,
66
+ normalized_at TEXT,
67
+ normalizer_version TEXT,
68
+ capture_mode TEXT,
69
+ raw_source_ref TEXT,
50
70
  FOREIGN KEY (session_id) REFERENCES sessions(session_id)
51
71
  )`;
52
72
 
@@ -64,6 +84,12 @@ CREATE TABLE IF NOT EXISTS execution_facts (
64
84
  output_tokens INTEGER,
65
85
  duration_ms INTEGER,
66
86
  completion_status TEXT,
87
+ schema_version TEXT,
88
+ platform TEXT,
89
+ normalized_at TEXT,
90
+ normalizer_version TEXT,
91
+ capture_mode TEXT,
92
+ raw_source_ref TEXT,
67
93
  FOREIGN KEY (session_id) REFERENCES sessions(session_id)
68
94
  )`;
69
95
 
@@ -151,6 +177,67 @@ CREATE TABLE IF NOT EXISTS orchestrate_runs (
151
177
  skill_actions_json TEXT NOT NULL
152
178
  )`;
153
179
 
180
+ // -- Query log table (from all_queries_log.jsonl) ----------------------------
181
+
182
+ export const CREATE_QUERIES = `
183
+ CREATE TABLE IF NOT EXISTS queries (
184
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
185
+ timestamp TEXT NOT NULL,
186
+ session_id TEXT NOT NULL,
187
+ query TEXT NOT NULL,
188
+ source TEXT
189
+ )`;
190
+
191
+ // -- Improvement signal table (from signal_log.jsonl) ------------------------
192
+
193
+ export const CREATE_IMPROVEMENT_SIGNALS = `
194
+ CREATE TABLE IF NOT EXISTS improvement_signals (
195
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
196
+ timestamp TEXT NOT NULL,
197
+ session_id TEXT NOT NULL,
198
+ query TEXT NOT NULL,
199
+ signal_type TEXT NOT NULL,
200
+ mentioned_skill TEXT,
201
+ consumed INTEGER NOT NULL DEFAULT 0,
202
+ consumed_at TEXT,
203
+ consumed_by_run TEXT
204
+ )`;
205
+
206
+ // -- Alpha upload queue -------------------------------------------------------
207
+
208
+ export const CREATE_UPLOAD_QUEUE = `
209
+ CREATE TABLE IF NOT EXISTS upload_queue (
210
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
211
+ payload_type TEXT NOT NULL,
212
+ payload_json TEXT NOT NULL,
213
+ status TEXT NOT NULL DEFAULT 'pending',
214
+ attempts INTEGER NOT NULL DEFAULT 0,
215
+ created_at TEXT NOT NULL,
216
+ updated_at TEXT NOT NULL,
217
+ last_error TEXT
218
+ )`;
219
+
220
+ // -- Canonical upload staging -------------------------------------------------
221
+
222
+ export const CREATE_CANONICAL_UPLOAD_STAGING = `
223
+ CREATE TABLE IF NOT EXISTS canonical_upload_staging (
224
+ local_seq INTEGER PRIMARY KEY AUTOINCREMENT,
225
+ record_kind TEXT NOT NULL,
226
+ record_id TEXT NOT NULL,
227
+ record_json TEXT NOT NULL,
228
+ session_id TEXT,
229
+ prompt_id TEXT,
230
+ normalized_at TEXT,
231
+ staged_at TEXT NOT NULL
232
+ )`;
233
+
234
+ export const CREATE_UPLOAD_WATERMARKS = `
235
+ CREATE TABLE IF NOT EXISTS upload_watermarks (
236
+ payload_type TEXT PRIMARY KEY,
237
+ last_uploaded_id INTEGER NOT NULL,
238
+ updated_at TEXT NOT NULL
239
+ )`;
240
+
154
241
  // -- Metadata table -----------------------------------------------------------
155
242
 
156
243
  export const CREATE_META = `
@@ -167,6 +254,7 @@ export const CREATE_INDEXES = [
167
254
  `CREATE INDEX IF NOT EXISTS idx_prompts_occurred ON prompts(occurred_at)`,
168
255
  `CREATE INDEX IF NOT EXISTS idx_skill_inv_session ON skill_invocations(session_id)`,
169
256
  `CREATE INDEX IF NOT EXISTS idx_skill_inv_name ON skill_invocations(skill_name)`,
257
+ `CREATE INDEX IF NOT EXISTS idx_skill_inv_ts ON skill_invocations(occurred_at)`,
170
258
  `CREATE INDEX IF NOT EXISTS idx_exec_facts_session ON execution_facts(session_id)`,
171
259
  `CREATE INDEX IF NOT EXISTS idx_evo_evidence_proposal ON evolution_evidence(proposal_id)`,
172
260
  `CREATE INDEX IF NOT EXISTS idx_evo_evidence_skill ON evolution_evidence(skill_name)`,
@@ -186,6 +274,65 @@ export const CREATE_INDEXES = [
186
274
  `CREATE UNIQUE INDEX IF NOT EXISTS idx_evo_evidence_dedup ON evolution_evidence(proposal_id, stage, timestamp)`,
187
275
  // -- Orchestrate run indexes -----------------------------------------------
188
276
  `CREATE INDEX IF NOT EXISTS idx_orchestrate_runs_ts ON orchestrate_runs(timestamp)`,
277
+ // -- Query log indexes ------------------------------------------------------
278
+ `CREATE INDEX IF NOT EXISTS idx_queries_session ON queries(session_id)`,
279
+ `CREATE INDEX IF NOT EXISTS idx_queries_ts ON queries(timestamp)`,
280
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_queries_dedup ON queries(session_id, query, timestamp)`,
281
+ // -- Improvement signal indexes ---------------------------------------------
282
+ `CREATE INDEX IF NOT EXISTS idx_signals_session ON improvement_signals(session_id)`,
283
+ `CREATE INDEX IF NOT EXISTS idx_signals_consumed ON improvement_signals(consumed)`,
284
+ `CREATE INDEX IF NOT EXISTS idx_signals_ts ON improvement_signals(timestamp)`,
285
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_signals_dedup ON improvement_signals(session_id, query, signal_type, timestamp)`,
286
+ // -- Alpha upload queue indexes ---------------------------------------------
287
+ `CREATE INDEX IF NOT EXISTS idx_upload_queue_status ON upload_queue(status)`,
288
+ `CREATE INDEX IF NOT EXISTS idx_upload_queue_type_status ON upload_queue(payload_type, status)`,
289
+ // -- Canonical upload staging indexes ---------------------------------------
290
+ `CREATE INDEX IF NOT EXISTS idx_staging_kind ON canonical_upload_staging(record_kind)`,
291
+ `CREATE INDEX IF NOT EXISTS idx_staging_session ON canonical_upload_staging(session_id)`,
292
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_staging_dedup ON canonical_upload_staging(record_kind, record_id)`,
293
+ ];
294
+
295
+ /**
296
+ * Schema migrations — ALTER TABLE statements for columns added after initial release.
297
+ * Each is safe to re-run: SQLite throws "duplicate column" which openDb() catches.
298
+ */
299
+ export const MIGRATIONS = [
300
+ // skill_invocations consolidation (skill_usage columns merged in)
301
+ `ALTER TABLE skill_invocations ADD COLUMN query TEXT`,
302
+ `ALTER TABLE skill_invocations ADD COLUMN skill_path TEXT`,
303
+ `ALTER TABLE skill_invocations ADD COLUMN skill_scope TEXT`,
304
+ `ALTER TABLE skill_invocations ADD COLUMN source TEXT`,
305
+ // Track how many iteration loops each evolution run used
306
+ `ALTER TABLE evolution_audit ADD COLUMN iterations_used INTEGER`,
307
+ // Canonical contract fields for upload staging (sessions already has schema_version, platform, normalized_at)
308
+ `ALTER TABLE sessions ADD COLUMN normalizer_version TEXT`,
309
+ `ALTER TABLE sessions ADD COLUMN capture_mode TEXT`,
310
+ `ALTER TABLE sessions ADD COLUMN raw_source_ref TEXT`,
311
+ `ALTER TABLE prompts ADD COLUMN schema_version TEXT`,
312
+ `ALTER TABLE prompts ADD COLUMN platform TEXT`,
313
+ `ALTER TABLE prompts ADD COLUMN normalized_at TEXT`,
314
+ `ALTER TABLE prompts ADD COLUMN normalizer_version TEXT`,
315
+ `ALTER TABLE prompts ADD COLUMN capture_mode TEXT`,
316
+ `ALTER TABLE prompts ADD COLUMN raw_source_ref TEXT`,
317
+ `ALTER TABLE skill_invocations ADD COLUMN schema_version TEXT`,
318
+ `ALTER TABLE skill_invocations ADD COLUMN platform TEXT`,
319
+ `ALTER TABLE skill_invocations ADD COLUMN normalized_at TEXT`,
320
+ `ALTER TABLE skill_invocations ADD COLUMN normalizer_version TEXT`,
321
+ `ALTER TABLE skill_invocations ADD COLUMN capture_mode TEXT`,
322
+ `ALTER TABLE skill_invocations ADD COLUMN raw_source_ref TEXT`,
323
+ `ALTER TABLE execution_facts ADD COLUMN schema_version TEXT`,
324
+ `ALTER TABLE execution_facts ADD COLUMN platform TEXT`,
325
+ `ALTER TABLE execution_facts ADD COLUMN normalized_at TEXT`,
326
+ `ALTER TABLE execution_facts ADD COLUMN normalizer_version TEXT`,
327
+ `ALTER TABLE execution_facts ADD COLUMN capture_mode TEXT`,
328
+ `ALTER TABLE execution_facts ADD COLUMN raw_source_ref TEXT`,
329
+ ];
330
+
331
+ /** Indexes that depend on migration columns — must run AFTER MIGRATIONS. */
332
+ export const POST_MIGRATION_INDEXES = [
333
+ `CREATE INDEX IF NOT EXISTS idx_skill_inv_query_triggered ON skill_invocations(query, triggered)`,
334
+ `CREATE INDEX IF NOT EXISTS idx_skill_inv_scope ON skill_invocations(skill_name, skill_scope, occurred_at)`,
335
+ `CREATE INDEX IF NOT EXISTS idx_skill_inv_dedup ON skill_invocations(session_id, skill_name, query, occurred_at, triggered)`,
189
336
  ];
190
337
 
191
338
  /** All DDL statements in creation order. */
@@ -199,6 +346,11 @@ export const ALL_DDL = [
199
346
  CREATE_SESSION_TELEMETRY,
200
347
  CREATE_SKILL_USAGE,
201
348
  CREATE_ORCHESTRATE_RUNS,
349
+ CREATE_QUERIES,
350
+ CREATE_IMPROVEMENT_SIGNALS,
351
+ CREATE_UPLOAD_QUEUE,
352
+ CREATE_UPLOAD_WATERMARKS,
353
+ CREATE_CANONICAL_UPLOAD_STAGING,
202
354
  CREATE_META,
203
355
  ...CREATE_INDEXES,
204
356
  ];
@@ -11,6 +11,12 @@ import { parseArgs } from "node:util";
11
11
  import { QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
12
12
  import { classifyInvocation } from "../eval/hooks-to-evals.js";
13
13
  import { getLastDeployedProposal } from "../evolution/audit.js";
14
+ import { getDb } from "../localdb/db.js";
15
+ import {
16
+ queryQueryLog,
17
+ querySessionTelemetry,
18
+ querySkillUsageRecords,
19
+ } from "../localdb/queries.js";
14
20
  import { updateContextAfterWatch } from "../memory/writer.js";
15
21
  import type { SyncResult } from "../sync.js";
16
22
  import type {
@@ -25,7 +31,6 @@ import {
25
31
  filterActionableQueryRecords,
26
32
  filterActionableSkillUsageRecords,
27
33
  } from "../utils/query-filter.js";
28
- import { readEffectiveSkillUsageRecords } from "../utils/skill-log.js";
29
34
 
30
35
  // ---------------------------------------------------------------------------
31
36
  // Public interfaces
@@ -207,13 +212,27 @@ export async function watch(options: WatchOptions): Promise<WatchResult> {
207
212
  );
208
213
  }
209
214
 
210
- // 1. Read log files
211
- const telemetry = readJsonl<SessionTelemetryRecord>(_telemetryLogPath);
212
- const skillRecords =
213
- _skillLogPath === SKILL_LOG
214
- ? readEffectiveSkillUsageRecords()
215
- : readJsonl<SkillUsageRecord>(_skillLogPath);
216
- const queryRecords = readJsonl<QueryLogRecord>(_queryLogPath);
215
+ // 1. Read log files from SQLite (fall back to JSONL for custom paths)
216
+ let telemetry: SessionTelemetryRecord[];
217
+ let skillRecords: SkillUsageRecord[];
218
+ let queryRecords: QueryLogRecord[];
219
+ if (
220
+ _telemetryLogPath === TELEMETRY_LOG &&
221
+ _skillLogPath === SKILL_LOG &&
222
+ _queryLogPath === QUERY_LOG
223
+ ) {
224
+ const db = getDb();
225
+ telemetry = querySessionTelemetry(db) as SessionTelemetryRecord[];
226
+ // SQLite queries return DESC order; computeMonitoringSnapshot expects chronological (ASC)
227
+ telemetry.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
228
+ skillRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
229
+ queryRecords = queryQueryLog(db) as QueryLogRecord[];
230
+ } else {
231
+ // Intentional JSONL fallback: custom log path overrides bypass SQLite reads
232
+ telemetry = readJsonl<SessionTelemetryRecord>(_telemetryLogPath);
233
+ skillRecords = readJsonl<SkillUsageRecord>(_skillLogPath);
234
+ queryRecords = readJsonl<QueryLogRecord>(_queryLogPath);
235
+ }
217
236
 
218
237
  // 2. Determine baseline pass rate from last deployed audit entry
219
238
  const lastDeployed = getLastDeployedProposal(skillName, _auditLogPath);
@@ -2,7 +2,7 @@
2
2
  * Canonical telemetry normalization helpers.
3
3
  *
4
4
  * This module provides shared functions that all platform adapters call
5
- * to produce canonical records alongside their raw JSONL output.
5
+ * to produce canonical records written to SQLite via writeCanonicalToDb().
6
6
  *
7
7
  * Contract rules (from telemetry-field-map.md):
8
8
  * 1. Normalization is additive — raw capture is preserved separately.
@@ -25,6 +25,7 @@ import {
25
25
  } from "node:fs";
26
26
  import { basename, dirname } from "node:path";
27
27
  import { CANONICAL_LOG, canonicalSessionStatePath } from "./constants.js";
28
+ import { writeCanonicalBatchToDb, writeCanonicalToDb } from "./localdb/direct-write.js";
28
29
  import {
29
30
  CANONICAL_SCHEMA_VERSION,
30
31
  type CanonicalCaptureMode,
@@ -81,9 +82,49 @@ function defaultPromptSessionState(sessionId: string): CanonicalPromptSessionSta
81
82
 
82
83
  function derivePromptSessionStateFromCanonicalLog(
83
84
  sessionId: string,
84
- canonicalLogPath: string = CANONICAL_LOG,
85
+ _canonicalLogPath: string = CANONICAL_LOG,
85
86
  ): CanonicalPromptSessionState {
86
87
  const recovered = defaultPromptSessionState(sessionId);
88
+
89
+ // Try SQLite first — canonical records now go to the local DB.
90
+ // Uses dynamic require + try/catch so this remains fail-safe during
91
+ // hook execution when the DB module may not be loadable.
92
+ try {
93
+ const { getDb } = require("./localdb/db.js") as {
94
+ getDb: () => import("bun:sqlite").Database;
95
+ };
96
+ const db = getDb();
97
+ const rows = db
98
+ .query(
99
+ "SELECT prompt_id, prompt_index, is_actionable FROM prompts WHERE session_id = ? ORDER BY prompt_index DESC LIMIT 1",
100
+ )
101
+ .all(sessionId) as Array<{
102
+ prompt_id: string;
103
+ prompt_index: number;
104
+ is_actionable: number;
105
+ }>;
106
+ if (rows.length > 0) {
107
+ const row = rows[0];
108
+ recovered.next_prompt_index = row.prompt_index + 1;
109
+ recovered.last_prompt_id = row.prompt_id;
110
+ // Get last actionable
111
+ const actionable = db
112
+ .query(
113
+ "SELECT prompt_id, prompt_index FROM prompts WHERE session_id = ? AND is_actionable = 1 ORDER BY prompt_index DESC LIMIT 1",
114
+ )
115
+ .get(sessionId) as { prompt_id: string; prompt_index: number } | null;
116
+ if (actionable) recovered.last_actionable_prompt_id = actionable.prompt_id;
117
+ return recovered;
118
+ }
119
+ if (_canonicalLogPath === CANONICAL_LOG) {
120
+ return recovered;
121
+ }
122
+ } catch {
123
+ // DB unavailable — fall through to JSONL recovery below.
124
+ }
125
+
126
+ // Fallback: scan canonical JSONL log (legacy path or DB unavailable).
127
+ const canonicalLogPath = _canonicalLogPath;
87
128
  let maxPromptIndex = -1;
88
129
  let maxActionablePromptIndex = -1;
89
130
 
@@ -346,22 +387,32 @@ export function getLatestPromptIdentity(
346
387
  };
347
388
  }
348
389
 
349
- export function appendCanonicalRecord(
350
- record: CanonicalRecord,
351
- logPath: string = CANONICAL_LOG,
352
- ): void {
353
- const dir = dirname(logPath);
354
- if (!existsSync(dir)) {
355
- mkdirSync(dir, { recursive: true });
390
+ export function appendCanonicalRecord(record: CanonicalRecord, logPath?: string): void {
391
+ writeCanonicalToDb(record);
392
+ // JSONL append — best-effort backup for prompt state recovery
393
+ try {
394
+ const path = logPath ?? CANONICAL_LOG;
395
+ const dir = dirname(path);
396
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
397
+ appendFileSync(path, `${JSON.stringify(record)}\n`, "utf-8");
398
+ } catch {
399
+ /* best-effort only */
356
400
  }
357
- appendFileSync(logPath, `${JSON.stringify(record)}\n`, "utf-8");
358
401
  }
359
402
 
360
- export function appendCanonicalRecords(
361
- records: CanonicalRecord[],
362
- logPath: string = CANONICAL_LOG,
363
- ): void {
364
- for (const record of records) appendCanonicalRecord(record, logPath);
403
+ export function appendCanonicalRecords(records: CanonicalRecord[], logPath?: string): void {
404
+ writeCanonicalBatchToDb(records);
405
+ // JSONL append — best-effort backup for prompt state recovery
406
+ try {
407
+ const path = logPath ?? CANONICAL_LOG;
408
+ const dir = dirname(path);
409
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
410
+ for (const record of records) {
411
+ appendFileSync(path, `${JSON.stringify(record)}\n`, "utf-8");
412
+ }
413
+ } catch {
414
+ /* best-effort only */
415
+ }
365
416
  }
366
417
 
367
418
  // ---------------------------------------------------------------------------
@@ -439,14 +490,34 @@ export interface InvocationClassification {
439
490
 
440
491
  /**
441
492
  * Classify how a skill was invoked.
493
+ *
494
+ * When `hook_invocation_type` is provided (from the skill-eval hook's
495
+ * classifyInvocationType), it takes precedence over the legacy heuristics:
496
+ * - "explicit" → user typed /skill (slash command) → explicit, confidence 1.0
497
+ * - "implicit" → user named the skill, Claude invoked it → implicit, confidence 0.85
498
+ * - "inferred" → Claude chose skill autonomously → inferred, confidence 0.6
499
+ * - "contextual" → SKILL.md was read (Read tool, not Skill tool) → inferred, confidence 0.5
442
500
  */
443
501
  export function deriveInvocationMode(opts: {
444
502
  has_skill_tool_call?: boolean;
445
503
  has_skill_md_read?: boolean;
446
504
  is_text_mention_only?: boolean;
447
505
  is_repaired?: boolean;
506
+ hook_invocation_type?: "explicit" | "implicit" | "inferred" | "contextual";
448
507
  }): InvocationClassification {
449
508
  if (opts.is_repaired) return { invocation_mode: "repaired", confidence: 0.9 };
509
+
510
+ // Prefer hook-level classification when available
511
+ if (opts.hook_invocation_type === "explicit")
512
+ return { invocation_mode: "explicit", confidence: 1.0 };
513
+ if (opts.hook_invocation_type === "implicit")
514
+ return { invocation_mode: "implicit", confidence: 0.85 };
515
+ if (opts.hook_invocation_type === "inferred")
516
+ return { invocation_mode: "inferred", confidence: 0.6 };
517
+ if (opts.hook_invocation_type === "contextual")
518
+ return { invocation_mode: "inferred", confidence: 0.5 };
519
+
520
+ // Legacy fallback for callers that don't pass hook_invocation_type
450
521
  if (opts.has_skill_tool_call) return { invocation_mode: "explicit", confidence: 1.0 };
451
522
  if (opts.has_skill_md_read) return { invocation_mode: "implicit", confidence: 0.7 };
452
523
  if (opts.is_text_mention_only) return { invocation_mode: "inferred", confidence: 0.4 };
@@ -613,6 +684,7 @@ export interface BuildSkillInvocationInput extends CanonicalBaseInput {
613
684
  confidence: number;
614
685
  tool_name?: string;
615
686
  tool_call_id?: string;
687
+ agent_type?: string;
616
688
  }
617
689
 
618
690
  export function buildCanonicalSkillInvocation(
@@ -636,6 +708,7 @@ export function buildCanonicalSkillInvocation(
636
708
  if (input.skill_version_hash !== undefined) record.skill_version_hash = input.skill_version_hash;
637
709
  if (input.tool_name !== undefined) record.tool_name = input.tool_name;
638
710
  if (input.tool_call_id !== undefined) record.tool_call_id = input.tool_call_id;
711
+ if (input.agent_type !== undefined) record.agent_type = input.agent_type;
639
712
 
640
713
  return record;
641
714
  }