selftune 0.2.13 → 0.2.14

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 (33) hide show
  1. package/apps/local-dashboard/dist/assets/index-BMIS6uUh.css +2 -0
  2. package/apps/local-dashboard/dist/assets/index-DIrdlu2_.js +16 -0
  3. package/apps/local-dashboard/dist/index.html +2 -2
  4. package/cli/selftune/activation-rules.ts +24 -48
  5. package/cli/selftune/constants.ts +7 -0
  6. package/cli/selftune/contribute/bundle.ts +9 -44
  7. package/cli/selftune/dashboard-contract.ts +12 -0
  8. package/cli/selftune/eval/hooks-to-evals.ts +5 -22
  9. package/cli/selftune/grading/auto-grade.ts +3 -13
  10. package/cli/selftune/grading/grade-session.ts +3 -13
  11. package/cli/selftune/hooks/evolution-guard.ts +14 -24
  12. package/cli/selftune/hooks/prompt-log.ts +0 -8
  13. package/cli/selftune/hooks/session-stop.ts +0 -8
  14. package/cli/selftune/ingestors/codex-rollout.ts +9 -4
  15. package/cli/selftune/ingestors/codex-wrapper.ts +15 -13
  16. package/cli/selftune/ingestors/openclaw-ingest.ts +24 -5
  17. package/cli/selftune/ingestors/opencode-ingest.ts +9 -4
  18. package/cli/selftune/localdb/queries.ts +57 -0
  19. package/cli/selftune/monitoring/watch.ts +7 -22
  20. package/cli/selftune/normalization.ts +2 -23
  21. package/cli/selftune/orchestrate.ts +213 -14
  22. package/cli/selftune/schedule.ts +51 -5
  23. package/cli/selftune/utils/jsonl.ts +2 -0
  24. package/package.json +3 -1
  25. package/packages/ui/src/components/RecentActivityFeed.tsx +86 -0
  26. package/packages/ui/src/components/index.ts +1 -0
  27. package/packages/ui/src/components/section-cards.tsx +13 -0
  28. package/skill/SKILL.md +1 -1
  29. package/skill/Workflows/Orchestrate.md +11 -7
  30. package/skill/Workflows/Schedule.md +11 -0
  31. package/skill/references/logs.md +22 -21
  32. package/apps/local-dashboard/dist/assets/index-4_dAY17K.js +0 -16
  33. package/apps/local-dashboard/dist/assets/index-BxV5WZHc.css +0 -2
@@ -5,12 +5,12 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>selftune — Dashboard</title>
7
7
  <link rel="icon" type="image/png" href="/favicon.png" />
