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.
- package/apps/local-dashboard/dist/assets/index-BMIS6uUh.css +2 -0
- package/apps/local-dashboard/dist/assets/index-DIrdlu2_.js +16 -0
- package/apps/local-dashboard/dist/index.html +2 -2
- package/cli/selftune/activation-rules.ts +24 -48
- package/cli/selftune/constants.ts +7 -0
- package/cli/selftune/contribute/bundle.ts +9 -44
- package/cli/selftune/dashboard-contract.ts +12 -0
- package/cli/selftune/eval/hooks-to-evals.ts +5 -22
- package/cli/selftune/grading/auto-grade.ts +3 -13
- package/cli/selftune/grading/grade-session.ts +3 -13
- package/cli/selftune/hooks/evolution-guard.ts +14 -24
- package/cli/selftune/hooks/prompt-log.ts +0 -8
- package/cli/selftune/hooks/session-stop.ts +0 -8
- package/cli/selftune/ingestors/codex-rollout.ts +9 -4
- package/cli/selftune/ingestors/codex-wrapper.ts +15 -13
- package/cli/selftune/ingestors/openclaw-ingest.ts +24 -5
- package/cli/selftune/ingestors/opencode-ingest.ts +9 -4
- package/cli/selftune/localdb/queries.ts +57 -0
- package/cli/selftune/monitoring/watch.ts +7 -22
- package/cli/selftune/normalization.ts +2 -23
- package/cli/selftune/orchestrate.ts +213 -14
- package/cli/selftune/schedule.ts +51 -5
- package/cli/selftune/utils/jsonl.ts +2 -0
- package/package.json +3 -1
- package/packages/ui/src/components/RecentActivityFeed.tsx +86 -0
- package/packages/ui/src/components/index.ts +1 -0
- package/packages/ui/src/components/section-cards.tsx +13 -0
- package/skill/SKILL.md +1 -1
- package/skill/Workflows/Orchestrate.md +11 -7
- package/skill/Workflows/Schedule.md +11 -0
- package/skill/references/logs.md +22 -21
- package/apps/local-dashboard/dist/assets/index-4_dAY17K.js +0 -16
- 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-
|
|
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-
|
|
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
|
-
*
|
|
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 {
|
|
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
|
|
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
|
|
26
|
+
// Count queries for this session
|
|
31
27
|
let queries: Array<{ session_id: string; query: string }>;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
38
|
+
// Count skill usages for this session
|
|
48
39
|
let skillUsages: Array<{ session_id: string }>;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
108
|
+
// Check last evolution timestamp
|
|
128
109
|
let auditEntries: Array<{ timestamp: string; action: string }>;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
// ---
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
808
|
-
|
|
809
|
-
|
|
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
|
|
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
|
-
|
|
48
|
+
_auditLogPath: string,
|
|
51
49
|
): Promise<boolean> {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
15
|
-
*
|
|
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
|
-
/**
|
|
244
|
-
export function logQuery(prompt: string, sessionId: string,
|
|
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
|
-
|
|
254
|
+
writeQueryToDb(record);
|
|
253
255
|
}
|
|
254
256
|
|
|
255
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
274
|
+
writeSessionTelemetryToDb(record);
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
/**
|
|
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
|
-
|
|
305
|
+
writeSkillUsageToDb(record);
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
/** Build canonical records from a wrapper session. */
|