selftune 0.2.6 → 0.2.8
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/README.md +1 -0
- package/apps/local-dashboard/dist/assets/index-Bk9vSHHd.js +15 -0
- package/apps/local-dashboard/dist/assets/index-CRtLkBTi.css +1 -0
- package/apps/local-dashboard/dist/assets/vendor-react-BQH_6WrG.js +60 -0
- package/apps/local-dashboard/dist/assets/{vendor-table-B7VF2Ipl.js → vendor-table-dK1QMLq9.js} +1 -1
- package/apps/local-dashboard/dist/assets/{vendor-ui-r2k_Ku_V.js → vendor-ui-CO2mrx6e.js} +60 -65
- package/apps/local-dashboard/dist/index.html +5 -5
- package/cli/selftune/activation-rules.ts +30 -9
- package/cli/selftune/agent-guidance.ts +96 -0
- package/cli/selftune/alpha-identity.ts +157 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +151 -0
- package/cli/selftune/alpha-upload/client.ts +113 -0
- package/cli/selftune/alpha-upload/flush.ts +191 -0
- package/cli/selftune/alpha-upload/index.ts +194 -0
- package/cli/selftune/alpha-upload/queue.ts +252 -0
- package/cli/selftune/alpha-upload/stage-canonical.ts +242 -0
- package/cli/selftune/alpha-upload-contract.ts +52 -0
- package/cli/selftune/auth/device-code.ts +110 -0
- package/cli/selftune/auto-update.ts +130 -0
- package/cli/selftune/badge/badge.ts +19 -9
- package/cli/selftune/canonical-export.ts +16 -3
- package/cli/selftune/constants.ts +28 -8
- package/cli/selftune/contribute/bundle.ts +32 -5
- package/cli/selftune/dashboard-contract.ts +32 -1
- package/cli/selftune/dashboard-server.ts +256 -692
- package/cli/selftune/dashboard.ts +1 -1
- package/cli/selftune/eval/baseline.ts +11 -7
- package/cli/selftune/eval/hooks-to-evals.ts +27 -9
- package/cli/selftune/eval/synthetic-evals.ts +54 -1
- package/cli/selftune/evolution/audit.ts +24 -19
- package/cli/selftune/evolution/constitutional.ts +176 -0
- package/cli/selftune/evolution/evidence.ts +18 -13
- package/cli/selftune/evolution/evolve-body.ts +104 -7
- package/cli/selftune/evolution/evolve.ts +195 -22
- package/cli/selftune/evolution/propose-body.ts +18 -1
- package/cli/selftune/evolution/propose-description.ts +27 -2
- package/cli/selftune/evolution/rollback.ts +11 -15
- package/cli/selftune/export.ts +84 -0
- package/cli/selftune/grading/auto-grade.ts +13 -4
- package/cli/selftune/grading/grade-session.ts +16 -6
- package/cli/selftune/hooks/evolution-guard.ts +26 -9
- package/cli/selftune/hooks/prompt-log.ts +23 -9
- package/cli/selftune/hooks/session-stop.ts +78 -15
- package/cli/selftune/hooks/skill-eval.ts +189 -10
- package/cli/selftune/index.ts +274 -2
- package/cli/selftune/ingestors/claude-replay.ts +48 -21
- package/cli/selftune/init.ts +249 -47
- package/cli/selftune/last.ts +7 -7
- package/cli/selftune/localdb/db.ts +90 -10
- package/cli/selftune/localdb/direct-write.ts +531 -0
- package/cli/selftune/localdb/materialize.ts +296 -42
- package/cli/selftune/localdb/queries.ts +325 -32
- package/cli/selftune/localdb/schema.ts +109 -0
- package/cli/selftune/monitoring/watch.ts +26 -8
- package/cli/selftune/normalization.ts +85 -15
- package/cli/selftune/observability.ts +248 -2
- package/cli/selftune/orchestrate.ts +165 -20
- package/cli/selftune/quickstart.ts +34 -10
- package/cli/selftune/repair/skill-usage.ts +12 -2
- package/cli/selftune/routes/actions.ts +77 -0
- package/cli/selftune/routes/badge.ts +66 -0
- package/cli/selftune/routes/doctor.ts +12 -0
- package/cli/selftune/routes/index.ts +14 -0
- package/cli/selftune/routes/orchestrate-runs.ts +13 -0
- package/cli/selftune/routes/overview.ts +14 -0
- package/cli/selftune/routes/report.ts +293 -0
- package/cli/selftune/routes/skill-report.ts +230 -0
- package/cli/selftune/status.ts +203 -7
- package/cli/selftune/sync.ts +13 -1
- package/cli/selftune/types.ts +50 -0
- package/cli/selftune/utils/jsonl.ts +58 -1
- package/cli/selftune/utils/selftune-meta.ts +38 -0
- package/cli/selftune/utils/skill-log.ts +30 -4
- package/cli/selftune/utils/transcript.ts +15 -0
- package/cli/selftune/workflows/workflows.ts +7 -6
- package/package.json +10 -6
- package/packages/telemetry-contract/fixtures/complete-push.ts +184 -0
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +58 -0
- package/packages/telemetry-contract/fixtures/golden.json +1 -0
- package/packages/telemetry-contract/fixtures/index.ts +4 -0
- package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +40 -0
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +79 -0
- package/packages/telemetry-contract/package.json +6 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +215 -0
- package/packages/telemetry-contract/src/types.ts +3 -1
- package/packages/telemetry-contract/src/validators.ts +3 -1
- package/packages/telemetry-contract/tests/compatibility.test.ts +144 -0
- package/packages/ui/package.json +4 -0
- package/packages/ui/src/components/ActivityTimeline.tsx +61 -29
- package/packages/ui/src/components/section-cards.tsx +31 -14
- package/packages/ui/src/types.ts +1 -0
- package/skill/SKILL.md +214 -174
- package/skill/Workflows/AlphaUpload.md +45 -0
- package/skill/Workflows/Baseline.md +18 -12
- package/skill/Workflows/Composability.md +3 -3
- package/skill/Workflows/Dashboard.md +44 -91
- package/skill/Workflows/Doctor.md +93 -66
- package/skill/Workflows/Evals.md +49 -40
- package/skill/Workflows/Evolve.md +76 -28
- package/skill/Workflows/EvolveBody.md +37 -38
- package/skill/Workflows/Initialize.md +172 -26
- package/skill/Workflows/Orchestrate.md +11 -2
- package/skill/Workflows/Sync.md +23 -0
- package/skill/Workflows/Watch.md +2 -5
- package/skill/agents/diagnosis-analyst.md +163 -0
- package/skill/agents/evolution-reviewer.md +149 -0
- package/skill/agents/integration-guide.md +154 -0
- package/skill/agents/pattern-analyst.md +149 -0
- package/skill/assets/multi-skill-settings.json +1 -1
- package/skill/assets/single-skill-settings.json +1 -1
- package/skill/references/interactive-config.md +39 -0
- package/skill/references/invocation-taxonomy.md +34 -0
- package/skill/references/logs.md +9 -1
- package/skill/references/setup-patterns.md +3 -3
- package/skill/settings_snippet.json +1 -1
- package/apps/local-dashboard/dist/assets/index-C75H1Q3n.css +0 -1
- package/apps/local-dashboard/dist/assets/index-axE4kz3Q.js +0 -15
- package/apps/local-dashboard/dist/assets/vendor-react-U7zYD9Rg.js +0 -60
|
@@ -45,13 +45,13 @@ export function getOverviewPayload(db: Database): OverviewPayload {
|
|
|
45
45
|
// Skill usage (bounded to most recent 2000)
|
|
46
46
|
const skillRows = db
|
|
47
47
|
.query(
|
|
48
|
-
`SELECT
|
|
49
|
-
FROM
|
|
50
|
-
ORDER BY
|
|
48
|
+
`SELECT occurred_at, session_id, skill_name, skill_path, query, triggered, source
|
|
49
|
+
FROM skill_invocations
|
|
50
|
+
ORDER BY occurred_at DESC
|
|
51
51
|
LIMIT 2000`,
|
|
52
52
|
)
|
|
53
53
|
.all() as Array<{
|
|
54
|
-
|
|
54
|
+
occurred_at: string;
|
|
55
55
|
session_id: string;
|
|
56
56
|
skill_name: string;
|
|
57
57
|
skill_path: string;
|
|
@@ -61,7 +61,7 @@ export function getOverviewPayload(db: Database): OverviewPayload {
|
|
|
61
61
|
}>;
|
|
62
62
|
|
|
63
63
|
const skills = skillRows.map((row) => ({
|
|
64
|
-
timestamp: row.
|
|
64
|
+
timestamp: row.occurred_at,
|
|
65
65
|
session_id: row.session_id,
|
|
66
66
|
skill_name: row.skill_name,
|
|
67
67
|
skill_path: row.skill_path,
|
|
@@ -73,7 +73,7 @@ export function getOverviewPayload(db: Database): OverviewPayload {
|
|
|
73
73
|
// Evolution audit (bounded to most recent 500)
|
|
74
74
|
const evolution = db
|
|
75
75
|
.query(
|
|
76
|
-
`SELECT timestamp, proposal_id, action, details
|
|
76
|
+
`SELECT timestamp, proposal_id, skill_name, action, details
|
|
77
77
|
FROM evolution_audit
|
|
78
78
|
ORDER BY timestamp DESC
|
|
79
79
|
LIMIT 500`,
|
|
@@ -81,6 +81,7 @@ export function getOverviewPayload(db: Database): OverviewPayload {
|
|
|
81
81
|
.all() as Array<{
|
|
82
82
|
timestamp: string;
|
|
83
83
|
proposal_id: string;
|
|
84
|
+
skill_name: string | null;
|
|
84
85
|
action: string;
|
|
85
86
|
details: string;
|
|
86
87
|
}>;
|
|
@@ -90,7 +91,7 @@ export function getOverviewPayload(db: Database): OverviewPayload {
|
|
|
90
91
|
.query(
|
|
91
92
|
`SELECT
|
|
92
93
|
(SELECT COUNT(*) FROM session_telemetry) as telemetry,
|
|
93
|
-
(SELECT COUNT(*) FROM
|
|
94
|
+
(SELECT COUNT(*) FROM skill_invocations) as skills,
|
|
94
95
|
(SELECT COUNT(*) FROM evolution_audit) as evolution,
|
|
95
96
|
(SELECT COUNT(*) FROM evolution_evidence) as evidence,
|
|
96
97
|
(SELECT COUNT(*) FROM sessions) as sessions,
|
|
@@ -105,18 +106,18 @@ export function getOverviewPayload(db: Database): OverviewPayload {
|
|
|
105
106
|
prompts: number;
|
|
106
107
|
};
|
|
107
108
|
|
|
108
|
-
// Unmatched queries:
|
|
109
|
+
// Unmatched queries: skill_invocations entries where triggered = 0 and no other
|
|
109
110
|
// record for the same query text triggered
|
|
110
111
|
const unmatchedRows = db
|
|
111
112
|
.query(
|
|
112
|
-
`SELECT
|
|
113
|
-
FROM
|
|
114
|
-
WHERE
|
|
113
|
+
`SELECT si.occurred_at AS timestamp, si.session_id, si.query
|
|
114
|
+
FROM skill_invocations si
|
|
115
|
+
WHERE si.triggered = 0
|
|
115
116
|
AND NOT EXISTS (
|
|
116
|
-
SELECT 1 FROM
|
|
117
|
-
WHERE
|
|
117
|
+
SELECT 1 FROM skill_invocations si2
|
|
118
|
+
WHERE si2.query = si.query AND si2.triggered = 1
|
|
118
119
|
)
|
|
119
|
-
ORDER BY
|
|
120
|
+
ORDER BY si.occurred_at DESC
|
|
120
121
|
LIMIT 500`,
|
|
121
122
|
)
|
|
122
123
|
.all() as Array<{ timestamp: string; session_id: string; query: string }>;
|
|
@@ -144,7 +145,7 @@ export function getSkillReportPayload(db: Database, skillName: string): SkillRep
|
|
|
144
145
|
`SELECT
|
|
145
146
|
COUNT(*) as total_checks,
|
|
146
147
|
SUM(CASE WHEN triggered = 1 THEN 1 ELSE 0 END) as triggered_count
|
|
147
|
-
FROM
|
|
148
|
+
FROM skill_invocations
|
|
148
149
|
WHERE skill_name = ?`,
|
|
149
150
|
)
|
|
150
151
|
.get(skillName) as { total_checks: number; triggered_count: number };
|
|
@@ -156,14 +157,14 @@ export function getSkillReportPayload(db: Database, skillName: string): SkillRep
|
|
|
156
157
|
// Recent invocations (last 100)
|
|
157
158
|
const invocationRows = db
|
|
158
159
|
.query(
|
|
159
|
-
`SELECT
|
|
160
|
-
FROM
|
|
160
|
+
`SELECT occurred_at, session_id, query, triggered, source
|
|
161
|
+
FROM skill_invocations
|
|
161
162
|
WHERE skill_name = ?
|
|
162
|
-
ORDER BY
|
|
163
|
+
ORDER BY occurred_at DESC
|
|
163
164
|
LIMIT 100`,
|
|
164
165
|
)
|
|
165
166
|
.all(skillName) as Array<{
|
|
166
|
-
|
|
167
|
+
occurred_at: string;
|
|
167
168
|
session_id: string;
|
|
168
169
|
query: string;
|
|
169
170
|
triggered: number;
|
|
@@ -171,7 +172,7 @@ export function getSkillReportPayload(db: Database, skillName: string): SkillRep
|
|
|
171
172
|
}>;
|
|
172
173
|
|
|
173
174
|
const recent_invocations = invocationRows.map((row) => ({
|
|
174
|
-
timestamp: row.
|
|
175
|
+
timestamp: row.occurred_at,
|
|
175
176
|
session_id: row.session_id,
|
|
176
177
|
query: row.query,
|
|
177
178
|
triggered: row.triggered === 1,
|
|
@@ -218,7 +219,7 @@ export function getSkillReportPayload(db: Database, skillName: string): SkillRep
|
|
|
218
219
|
|
|
219
220
|
// Unique sessions count
|
|
220
221
|
const sessionsRow = db
|
|
221
|
-
.query(`SELECT COUNT(DISTINCT session_id) as c FROM
|
|
222
|
+
.query(`SELECT COUNT(DISTINCT session_id) as c FROM skill_invocations WHERE skill_name = ?`)
|
|
222
223
|
.get(skillName) as { c: number };
|
|
223
224
|
|
|
224
225
|
return {
|
|
@@ -241,16 +242,21 @@ export function getSkillsList(db: Database): SkillSummary[] {
|
|
|
241
242
|
const rows = db
|
|
242
243
|
.query(
|
|
243
244
|
`SELECT
|
|
244
|
-
|
|
245
|
-
(
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
si.skill_name,
|
|
246
|
+
COALESCE(
|
|
247
|
+
(SELECT s2.skill_scope FROM skill_invocations s2
|
|
248
|
+
WHERE s2.skill_name = si.skill_name AND s2.skill_scope IS NOT NULL
|
|
249
|
+
ORDER BY s2.occurred_at DESC LIMIT 1),
|
|
250
|
+
(SELECT su.skill_scope FROM skill_usage su
|
|
251
|
+
WHERE su.skill_name = si.skill_name AND su.skill_scope IS NOT NULL
|
|
252
|
+
ORDER BY su.timestamp DESC LIMIT 1)
|
|
253
|
+
) as skill_scope,
|
|
248
254
|
COUNT(*) as total_checks,
|
|
249
|
-
SUM(CASE WHEN
|
|
250
|
-
COUNT(DISTINCT
|
|
251
|
-
MAX(
|
|
252
|
-
FROM
|
|
253
|
-
GROUP BY
|
|
255
|
+
SUM(CASE WHEN si.triggered = 1 THEN 1 ELSE 0 END) as triggered_count,
|
|
256
|
+
COUNT(DISTINCT si.session_id) as unique_sessions,
|
|
257
|
+
MAX(si.occurred_at) as last_seen
|
|
258
|
+
FROM skill_invocations si
|
|
259
|
+
GROUP BY si.skill_name
|
|
254
260
|
ORDER BY total_checks DESC`,
|
|
255
261
|
)
|
|
256
262
|
.all() as Array<{
|
|
@@ -354,9 +360,296 @@ export function getOrchestrateRuns(db: Database, limit = 20): OrchestrateRunRepo
|
|
|
354
360
|
}));
|
|
355
361
|
}
|
|
356
362
|
|
|
363
|
+
// -- Generic read queries (Phase 3: replace readJsonl calls) ------------------
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Read all session telemetry records from SQLite.
|
|
367
|
+
* Replaces: readJsonl<SessionTelemetryRecord>(TELEMETRY_LOG)
|
|
368
|
+
*/
|
|
369
|
+
export function querySessionTelemetry(db: Database): Array<{
|
|
370
|
+
timestamp: string;
|
|
371
|
+
session_id: string;
|
|
372
|
+
cwd: string;
|
|
373
|
+
transcript_path: string;
|
|
374
|
+
tool_calls: Record<string, number>;
|
|
375
|
+
total_tool_calls: number;
|
|
376
|
+
bash_commands: string[];
|
|
377
|
+
skills_triggered: string[];
|
|
378
|
+
skills_invoked?: string[];
|
|
379
|
+
assistant_turns: number;
|
|
380
|
+
errors_encountered: number;
|
|
381
|
+
transcript_chars: number;
|
|
382
|
+
last_user_query: string;
|
|
383
|
+
source?: string;
|
|
384
|
+
input_tokens?: number;
|
|
385
|
+
output_tokens?: number;
|
|
386
|
+
}> {
|
|
387
|
+
const rows = db.query(`SELECT * FROM session_telemetry ORDER BY timestamp DESC`).all() as Array<
|
|
388
|
+
Record<string, unknown>
|
|
389
|
+
>;
|
|
390
|
+
return rows.map((r) => ({
|
|
391
|
+
timestamp: r.timestamp as string,
|
|
392
|
+
session_id: r.session_id as string,
|
|
393
|
+
cwd: r.cwd as string,
|
|
394
|
+
transcript_path: r.transcript_path as string,
|
|
395
|
+
tool_calls: (safeParseJson(r.tool_calls_json as string) as Record<string, number>) ?? {},
|
|
396
|
+
total_tool_calls: r.total_tool_calls as number,
|
|
397
|
+
bash_commands: safeParseJsonArray<string>(r.bash_commands_json as string),
|
|
398
|
+
skills_triggered: safeParseJsonArray<string>(r.skills_triggered_json as string),
|
|
399
|
+
skills_invoked: r.skills_invoked_json
|
|
400
|
+
? safeParseJsonArray<string>(r.skills_invoked_json as string)
|
|
401
|
+
: undefined,
|
|
402
|
+
assistant_turns: r.assistant_turns as number,
|
|
403
|
+
errors_encountered: r.errors_encountered as number,
|
|
404
|
+
transcript_chars: (r.transcript_chars as number) ?? 0,
|
|
405
|
+
last_user_query: (r.last_user_query as string) ?? "",
|
|
406
|
+
source: r.source as string | undefined,
|
|
407
|
+
input_tokens: r.input_tokens as number | undefined,
|
|
408
|
+
output_tokens: r.output_tokens as number | undefined,
|
|
409
|
+
}));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Read all skill invocation records from SQLite.
|
|
414
|
+
* Replaces: readEffectiveSkillUsageRecords()
|
|
415
|
+
*/
|
|
416
|
+
export function querySkillRecords(db: Database): Array<{
|
|
417
|
+
timestamp: string;
|
|
418
|
+
session_id: string;
|
|
419
|
+
skill_name: string;
|
|
420
|
+
skill_path: string;
|
|
421
|
+
skill_scope?: string;
|
|
422
|
+
query: string;
|
|
423
|
+
triggered: boolean;
|
|
424
|
+
source?: string;
|
|
425
|
+
}> {
|
|
426
|
+
const rows = db
|
|
427
|
+
.query(
|
|
428
|
+
`SELECT occurred_at, session_id, skill_name, skill_path, skill_scope, query, triggered, source
|
|
429
|
+
FROM skill_invocations ORDER BY occurred_at DESC`,
|
|
430
|
+
)
|
|
431
|
+
.all() as Array<Record<string, unknown>>;
|
|
432
|
+
return rows.map((r) => ({
|
|
433
|
+
timestamp: r.occurred_at as string,
|
|
434
|
+
session_id: r.session_id as string,
|
|
435
|
+
skill_name: r.skill_name as string,
|
|
436
|
+
skill_path: r.skill_path as string,
|
|
437
|
+
skill_scope: r.skill_scope as string | undefined,
|
|
438
|
+
query: r.query as string,
|
|
439
|
+
triggered: (r.triggered as number) === 1,
|
|
440
|
+
source: r.source as string | undefined,
|
|
441
|
+
}));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** @deprecated Use querySkillRecords instead. Kept for backward compatibility. */
|
|
445
|
+
export const querySkillUsageRecords = querySkillRecords;
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Read all query log records from SQLite.
|
|
449
|
+
* Replaces: readJsonl<QueryLogRecord>(QUERY_LOG)
|
|
450
|
+
*/
|
|
451
|
+
export function queryQueryLog(db: Database): Array<{
|
|
452
|
+
timestamp: string;
|
|
453
|
+
session_id: string;
|
|
454
|
+
query: string;
|
|
455
|
+
source?: string;
|
|
456
|
+
}> {
|
|
457
|
+
return db
|
|
458
|
+
.query(`SELECT timestamp, session_id, query, source FROM queries ORDER BY timestamp DESC`)
|
|
459
|
+
.all() as Array<{ timestamp: string; session_id: string; query: string; source?: string }>;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Read all evolution audit entries from SQLite.
|
|
464
|
+
* Replaces: readJsonl<EvolutionAuditEntry>(EVOLUTION_AUDIT_LOG)
|
|
465
|
+
*/
|
|
466
|
+
export function queryEvolutionAudit(
|
|
467
|
+
db: Database,
|
|
468
|
+
skillName?: string,
|
|
469
|
+
): Array<{
|
|
470
|
+
timestamp: string;
|
|
471
|
+
proposal_id: string;
|
|
472
|
+
skill_name?: string;
|
|
473
|
+
action: string;
|
|
474
|
+
details: string;
|
|
475
|
+
eval_snapshot?: Record<string, unknown>;
|
|
476
|
+
}> {
|
|
477
|
+
const sql = skillName
|
|
478
|
+
? `SELECT * FROM evolution_audit
|
|
479
|
+
WHERE skill_name = ?
|
|
480
|
+
OR (skill_name IS NULL AND proposal_id LIKE 'evo-' || ? || '-%')
|
|
481
|
+
ORDER BY timestamp DESC`
|
|
482
|
+
: `SELECT * FROM evolution_audit ORDER BY timestamp DESC`;
|
|
483
|
+
const rows = (skillName ? db.query(sql).all(skillName, skillName) : db.query(sql).all()) as Array<
|
|
484
|
+
Record<string, unknown>
|
|
485
|
+
>;
|
|
486
|
+
return rows.map((r) => ({
|
|
487
|
+
timestamp: r.timestamp as string,
|
|
488
|
+
proposal_id: r.proposal_id as string,
|
|
489
|
+
skill_name: r.skill_name as string | undefined,
|
|
490
|
+
action: r.action as string,
|
|
491
|
+
details: r.details as string,
|
|
492
|
+
eval_snapshot: r.eval_snapshot_json
|
|
493
|
+
? (safeParseJson(r.eval_snapshot_json as string) as Record<string, unknown>)
|
|
494
|
+
: undefined,
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Read all evolution evidence entries from SQLite.
|
|
500
|
+
* Replaces: readEvidenceTrail() / readJsonl<EvolutionEvidenceEntry>(EVOLUTION_EVIDENCE_LOG)
|
|
501
|
+
*/
|
|
502
|
+
export function queryEvolutionEvidence(
|
|
503
|
+
db: Database,
|
|
504
|
+
skillName?: string,
|
|
505
|
+
): Array<{
|
|
506
|
+
timestamp: string;
|
|
507
|
+
proposal_id: string;
|
|
508
|
+
skill_name: string;
|
|
509
|
+
skill_path: string;
|
|
510
|
+
target: string;
|
|
511
|
+
stage: string;
|
|
512
|
+
rationale?: string;
|
|
513
|
+
confidence?: number;
|
|
514
|
+
details?: string;
|
|
515
|
+
original_text?: string;
|
|
516
|
+
proposed_text?: string;
|
|
517
|
+
eval_set?: Record<string, unknown>[];
|
|
518
|
+
validation?: Record<string, unknown>;
|
|
519
|
+
}> {
|
|
520
|
+
const sql = skillName
|
|
521
|
+
? `SELECT * FROM evolution_evidence WHERE skill_name = ? ORDER BY timestamp DESC`
|
|
522
|
+
: `SELECT * FROM evolution_evidence ORDER BY timestamp DESC`;
|
|
523
|
+
const rows = (skillName ? db.query(sql).all(skillName) : db.query(sql).all()) as Array<
|
|
524
|
+
Record<string, unknown>
|
|
525
|
+
>;
|
|
526
|
+
return rows.map((r) => ({
|
|
527
|
+
timestamp: r.timestamp as string,
|
|
528
|
+
proposal_id: r.proposal_id as string,
|
|
529
|
+
skill_name: r.skill_name as string,
|
|
530
|
+
skill_path: r.skill_path as string,
|
|
531
|
+
target: r.target as string,
|
|
532
|
+
stage: r.stage as string,
|
|
533
|
+
rationale: r.rationale as string | undefined,
|
|
534
|
+
confidence: r.confidence as number | undefined,
|
|
535
|
+
details: r.details as string | undefined,
|
|
536
|
+
original_text: r.original_text as string | undefined,
|
|
537
|
+
proposed_text: r.proposed_text as string | undefined,
|
|
538
|
+
eval_set: r.eval_set_json
|
|
539
|
+
? safeParseJsonArray<Record<string, unknown>>(r.eval_set_json as string)
|
|
540
|
+
: undefined,
|
|
541
|
+
validation: r.validation_json
|
|
542
|
+
? (safeParseJson(r.validation_json as string) as Record<string, unknown>)
|
|
543
|
+
: undefined,
|
|
544
|
+
}));
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Read improvement signals from SQLite.
|
|
549
|
+
* Replaces: readJsonl<ImprovementSignalRecord>(SIGNAL_LOG)
|
|
550
|
+
*/
|
|
551
|
+
export function queryImprovementSignals(
|
|
552
|
+
db: Database,
|
|
553
|
+
consumedOnly?: boolean,
|
|
554
|
+
): Array<{
|
|
555
|
+
timestamp: string;
|
|
556
|
+
session_id: string;
|
|
557
|
+
query: string;
|
|
558
|
+
signal_type: string;
|
|
559
|
+
mentioned_skill?: string;
|
|
560
|
+
consumed: boolean;
|
|
561
|
+
consumed_at?: string;
|
|
562
|
+
consumed_by_run?: string;
|
|
563
|
+
}> {
|
|
564
|
+
const where =
|
|
565
|
+
consumedOnly === undefined ? "" : consumedOnly ? " WHERE consumed = 1" : " WHERE consumed = 0";
|
|
566
|
+
const rows = db
|
|
567
|
+
.query(`SELECT * FROM improvement_signals${where} ORDER BY timestamp DESC`)
|
|
568
|
+
.all() as Array<Record<string, unknown>>;
|
|
569
|
+
return rows.map((r) => ({
|
|
570
|
+
timestamp: r.timestamp as string,
|
|
571
|
+
session_id: r.session_id as string,
|
|
572
|
+
query: r.query as string,
|
|
573
|
+
signal_type: r.signal_type as string,
|
|
574
|
+
mentioned_skill: r.mentioned_skill as string | undefined,
|
|
575
|
+
consumed: (r.consumed as number) === 1,
|
|
576
|
+
consumed_at: r.consumed_at as string | undefined,
|
|
577
|
+
consumed_by_run: r.consumed_by_run as string | undefined,
|
|
578
|
+
}));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// -- Alpha upload query helpers -----------------------------------------------
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Get the most recent failed queue item's error and timestamp.
|
|
585
|
+
* Returns null if no failed items exist.
|
|
586
|
+
*/
|
|
587
|
+
export function getLastUploadError(
|
|
588
|
+
db: Database,
|
|
589
|
+
): { last_error: string | null; updated_at: string } | null {
|
|
590
|
+
try {
|
|
591
|
+
const row = db
|
|
592
|
+
.query(
|
|
593
|
+
`SELECT last_error, updated_at
|
|
594
|
+
FROM upload_queue
|
|
595
|
+
WHERE status = 'failed'
|
|
596
|
+
ORDER BY updated_at DESC
|
|
597
|
+
LIMIT 1`,
|
|
598
|
+
)
|
|
599
|
+
.get() as { last_error: string | null; updated_at: string } | null;
|
|
600
|
+
return row ?? null;
|
|
601
|
+
} catch {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Get the most recent sent queue item's timestamp.
|
|
608
|
+
* Returns null if no sent items exist.
|
|
609
|
+
*/
|
|
610
|
+
export function getLastUploadSuccess(db: Database): { updated_at: string } | null {
|
|
611
|
+
try {
|
|
612
|
+
const row = db
|
|
613
|
+
.query(
|
|
614
|
+
`SELECT updated_at
|
|
615
|
+
FROM upload_queue
|
|
616
|
+
WHERE status = 'sent'
|
|
617
|
+
ORDER BY updated_at DESC
|
|
618
|
+
LIMIT 1`,
|
|
619
|
+
)
|
|
620
|
+
.get() as { updated_at: string } | null;
|
|
621
|
+
return row ?? null;
|
|
622
|
+
} catch {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Get the age in seconds of the oldest pending queue item.
|
|
629
|
+
* Returns null if no pending items exist.
|
|
630
|
+
*/
|
|
631
|
+
export function getOldestPendingAge(db: Database): number | null {
|
|
632
|
+
try {
|
|
633
|
+
const row = db
|
|
634
|
+
.query(
|
|
635
|
+
`SELECT created_at
|
|
636
|
+
FROM upload_queue
|
|
637
|
+
WHERE status = 'pending'
|
|
638
|
+
ORDER BY created_at ASC
|
|
639
|
+
LIMIT 1`,
|
|
640
|
+
)
|
|
641
|
+
.get() as { created_at: string } | null;
|
|
642
|
+
if (!row) return null;
|
|
643
|
+
const ageMs = Date.now() - new Date(row.created_at).getTime();
|
|
644
|
+
return Math.floor(ageMs / 1000);
|
|
645
|
+
} catch {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
357
650
|
// -- Helpers ------------------------------------------------------------------
|
|
358
651
|
|
|
359
|
-
function safeParseJsonArray<T = string>(json: string | null): T[] {
|
|
652
|
+
export function safeParseJsonArray<T = string>(json: string | null): T[] {
|
|
360
653
|
if (!json) return [];
|
|
361
654
|
try {
|
|
362
655
|
const parsed = JSON.parse(json);
|
|
@@ -366,7 +659,7 @@ function safeParseJsonArray<T = string>(json: string | null): T[] {
|
|
|
366
659
|
}
|
|
367
660
|
}
|
|
368
661
|
|
|
369
|
-
function safeParseJson(json: string | null): Record<string, unknown> | null {
|
|
662
|
+
export function safeParseJson(json: string | null): Record<string, unknown> | null {
|
|
370
663
|
if (!json) return null;
|
|
371
664
|
try {
|
|
372
665
|
return JSON.parse(json);
|
|
@@ -47,6 +47,11 @@ CREATE TABLE IF NOT EXISTS skill_invocations (
|
|
|
47
47
|
confidence REAL,
|
|
48
48
|
tool_name TEXT,
|
|
49
49
|
matched_prompt_id TEXT,
|
|
50
|
+
agent_type TEXT,
|
|
51
|
+
query TEXT,
|
|
52
|
+
skill_path TEXT,
|
|
53
|
+
skill_scope TEXT,
|
|
54
|
+
source TEXT,
|
|
50
55
|
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
|
|
51
56
|
)`;
|
|
52
57
|
|
|
@@ -151,6 +156,67 @@ CREATE TABLE IF NOT EXISTS orchestrate_runs (
|
|
|
151
156
|
skill_actions_json TEXT NOT NULL
|
|
152
157
|
)`;
|
|
153
158
|
|
|
159
|
+
// -- Query log table (from all_queries_log.jsonl) ----------------------------
|
|
160
|
+
|
|
161
|
+
export const CREATE_QUERIES = `
|
|
162
|
+
CREATE TABLE IF NOT EXISTS queries (
|
|
163
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
164
|
+
timestamp TEXT NOT NULL,
|
|
165
|
+
session_id TEXT NOT NULL,
|
|
166
|
+
query TEXT NOT NULL,
|
|
167
|
+
source TEXT
|
|
168
|
+
)`;
|
|
169
|
+
|
|
170
|
+
// -- Improvement signal table (from signal_log.jsonl) ------------------------
|
|
171
|
+
|
|
172
|
+
export const CREATE_IMPROVEMENT_SIGNALS = `
|
|
173
|
+
CREATE TABLE IF NOT EXISTS improvement_signals (
|
|
174
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
175
|
+
timestamp TEXT NOT NULL,
|
|
176
|
+
session_id TEXT NOT NULL,
|
|
177
|
+
query TEXT NOT NULL,
|
|
178
|
+
signal_type TEXT NOT NULL,
|
|
179
|
+
mentioned_skill TEXT,
|
|
180
|
+
consumed INTEGER NOT NULL DEFAULT 0,
|
|
181
|
+
consumed_at TEXT,
|
|
182
|
+
consumed_by_run TEXT
|
|
183
|
+
)`;
|
|
184
|
+
|
|
185
|
+
// -- Alpha upload queue -------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
export const CREATE_UPLOAD_QUEUE = `
|
|
188
|
+
CREATE TABLE IF NOT EXISTS upload_queue (
|
|
189
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
190
|
+
payload_type TEXT NOT NULL,
|
|
191
|
+
payload_json TEXT NOT NULL,
|
|
192
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
193
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
194
|
+
created_at TEXT NOT NULL,
|
|
195
|
+
updated_at TEXT NOT NULL,
|
|
196
|
+
last_error TEXT
|
|
197
|
+
)`;
|
|
198
|
+
|
|
199
|
+
// -- Canonical upload staging -------------------------------------------------
|
|
200
|
+
|
|
201
|
+
export const CREATE_CANONICAL_UPLOAD_STAGING = `
|
|
202
|
+
CREATE TABLE IF NOT EXISTS canonical_upload_staging (
|
|
203
|
+
local_seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
204
|
+
record_kind TEXT NOT NULL,
|
|
205
|
+
record_id TEXT NOT NULL,
|
|
206
|
+
record_json TEXT NOT NULL,
|
|
207
|
+
session_id TEXT,
|
|
208
|
+
prompt_id TEXT,
|
|
209
|
+
normalized_at TEXT,
|
|
210
|
+
staged_at TEXT NOT NULL
|
|
211
|
+
)`;
|
|
212
|
+
|
|
213
|
+
export const CREATE_UPLOAD_WATERMARKS = `
|
|
214
|
+
CREATE TABLE IF NOT EXISTS upload_watermarks (
|
|
215
|
+
payload_type TEXT PRIMARY KEY,
|
|
216
|
+
last_uploaded_id INTEGER NOT NULL,
|
|
217
|
+
updated_at TEXT NOT NULL
|
|
218
|
+
)`;
|
|
219
|
+
|
|
154
220
|
// -- Metadata table -----------------------------------------------------------
|
|
155
221
|
|
|
156
222
|
export const CREATE_META = `
|
|
@@ -167,6 +233,7 @@ export const CREATE_INDEXES = [
|
|
|
167
233
|
`CREATE INDEX IF NOT EXISTS idx_prompts_occurred ON prompts(occurred_at)`,
|
|
168
234
|
`CREATE INDEX IF NOT EXISTS idx_skill_inv_session ON skill_invocations(session_id)`,
|
|
169
235
|
`CREATE INDEX IF NOT EXISTS idx_skill_inv_name ON skill_invocations(skill_name)`,
|
|
236
|
+
`CREATE INDEX IF NOT EXISTS idx_skill_inv_ts ON skill_invocations(occurred_at)`,
|
|
170
237
|
`CREATE INDEX IF NOT EXISTS idx_exec_facts_session ON execution_facts(session_id)`,
|
|
171
238
|
`CREATE INDEX IF NOT EXISTS idx_evo_evidence_proposal ON evolution_evidence(proposal_id)`,
|
|
172
239
|
`CREATE INDEX IF NOT EXISTS idx_evo_evidence_skill ON evolution_evidence(skill_name)`,
|
|
@@ -186,6 +253,43 @@ export const CREATE_INDEXES = [
|
|
|
186
253
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_evo_evidence_dedup ON evolution_evidence(proposal_id, stage, timestamp)`,
|
|
187
254
|
// -- Orchestrate run indexes -----------------------------------------------
|
|
188
255
|
`CREATE INDEX IF NOT EXISTS idx_orchestrate_runs_ts ON orchestrate_runs(timestamp)`,
|
|
256
|
+
// -- Query log indexes ------------------------------------------------------
|
|
257
|
+
`CREATE INDEX IF NOT EXISTS idx_queries_session ON queries(session_id)`,
|
|
258
|
+
`CREATE INDEX IF NOT EXISTS idx_queries_ts ON queries(timestamp)`,
|
|
259
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_queries_dedup ON queries(session_id, query, timestamp)`,
|
|
260
|
+
// -- Improvement signal indexes ---------------------------------------------
|
|
261
|
+
`CREATE INDEX IF NOT EXISTS idx_signals_session ON improvement_signals(session_id)`,
|
|
262
|
+
`CREATE INDEX IF NOT EXISTS idx_signals_consumed ON improvement_signals(consumed)`,
|
|
263
|
+
`CREATE INDEX IF NOT EXISTS idx_signals_ts ON improvement_signals(timestamp)`,
|
|
264
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_signals_dedup ON improvement_signals(session_id, query, signal_type, timestamp)`,
|
|
265
|
+
// -- Alpha upload queue indexes ---------------------------------------------
|
|
266
|
+
`CREATE INDEX IF NOT EXISTS idx_upload_queue_status ON upload_queue(status)`,
|
|
267
|
+
`CREATE INDEX IF NOT EXISTS idx_upload_queue_type_status ON upload_queue(payload_type, status)`,
|
|
268
|
+
// -- Canonical upload staging indexes ---------------------------------------
|
|
269
|
+
`CREATE INDEX IF NOT EXISTS idx_staging_kind ON canonical_upload_staging(record_kind)`,
|
|
270
|
+
`CREATE INDEX IF NOT EXISTS idx_staging_session ON canonical_upload_staging(session_id)`,
|
|
271
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_staging_dedup ON canonical_upload_staging(record_kind, record_id)`,
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Schema migrations — ALTER TABLE statements for columns added after initial release.
|
|
276
|
+
* Each is safe to re-run: SQLite throws "duplicate column" which openDb() catches.
|
|
277
|
+
*/
|
|
278
|
+
export const MIGRATIONS = [
|
|
279
|
+
// skill_invocations consolidation (skill_usage columns merged in)
|
|
280
|
+
`ALTER TABLE skill_invocations ADD COLUMN query TEXT`,
|
|
281
|
+
`ALTER TABLE skill_invocations ADD COLUMN skill_path TEXT`,
|
|
282
|
+
`ALTER TABLE skill_invocations ADD COLUMN skill_scope TEXT`,
|
|
283
|
+
`ALTER TABLE skill_invocations ADD COLUMN source TEXT`,
|
|
284
|
+
// Track how many iteration loops each evolution run used
|
|
285
|
+
`ALTER TABLE evolution_audit ADD COLUMN iterations_used INTEGER`,
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
/** Indexes that depend on migration columns — must run AFTER MIGRATIONS. */
|
|
289
|
+
export const POST_MIGRATION_INDEXES = [
|
|
290
|
+
`CREATE INDEX IF NOT EXISTS idx_skill_inv_query_triggered ON skill_invocations(query, triggered)`,
|
|
291
|
+
`CREATE INDEX IF NOT EXISTS idx_skill_inv_scope ON skill_invocations(skill_name, skill_scope, occurred_at)`,
|
|
292
|
+
`CREATE INDEX IF NOT EXISTS idx_skill_inv_dedup ON skill_invocations(session_id, skill_name, query, occurred_at, triggered)`,
|
|
189
293
|
];
|
|
190
294
|
|
|
191
295
|
/** All DDL statements in creation order. */
|
|
@@ -199,6 +303,11 @@ export const ALL_DDL = [
|
|
|
199
303
|
CREATE_SESSION_TELEMETRY,
|
|
200
304
|
CREATE_SKILL_USAGE,
|
|
201
305
|
CREATE_ORCHESTRATE_RUNS,
|
|
306
|
+
CREATE_QUERIES,
|
|
307
|
+
CREATE_IMPROVEMENT_SIGNALS,
|
|
308
|
+
CREATE_UPLOAD_QUEUE,
|
|
309
|
+
CREATE_UPLOAD_WATERMARKS,
|
|
310
|
+
CREATE_CANONICAL_UPLOAD_STAGING,
|
|
202
311
|
CREATE_META,
|
|
203
312
|
...CREATE_INDEXES,
|
|
204
313
|
];
|
|
@@ -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,26 @@ export async function watch(options: WatchOptions): Promise<WatchResult> {
|
|
|
207
212
|
);
|
|
208
213
|
}
|
|
209
214
|
|
|
210
|
-
// 1. Read log files
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
telemetry = readJsonl<SessionTelemetryRecord>(_telemetryLogPath);
|
|
232
|
+
skillRecords = readJsonl<SkillUsageRecord>(_skillLogPath);
|
|
233
|
+
queryRecords = readJsonl<QueryLogRecord>(_queryLogPath);
|
|
234
|
+
}
|
|
217
235
|
|
|
218
236
|
// 2. Determine baseline pass rate from last deployed audit entry
|
|
219
237
|
const lastDeployed = getLastDeployedProposal(skillName, _auditLogPath);
|