8
- <script type="module" crossorigin src="/assets/index-4_dAY17K.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DIrdlu2_.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-Dw2cE7zH.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-CKkiCskZ.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-ui-7xD7fNEU.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/vendor-table-pHbDxq36.js">
13
- <link rel="stylesheet" crossorigin href="/assets/index-BxV5WZHc.css">
13
+ <link rel="stylesheet" crossorigin href="/assets/index-BMIS6uUh.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="root"></div>
@@ -5,64 +5,45 @@
5
5
  * (or null if the rule doesn't fire). Rules must be pure functions —
6
6
  * no network calls, no imports from evolution/monitoring/grading layers.
7
7
  *
8
- * SQLite is the default read path for log data. JSONL fallback is used
9
- * only when context paths differ from the well-known constants
10
- * (test/custom-path override).
8
+ * All log data is read from SQLite.
11
9
  */
12
10
 
13
11
  import { existsSync, readdirSync, readFileSync } from "node:fs";
14
- import { dirname, join } from "node:path";
12
+ import { join } from "node:path";
15
13
 
16
- import { EVOLUTION_AUDIT_LOG, QUERY_LOG } from "./constants.js";
17
14
  import { getDb } from "./localdb/db.js";
18
15
  import { queryEvolutionAudit, queryQueryLog, querySkillUsageRecords } from "./localdb/queries.js";
19
16
  import type { ActivationContext, ActivationRule } from "./types.js";
20
- import { readJsonl } from "./utils/jsonl.js";
21
17
 
22
18
  // ---------------------------------------------------------------------------
23
- // Rule: post-session diagnostic (SQLite-first; JSONL for test/custom paths)
19
+ // Rule: post-session diagnostic
24
20
  // ---------------------------------------------------------------------------
25
21
 
26
22
  const postSessionDiagnostic: ActivationRule = {
27
23
  id: "post-session-diagnostic",
28
24
  description: "Suggest `selftune last` when session has >2 unmatched queries",
29
25
  evaluate(ctx: ActivationContext): string | null {
30
- // Count queries for this session — SQLite is the default path
26
+ // Count queries for this session
31
27
  let queries: Array<{ session_id: string; query: string }>;
32
- if (ctx.query_log_path === QUERY_LOG) {
33
- try {
34
- const db = getDb();
35
- queries = queryQueryLog(db) as Array<{ session_id: string; query: string }>;
36
- } catch {
37
- return null;
38
- }
39
- } else {
40
- // test/custom-path fallback
41
- queries = readJsonl<{ session_id: string; query: string }>(ctx.query_log_path);
28
+ try {
29
+ const db = getDb();
30
+ queries = queryQueryLog(db) as Array<{ session_id: string; query: string }>;
31
+ } catch {
32
+ return null;
42
33
  }
43
34
  const sessionQueries = queries.filter((q) => q.session_id === ctx.session_id);
44
35
 
45
36
  if (sessionQueries.length === 0) return null;
46
37
 
47
- // Count skill usages for this session — SQLite is the default path
38
+ // Count skill usages for this session
48
39
  let skillUsages: Array<{ session_id: string }>;
49
- if (ctx.query_log_path === QUERY_LOG) {
50
- try {
51
- const db = getDb();
52
- skillUsages = (querySkillUsageRecords(db) as Array<{ session_id: string }>).filter(
53
- (s) => s.session_id === ctx.session_id,
54
- );
55
- } catch {
56
- return null;
57
- }
58
- } else {
59
- // test/custom-path fallback
60
- const skillLogPath = join(dirname(ctx.query_log_path), "skill_usage_log.jsonl");
61
- skillUsages = existsSync(skillLogPath)
62
- ? readJsonl<{ session_id: string }>(skillLogPath).filter(
63
- (s) => s.session_id === ctx.session_id,
64
- )
65
- : [];
40
+ try {
41
+ const db = getDb();
42
+ skillUsages = (querySkillUsageRecords(db) as Array<{ session_id: string }>).filter(
43
+ (s) => s.session_id === ctx.session_id,
44
+ );
45
+ } catch {
46
+ return null;
66
47
  }
67
48
 
68
49
  const unmatchedCount = sessionQueries.length - skillUsages.length;
@@ -114,7 +95,7 @@ const gradingThresholdBreach: ActivationRule = {
114
95
  };
115
96
 
116
97
  // ---------------------------------------------------------------------------
117
- // Rule: stale evolution (SQLite-first; JSONL for test/custom paths)
98
+ // Rule: stale evolution
118
99
  // ---------------------------------------------------------------------------
119
100
 
120
101
  const staleEvolution: ActivationRule = {
@@ -124,18 +105,13 @@ const staleEvolution: ActivationRule = {
124
105
  evaluate(ctx: ActivationContext): string | null {
125
106
  const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
126
107
 
127
- // Check last evolution timestamp — SQLite is the default path
108
+ // Check last evolution timestamp
128
109
  let auditEntries: Array<{ timestamp: string; action: string }>;
129
- if (ctx.evolution_audit_log_path === EVOLUTION_AUDIT_LOG) {
130
- try {
131
- const db = getDb();
132
- auditEntries = queryEvolutionAudit(db) as Array<{ timestamp: string; action: string }>;
133
- } catch {
134
- return null;
135
- }
136
- } else {
137
- // test/custom-path fallback
138
- auditEntries = readJsonl<{ timestamp: string; action: string }>(ctx.evolution_audit_log_path);
110
+ try {
111
+ const db = getDb();
112
+ auditEntries = queryEvolutionAudit(db) as Array<{ timestamp: string; action: string }>;
113
+ } catch {
114
+ return null;
139
115
  }
140
116
 
141
117
  if (auditEntries.length === 0) {
@@ -22,15 +22,22 @@ export const SELFTUNE_CONFIG_PATH = join(SELFTUNE_CONFIG_DIR, "config.json");
22
22
 
23
23
  export const LOG_DIR = (process.env.SELFTUNE_LOG_DIR || undefined) ?? claudeHomeDir;
24
24
 
25
+ /** @deprecated Phase 3: JSONL writes removed. Used only by materializer recovery and export. */
25
26
  export const TELEMETRY_LOG = join(LOG_DIR, "session_telemetry_log.jsonl");
26
27
  export const SKILL_LOG = join(LOG_DIR, "skill_usage_log.jsonl");
27
28
  export const REPAIRED_SKILL_LOG = join(LOG_DIR, "skill_usage_repaired.jsonl");
29
+ /** @deprecated Phase 3: JSONL writes removed. Used only by materializer recovery and export. */
28
30
  export const CANONICAL_LOG = join(LOG_DIR, "canonical_telemetry_log.jsonl");
29
31
  export const REPAIRED_SKILL_SESSIONS_MARKER = join(LOG_DIR, "skill_usage_repaired_sessions.json");
32
+ /** @deprecated Phase 3: JSONL writes removed. Used only by materializer recovery and export. */
30
33
  export const QUERY_LOG = join(LOG_DIR, "all_queries_log.jsonl");
34
+ /** @deprecated Phase 3: JSONL writes removed. Used only by materializer recovery and export. */
31
35
  export const EVOLUTION_AUDIT_LOG = join(LOG_DIR, "evolution_audit_log.jsonl");
36
+ /** @deprecated Phase 3: JSONL writes removed. Used only by materializer recovery and export. */
32
37
  export const EVOLUTION_EVIDENCE_LOG = join(LOG_DIR, "evolution_evidence_log.jsonl");
38
+ /** @deprecated Phase 3: JSONL writes removed. Used only by materializer recovery and export. */
33
39
  export const ORCHESTRATE_RUN_LOG = join(LOG_DIR, "orchestrate_runs.jsonl");
40
+ /** @deprecated Phase 3: JSONL writes removed. Used only by materializer recovery and export. */
34
41
  export const SIGNAL_LOG = join(LOG_DIR, "improvement_signals.jsonl");
35
42
  export const ORCHESTRATE_LOCK = join(LOG_DIR, ".orchestrate.lock");
36
43
 
@@ -9,13 +9,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
9
9
  import { homedir } from "node:os";
10
10
  import { join } from "node:path";
11
11
 
12
- import {
13
- EVOLUTION_AUDIT_LOG,
14
- QUERY_LOG,
15
- SELFTUNE_CONFIG_DIR,
16
- SKILL_LOG,
17
- TELEMETRY_LOG,
18
- } from "../constants.js";
12
+ import { SELFTUNE_CONFIG_DIR } from "../constants.js";
19
13
  import { buildEvalSet, classifyInvocation } from "../eval/hooks-to-evals.js";
20
14
  import { getDb } from "../localdb/db.js";
21
15
  import {
@@ -36,7 +30,6 @@ import type {
36
30
  SessionTelemetryRecord,
37
31
  SkillUsageRecord,
38
32
  } from "../types.js";
39
- import { readJsonl } from "../utils/jsonl.js";
40
33
 
41
34
  // ---------------------------------------------------------------------------
42
35
  // Helpers
@@ -201,42 +194,14 @@ export function assembleBundle(options: {
201
194
  telemetryLogPath?: string;
202
195
  evolutionAuditLogPath?: string;
203
196
  }): ContributionBundle {
204
- const {
205
- skillName,
206
- since,
207
- sanitizationLevel,
208
- queryLogPath = QUERY_LOG,
209
- skillLogPath = SKILL_LOG,
210
- telemetryLogPath = TELEMETRY_LOG,
211
- evolutionAuditLogPath = EVOLUTION_AUDIT_LOG,
212
- } = options;
213
-
214
- // Read from JSONL when custom (non-default) paths are provided (test isolation),
215
- // otherwise read from SQLite (production).
216
- const useJsonl =
217
- queryLogPath !== QUERY_LOG ||
218
- skillLogPath !== SKILL_LOG ||
219
- telemetryLogPath !== TELEMETRY_LOG ||
220
- evolutionAuditLogPath !== EVOLUTION_AUDIT_LOG;
221
-
222
- let allSkillRecords: SkillUsageRecord[];
223
- let allQueryRecords: QueryLogRecord[];
224
- let allTelemetryRecords: SessionTelemetryRecord[];
225
- let allEvolutionRecords: EvolutionAuditEntry[];
226
-
227
- if (useJsonl) {
228
- // JSONL fallback: only used when custom (non-default) log paths are provided (test isolation)
229
- allSkillRecords = readJsonl<SkillUsageRecord>(skillLogPath);
230
- allQueryRecords = readJsonl<QueryLogRecord>(queryLogPath);
231
- allTelemetryRecords = readJsonl<SessionTelemetryRecord>(telemetryLogPath);
232
- allEvolutionRecords = readJsonl<EvolutionAuditEntry>(evolutionAuditLogPath);
233
- } else {
234
- const db = getDb();
235
- allSkillRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
236
- allQueryRecords = queryQueryLog(db) as QueryLogRecord[];
237
- allTelemetryRecords = querySessionTelemetry(db) as SessionTelemetryRecord[];
238
- allEvolutionRecords = queryEvolutionAudit(db) as EvolutionAuditEntry[];
239
- }
197
+ const { skillName, since, sanitizationLevel } = options;
198
+
199
+ const db = getDb();
200
+ const allSkillRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
201
+ const allQueryRecords = queryQueryLog(db) as QueryLogRecord[];
202
+ const allTelemetryRecords = querySessionTelemetry(db) as SessionTelemetryRecord[];
203
+ // queryEvolutionAudit returns DESC order; reverse to ASC for chronological processing
204
+ const allEvolutionRecords = (queryEvolutionAudit(db) as EvolutionAuditEntry[]).toReversed();
240
205
 
241
206
  // Filter by skill and since
242
207
  const skillRecords = filterSince(
@@ -48,6 +48,15 @@ export interface PendingProposal {
48
48
  skill_name?: string;
49
49
  }
50
50
 
51
+ export interface RecentActivityItem {
52
+ timestamp: string;
53
+ session_id: string;
54
+ skill_name: string;
55
+ query: string;
56
+ triggered: boolean;
57
+ is_live: boolean;
58
+ }
59
+
51
60
  export interface SkillSummary {
52
61
  skill_name: string;
53
62
  skill_scope: string | null;
@@ -73,6 +82,8 @@ export interface OverviewPayload {
73
82
  };
74
83
  unmatched_queries: UnmatchedQuery[];
75
84
  pending_proposals: PendingProposal[];
85
+ active_sessions: number;
86
+ recent_activity: RecentActivityItem[];
76
87
  }
77
88
 
78
89
  export interface OverviewResponse {
@@ -179,6 +190,7 @@ export interface OrchestrateRunReport {
179
190
  deployed: number;
180
191
  watched: number;
181
192
  skipped: number;
193
+ auto_graded?: number;
182
194
  skill_actions: OrchestrateRunSkillAction[];
183
195
  }
184
196
 
@@ -36,7 +36,6 @@ import type {
36
36
  SessionTelemetryRecord,
37
37
  SkillUsageRecord,
38
38
  } from "../types.js";
39
- import { readJsonl } from "../utils/jsonl.js";
40
39
  import { detectAgent } from "../utils/llm-call.js";
41
40
  import {
42
41
  filterActionableQueryRecords,
@@ -464,31 +463,15 @@ export async function cliMain(): Promise<void> {
464
463
  return;
465
464
  }
466
465
 
467
- // --- Log-based mode (original behavior) ---
468
- const skillLogPath = values["skill-log"] ?? SKILL_LOG;
469
- const queryLogPath = values["query-log"] ?? QUERY_LOG;
470
- const telemetryLogPath = values["telemetry-log"] ?? TELEMETRY_LOG;
471
-
466
+ // --- SQLite-based mode ---
472
467
  let skillRecords: SkillUsageRecord[];
473
468
  let queryRecords: QueryLogRecord[];
474
469
  let telemetryRecords: SessionTelemetryRecord[];
475
470
 
476
- // SQLite is the default path; JSONL fallback only for custom --*-log overrides
477
- if (
478
- skillLogPath === SKILL_LOG &&
479
- queryLogPath === QUERY_LOG &&
480
- telemetryLogPath === TELEMETRY_LOG
481
- ) {
482
- const db = getDb();
483
- skillRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
484
- queryRecords = queryQueryLog(db) as QueryLogRecord[];
485
- telemetryRecords = querySessionTelemetry(db) as SessionTelemetryRecord[];
486
- } else {
487
- // test/custom-path fallback
488
- skillRecords = readJsonl<SkillUsageRecord>(skillLogPath);
489
- queryRecords = readJsonl<QueryLogRecord>(queryLogPath);
490
- telemetryRecords = readJsonl<SessionTelemetryRecord>(telemetryLogPath);
491
- }
471
+ const db = getDb();
472
+ skillRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
473
+ queryRecords = queryQueryLog(db) as QueryLogRecord[];
474
+ telemetryRecords = querySessionTelemetry(db) as SessionTelemetryRecord[];
492
475
 
493
476
  if (values["list-skills"]) {
494
477
  listSkills(skillRecords, queryRecords, telemetryRecords);
@@ -17,7 +17,6 @@ import { AGENT_CANDIDATES, TELEMETRY_LOG } from "../constants.js";
17
17
  import { getDb } from "../localdb/db.js";
18
18
  import { querySessionTelemetry, querySkillUsageRecords } from "../localdb/queries.js";
19
19
  import type { GradingResult, SessionTelemetryRecord, SkillUsageRecord } from "../types.js";
20
- import { readJsonl } from "../utils/jsonl.js";
21
20
  import { detectAgent as _detectAgent } from "../utils/llm-call.js";
22
21
  import { readExcerpt } from "../utils/transcript.js";
23
22
  import {
@@ -93,18 +92,9 @@ Options:
93
92
  console.error(`[INFO] Auto-grade via agent: ${agent}`);
94
93
 
95
94
  // --- Auto-find session ---
96
- const telemetryLog = values["telemetry-log"] ?? TELEMETRY_LOG;
97
- let telRecords: SessionTelemetryRecord[];
98
- let skillUsageRecords: SkillUsageRecord[];
99
- if (telemetryLog === TELEMETRY_LOG) {
100
- const db = getDb();
101
- telRecords = querySessionTelemetry(db) as SessionTelemetryRecord[];
102
- skillUsageRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
103
- } else {
104
- // Intentional JSONL fallback: custom --telemetry-log path overrides SQLite reads
105
- telRecords = readJsonl<SessionTelemetryRecord>(telemetryLog);
106
- skillUsageRecords = [];
107
- }
95
+ const db = getDb();
96
+ const telRecords = querySessionTelemetry(db) as SessionTelemetryRecord[];
97
+ const skillUsageRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
108
98
 
109
99
  let telemetry: SessionTelemetryRecord;
110
100
  let sessionId: string;
@@ -28,7 +28,6 @@ import type {
28
28
  SessionTelemetryRecord,
29
29
  SkillUsageRecord,
30
30
  } from "../types.js";
31
- import { readJsonl } from "../utils/jsonl.js";
32
31
  import {
33
32
  detectAgent as _detectAgent,
34
33
  stripMarkdownFences as _stripMarkdownFences,
@@ -804,18 +803,9 @@ Options:
804
803
  let transcriptPath = "";
805
804
  let sessionId = "unknown";
806
805
 
807
- const telemetryLog = values["telemetry-log"] ?? TELEMETRY_LOG;
808
- let telRecords: SessionTelemetryRecord[];
809
- let skillUsageRecords: SkillUsageRecord[];
810
- if (telemetryLog === TELEMETRY_LOG) {
811
- const db = getDb();
812
- telRecords = querySessionTelemetry(db) as SessionTelemetryRecord[];
813
- skillUsageRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
814
- } else {
815
- // Intentional JSONL fallback: custom --telemetry-log path overrides SQLite reads
816
- telRecords = readJsonl<SessionTelemetryRecord>(telemetryLog);
817
- skillUsageRecords = [];
818
- }
806
+ const db = getDb();
807
+ const telRecords = querySessionTelemetry(db) as SessionTelemetryRecord[];
808
+ const skillUsageRecords = querySkillUsageRecords(db) as SkillUsageRecord[];
819
809
 
820
810
  if (values.transcript) {
821
811
  transcriptPath = values.transcript;
@@ -19,7 +19,6 @@ import { basename, dirname, join } from "node:path";
19
19
 
20
20
  import { EVOLUTION_AUDIT_LOG, SELFTUNE_CONFIG_DIR } from "../constants.js";
21
21
  import type { PreToolUsePayload } from "../types.js";
22
- import { readJsonl } from "../utils/jsonl.js";
23
22
 
24
23
  // ---------------------------------------------------------------------------
25
24
  // Detection helpers (same pattern as skill-change-guard)
@@ -35,40 +34,31 @@ function extractSkillName(filePath: string): string {
35
34
  }
36
35
 
37
36
  // ---------------------------------------------------------------------------
38
- // Active monitoring check (SQLite-first — JSONL only for test/custom paths)
37
+ // Active monitoring check (always SQLite)
39
38
  // ---------------------------------------------------------------------------
40
39
 
41
40
  /**
42
41
  * Check if a skill has an active deployed evolution (meaning it's under monitoring).
43
- * SQLite is the default read path; JSONL is used only for test/custom-path overrides.
44
42
  *
45
43
  * A skill is "actively monitored" if its last audit action is "deployed".
46
44
  * If the last action is "rolled_back", it's no longer monitored.
47
45
  */
48
46
  export async function checkActiveMonitoring(
49
47
  skillName: string,
50
- auditLogPath: string,
48
+ _auditLogPath: string,
51
49
  ): Promise<boolean> {
52
- // SQLite is the default path; JSONL fallback only for non-default paths (tests)
53
- let entries: Array<{ skill_name?: string; action: string }>;
54
- if (auditLogPath === EVOLUTION_AUDIT_LOG) {
55
- const { getDb } = await import("../localdb/db.js");
56
- const { queryEvolutionAudit } = await import("../localdb/queries.js");
57
- const db = getDb();
58
- entries = queryEvolutionAudit(db, skillName) as Array<{
59
- skill_name?: string;
60
- action: string;
61
- }>;
62
- } else {
63
- // test/custom-path fallback
64
- entries = readJsonl<{ skill_name?: string; action: string }>(auditLogPath);
65
- }
66
-
67
- // Filter entries for this skill by skill_name field
68
- const skillEntries = entries.filter((e) => e.skill_name === skillName);
69
- if (skillEntries.length === 0) return false;
70
-
71
- const lastEntry = skillEntries[skillEntries.length - 1];
50
+ const { getDb } = await import("../localdb/db.js");
51
+ const { queryEvolutionAudit } = await import("../localdb/queries.js");
52
+ const db = getDb();
53
+ const entries = queryEvolutionAudit(db, skillName) as Array<{
54
+ skill_name?: string;
55
+ action: string;
56
+ }>;
57
+
58
+ if (entries.length === 0) return false;
59
+
60
+ // queryEvolutionAudit returns DESC order, so [0] is the most recent entry
61
+ const lastEntry = entries[0];
72
62
  return lastEntry.action === "deployed";
73
63
  }
74
64
 
@@ -21,7 +21,6 @@ import {
21
21
  reservePromptIdentity,
22
22
  } from "../normalization.js";
23
23
  import type { ImprovementSignalRecord, PromptSubmitPayload, QueryLogRecord } from "../types.js";
24
- import { appendJsonl } from "../utils/jsonl.js";
25
24
 
26
25
  // ---------------------------------------------------------------------------
27
26
  // Installed skill name cache
@@ -179,13 +178,6 @@ export async function processPrompt(
179
178
  /* hooks must never block */
180
179
  }
181
180
 
182
- // JSONL backup (best-effort, hooks must never block)
183
- try {
184
- appendJsonl(logPath, record);
185
- } catch {
186
- /* hooks must never block */
187
- }
188
-
189
181
  // Emit canonical prompt record (additive)
190
182
  const baseInput: CanonicalBaseInput = {
191
183
  platform: "claude_code",
@@ -20,7 +20,6 @@ import {
20
20
  getLatestPromptIdentity,
21
21
  } from "../normalization.js";
22
22
  import type { SessionTelemetryRecord, StopPayload } from "../types.js";
23
- import { appendJsonl } from "../utils/jsonl.js";
24
23
  import { parseTranscript } from "../utils/transcript.js";
25
24
 
26
25
  const LOCK_STALE_MS = 30 * 60 * 1000;
@@ -120,13 +119,6 @@ export async function processSessionStop(
120
119
  /* hooks must never block */
121
120
  }
122
121
 
123
- // JSONL backup (append-only, fail-open)
124
- try {
125
- appendJsonl(logPath, record);
126
- } catch {
127
- /* JSONL is a backup — never block on failure */
128
- }
129
-
130
122
  // Emit canonical session + execution fact records (additive)
131
123
  const baseInput: CanonicalBaseInput = {
132
124
  platform: "claude_code",
@@ -27,6 +27,11 @@ import { basename, join } from "node:path";
27
27
  import { parseArgs } from "node:util";
28
28
 
29
29
  import { CANONICAL_LOG, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
30
+ import {
31
+ writeQueryToDb,
32
+ writeSessionTelemetryToDb,
33
+ writeSkillUsageToDb,
34
+ } from "../localdb/direct-write.js";
30
35
  import {
31
36
  appendCanonicalRecords,
32
37
  buildCanonicalExecutionFact,
@@ -44,7 +49,7 @@ import type {
44
49
  SessionTelemetryRecord,
45
50
  SkillUsageRecord,
46
51
  } from "../types.js";
47
- import { appendJsonl, loadMarker, saveMarker } from "../utils/jsonl.js";
52
+ import { loadMarker, saveMarker } from "../utils/jsonl.js";
48
53
  import { extractActionableQueryText } from "../utils/query-filter.js";
49
54
  import {
50
55
  classifySkillPath,
@@ -490,7 +495,7 @@ export function ingestFile(
490
495
  query: prompt,
491
496
  source: "codex_rollout",
492
497
  };
493
- appendJsonl(queryLogPath, queryRecord, "all_queries");
498
+ writeQueryToDb(queryRecord);
494
499
  }
495
500
 
496
501
  // Write telemetry — explicitly select SessionTelemetryRecord fields
@@ -513,7 +518,7 @@ export function ingestFile(
513
518
  output_tokens: parsed.output_tokens,
514
519
  rollout_path: parsed.rollout_path,
515
520
  };
516
- appendJsonl(telemetryLogPath, telemetry, "session_telemetry");
521
+ writeSessionTelemetryToDb(telemetry);
517
522
 
518
523
  // Write skill triggers
519
524
  for (const skillName of skills) {
@@ -537,7 +542,7 @@ export function ingestFile(
537
542
  triggered: true,
538
543
  source: isExplicit ? "codex_rollout_explicit" : "codex_rollout",
539
544
  };
540
- appendJsonl(skillLogPath, skillRecord, "skill_usage");
545
+ writeSkillUsageToDb(skillRecord);
541
546
  }
542
547
 
543
548
  // --- Canonical normalization records (additive) ---
@@ -11,16 +11,19 @@
11
11
  * The wrapper:
12
12
  * 1. Runs `codex exec --json <your args>` as a subprocess
13
13
  * 2. Streams stdout (JSONL events) to your terminal in real time
14
- * 3. Parses events and writes to:
15
- * ~/.claude/all_queries_log.jsonl
16
- * ~/.claude/session_telemetry_log.jsonl
17
- * ~/.claude/skill_usage_log.jsonl
14
+ * 3. Writes to SQLite via writeQueryToDb, writeSessionTelemetryToDb,
15
+ * writeSkillUsageToDb (Phase 3: JSONL writes removed)
18
16
  */
19
17
 
20
18
  import { homedir } from "node:os";
21
19
  import { join } from "node:path";
22
20
 
23
21
  import { CANONICAL_LOG, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
22
+ import {
23
+ writeQueryToDb,
24
+ writeSessionTelemetryToDb,
25
+ writeSkillUsageToDb,
26
+ } from "../localdb/direct-write.js";
24
27
  import {
25
28
  appendCanonicalRecords,
26
29
  buildCanonicalExecutionFact,
@@ -38,7 +41,6 @@ import type {
38
41
  SessionTelemetryRecord,
39
42
  SkillUsageRecord,
40
43
  } from "../types.js";
41
- import { appendJsonl } from "../utils/jsonl.js";
42
44
  import {
43
45
  classifySkillPath,
44
46
  containsWholeSkillMention,
@@ -240,8 +242,8 @@ export function parseJsonlStream(lines: string[], skillNames: Set<string>): Pars
240
242
  };
241
243
  }
242
244
 
243
- /** Append the user prompt to all_queries_log.jsonl. */
244
- export function logQuery(prompt: string, sessionId: string, logPath: string = QUERY_LOG): void {
245
+ /** Write the user prompt to SQLite. */
246
+ export function logQuery(prompt: string, sessionId: string, _logPath: string = QUERY_LOG): void {
245
247
  if (!prompt || prompt.length < 4) return;
246
248
  const record: QueryLogRecord = {
247
249
  timestamp: new Date().toISOString(),
@@ -249,16 +251,16 @@ export function logQuery(prompt: string, sessionId: string, logPath: string = QU
249
251
  query: prompt,
250
252
  source: "codex",
251
253
  };
252
- appendJsonl(logPath, record);
254
+ writeQueryToDb(record);
253
255
  }
254
256
 
255
- /** Append session metrics to session_telemetry_log.jsonl. */
257
+ /** Write session metrics to SQLite. */
256
258
  export function logTelemetry(
257
259
  metrics: Omit<ParsedCodexStream, "thread_id">,
258
260
  prompt: string,
259
261
  sessionId: string,
260
262
  cwd: string,
261
- logPath: string = TELEMETRY_LOG,
263
+ _logPath: string = TELEMETRY_LOG,
262
264
  ): void {
263
265
  const record: SessionTelemetryRecord = {
264
266
  timestamp: new Date().toISOString(),
@@ -269,10 +271,10 @@ export function logTelemetry(
269
271
  source: "codex",
270
272
  ...metrics,
271
273
  };
272
- appendJsonl(logPath, record);
274
+ writeSessionTelemetryToDb(record);
273
275
  }
274
276
 
275
- /** Append a skill trigger to skill_usage_log.jsonl. */
277
+ /** Write a skill trigger to SQLite. */
276
278
  export function logSkillTrigger(
277
279
  skillName: string,
278
280
  prompt: string,
@@ -300,7 +302,7 @@ export function logSkillTrigger(
300
302
  triggered: true,
301
303
  source: "codex",
302
304
  };
303
- appendJsonl(logPath, record);
305
+ writeSkillUsageToDb(record);
304
306
  }
305
307
 
306
308
  /** Build canonical records from a wrapper session. */