@jmtrin/opencode-kevin 1.2.0 → 1.4.0
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 +84 -22
- package/dist/plugin/index.d.ts +5 -4
- package/dist/plugin/index.js +127 -113
- package/dist/plugin/native.d.ts +2 -2
- package/dist/plugin/native.js +2 -2
- package/package.json +56 -68
- package/dist/migrations/001_initial.sql +0 -92
- package/dist/migrations/002_indexes.sql +0 -14
- package/dist/migrations/003_v02_signal.sql +0 -58
- package/dist/migrations/004_v03_knowledge.sql +0 -138
- package/dist/migrations/005_v04_signal.sql +0 -57
- package/dist/migrations/006_v05_glassbox.sql +0 -118
- package/dist/migrations/007_v06_pull.sql +0 -145
- package/dist/migrations/008_v07_truth.sql +0 -124
- package/dist/migrations/009_v08_team.sql +0 -100
- package/dist/migrations/010_v09_native.sql +0 -78
- package/dist/migrations/011_v10_proven.sql +0 -69
- package/dist/migrations/012_v11_drift.sql +0 -24
- package/dist/plugin/Archiver.d.ts +0 -42
- package/dist/plugin/Archiver.js +0 -83
- package/dist/plugin/ArtifactWriter.d.ts +0 -93
- package/dist/plugin/ArtifactWriter.js +0 -291
- package/dist/plugin/CausalChain.d.ts +0 -22
- package/dist/plugin/CausalChain.js +0 -197
- package/dist/plugin/ChatBridge.d.ts +0 -41
- package/dist/plugin/ChatBridge.js +0 -103
- package/dist/plugin/ConflictDetector.d.ts +0 -35
- package/dist/plugin/ConflictDetector.js +0 -260
- package/dist/plugin/ContextInjector.d.ts +0 -222
- package/dist/plugin/ContextInjector.js +0 -533
- package/dist/plugin/ConventionMiner.d.ts +0 -35
- package/dist/plugin/ConventionMiner.js +0 -242
- package/dist/plugin/Curator.d.ts +0 -116
- package/dist/plugin/Curator.js +0 -324
- package/dist/plugin/DashboardHtml.d.ts +0 -5
- package/dist/plugin/DashboardHtml.js +0 -180
- package/dist/plugin/Feedback.d.ts +0 -67
- package/dist/plugin/Feedback.js +0 -120
- package/dist/plugin/HookLiveness.d.ts +0 -88
- package/dist/plugin/HookLiveness.js +0 -322
- package/dist/plugin/InjectionLedger.d.ts +0 -98
- package/dist/plugin/InjectionLedger.js +0 -295
- package/dist/plugin/LessonFixer.d.ts +0 -44
- package/dist/plugin/LessonFixer.js +0 -45
- package/dist/plugin/Materializer.d.ts +0 -83
- package/dist/plugin/Materializer.js +0 -181
- package/dist/plugin/MemoryService.d.ts +0 -360
- package/dist/plugin/MemoryService.js +0 -1242
- package/dist/plugin/Migrate.d.ts +0 -17
- package/dist/plugin/Migrate.js +0 -271
- package/dist/plugin/PatternMiner.d.ts +0 -49
- package/dist/plugin/PatternMiner.js +0 -132
- package/dist/plugin/QualityGate.d.ts +0 -124
- package/dist/plugin/QualityGate.js +0 -119
- package/dist/plugin/Reflector.d.ts +0 -91
- package/dist/plugin/Reflector.js +0 -307
- package/dist/plugin/RepoIdentity.d.ts +0 -124
- package/dist/plugin/RepoIdentity.js +0 -300
- package/dist/plugin/RepoTruth.d.ts +0 -80
- package/dist/plugin/RepoTruth.js +0 -599
- package/dist/plugin/Retrospective.d.ts +0 -17
- package/dist/plugin/Retrospective.js +0 -216
- package/dist/plugin/SharedLayer.d.ts +0 -159
- package/dist/plugin/SharedLayer.js +0 -462
- package/dist/plugin/Store.d.ts +0 -29
- package/dist/plugin/Store.js +0 -50
- package/dist/plugin/ToolCallObserver.d.ts +0 -32
- package/dist/plugin/ToolCallObserver.js +0 -201
- package/dist/plugin/TuiActions.d.ts +0 -43
- package/dist/plugin/TuiActions.js +0 -181
- package/dist/plugin/TuiSnapshots.d.ts +0 -24
- package/dist/plugin/TuiSnapshots.js +0 -158
- package/dist/plugin/columns.d.ts +0 -11
- package/dist/plugin/columns.js +0 -54
- package/dist/plugin/confidence.d.ts +0 -8
- package/dist/plugin/confidence.js +0 -34
- package/dist/plugin/contract.d.ts +0 -54
- package/dist/plugin/contract.js +0 -386
- package/dist/plugin/diff.d.ts +0 -8
- package/dist/plugin/diff.js +0 -182
- package/dist/plugin/escape.d.ts +0 -39
- package/dist/plugin/escape.js +0 -60
- package/dist/plugin/fingerprint.d.ts +0 -27
- package/dist/plugin/fingerprint.js +0 -73
- package/dist/plugin/inferability.d.ts +0 -32
- package/dist/plugin/inferability.js +0 -88
- package/dist/plugin/kevin_approve.d.ts +0 -34
- package/dist/plugin/kevin_approve.js +0 -52
- package/dist/plugin/kevin_audit.d.ts +0 -229
- package/dist/plugin/kevin_audit.js +0 -631
- package/dist/plugin/kevin_bench.d.ts +0 -10
- package/dist/plugin/kevin_bench.js +0 -82
- package/dist/plugin/kevin_conflicts.d.ts +0 -9
- package/dist/plugin/kevin_conflicts.js +0 -50
- package/dist/plugin/kevin_contract.d.ts +0 -8
- package/dist/plugin/kevin_contract.js +0 -59
- package/dist/plugin/kevin_doctor.d.ts +0 -62
- package/dist/plugin/kevin_doctor.js +0 -196
- package/dist/plugin/kevin_facts.d.ts +0 -42
- package/dist/plugin/kevin_facts.js +0 -36
- package/dist/plugin/kevin_forget.d.ts +0 -33
- package/dist/plugin/kevin_forget.js +0 -260
- package/dist/plugin/kevin_native.d.ts +0 -29
- package/dist/plugin/kevin_native.js +0 -79
- package/dist/plugin/kevin_propose.d.ts +0 -23
- package/dist/plugin/kevin_propose.js +0 -14
- package/dist/plugin/kevin_publish.d.ts +0 -38
- package/dist/plugin/kevin_publish.js +0 -18
- package/dist/plugin/kevin_why.d.ts +0 -23
- package/dist/plugin/kevin_why.js +0 -111
- package/dist/plugin/memory-format.d.ts +0 -20
- package/dist/plugin/memory-format.js +0 -59
- package/dist/plugin/metrics.d.ts +0 -97
- package/dist/plugin/metrics.js +0 -288
- package/dist/plugin/okf-export.d.ts +0 -3
- package/dist/plugin/okf-export.js +0 -134
- package/dist/plugin/okf-import.d.ts +0 -76
- package/dist/plugin/okf-import.js +0 -271
- package/dist/plugin/okf.d.ts +0 -107
- package/dist/plugin/okf.js +0 -303
- package/dist/plugin/perf.d.ts +0 -35
- package/dist/plugin/perf.js +0 -174
- package/dist/plugin/query-tokenizer.d.ts +0 -13
- package/dist/plugin/query-tokenizer.js +0 -133
- package/dist/plugin/redact.d.ts +0 -2
- package/dist/plugin/redact.js +0 -18
- package/dist/plugin/replay-types.d.ts +0 -69
- package/dist/plugin/replay-types.js +0 -156
- package/dist/plugin/replay.d.ts +0 -36
- package/dist/plugin/replay.js +0 -202
- package/dist/plugin/sqlite-adapter.d.ts +0 -12
- package/dist/plugin/sqlite-adapter.js +0 -81
- package/dist/plugin/time-ms.d.ts +0 -1
- package/dist/plugin/time-ms.js +0 -16
- package/dist/plugin/tui-types.d.ts +0 -59
- package/dist/plugin/tui-types.js +0 -4
- package/dist/plugin/tui.d.ts +0 -18
- package/dist/plugin/tui.js +0 -198
- package/dist/plugin/uuid.d.ts +0 -1
- package/dist/plugin/uuid.js +0 -50
package/dist/plugin/Migrate.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { Store } from "./Store.js";
|
|
2
|
-
export interface MigrateResult {
|
|
3
|
-
from: string;
|
|
4
|
-
to: string;
|
|
5
|
-
applied: string[];
|
|
6
|
-
}
|
|
7
|
-
export type PostApplyHook = (store: Store) => void;
|
|
8
|
-
export declare const HOOK_NAMES: readonly ["tool.execute.before", "tool.execute.after", "chat.message", "experimental.chat.system.transform", "experimental.session.compacting", "event", "dispose"];
|
|
9
|
-
export declare class Migrate {
|
|
10
|
-
private store;
|
|
11
|
-
private migrationsDir;
|
|
12
|
-
private readonly postApplyHooks;
|
|
13
|
-
constructor(store: Store, migrationsDir: string, postApplyHooks?: Record<string, PostApplyHook>);
|
|
14
|
-
registerPostApply(version: string, hook: PostApplyHook): void;
|
|
15
|
-
run(): Promise<MigrateResult>;
|
|
16
|
-
private listPending;
|
|
17
|
-
}
|
package/dist/plugin/Migrate.js
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
// v0.9.0 (K9-001 / plan §5.3, D9-08) — the six hooks Kevin registers, in
|
|
4
|
-
// hook-object order, plus `dispose` (v1.0.0 K10-013): the seventh
|
|
5
|
-
// instrumented hook. Canonical list: the "010" post-apply hook seeds
|
|
6
|
-
// hook_liveness from it, and HookLiveness (K9-009) wraps exactly these
|
|
7
|
-
// names, so a future hook added here is visible in the table on the next
|
|
8
|
-
// migration run.
|
|
9
|
-
export const HOOK_NAMES = [
|
|
10
|
-
"tool.execute.before",
|
|
11
|
-
"tool.execute.after",
|
|
12
|
-
"chat.message",
|
|
13
|
-
"experimental.chat.system.transform",
|
|
14
|
-
"experimental.session.compacting",
|
|
15
|
-
"event",
|
|
16
|
-
"dispose",
|
|
17
|
-
];
|
|
18
|
-
// Built-in post-apply hooks, keyed by migration version. Each hook runs inside
|
|
19
|
-
// the same transaction as the migration's DDL, so a hook failure rolls back the
|
|
20
|
-
// whole migration. Hooks run when their version is applied. Migration 008's
|
|
21
|
-
// reconciliation hook is also safe to run on later no-op starts (K7-002).
|
|
22
|
-
const DEFAULT_POST_APPLY_HOOKS = {
|
|
23
|
-
// v0.2.0 Signal Quality: defensive backfill of memories.origin for legacy
|
|
24
|
-
// rows. The column is NOT NULL DEFAULT 'agent', so SQLite already populates
|
|
25
|
-
// pre-existing rows with 'agent' on ALTER TABLE. This hook is a belt-and-
|
|
26
|
-
// braces UPDATE that coerces any NULL/empty stragglers (which would only
|
|
27
|
-
// exist if a partial DB skipped the DEFAULT) back to 'agent'.
|
|
28
|
-
"003": (store) => {
|
|
29
|
-
store
|
|
30
|
-
.prepare("UPDATE memories SET origin = 'agent' WHERE origin IS NULL OR origin = ''")
|
|
31
|
-
.run();
|
|
32
|
-
},
|
|
33
|
-
// v0.3.0 Knowledge + Causality: backfill evidence_count and status for
|
|
34
|
-
// legacy rows. Columns have NOT NULL DEFAULT, so SQLite already populates
|
|
35
|
-
// pre-existing rows. This hook is belt-and-braces in case a partial DB
|
|
36
|
-
// skipped the defaults.
|
|
37
|
-
"004": (store) => {
|
|
38
|
-
store
|
|
39
|
-
.prepare("UPDATE memories SET evidence_count = 0 WHERE evidence_count IS NULL")
|
|
40
|
-
.run();
|
|
41
|
-
store
|
|
42
|
-
.prepare("UPDATE memories SET status = 'active' WHERE status IS NULL OR status = ''")
|
|
43
|
-
.run();
|
|
44
|
-
},
|
|
45
|
-
// v0.4.0 Signal over Noise: backfill recurrence_count for legacy rows.
|
|
46
|
-
// recurrence_count has NOT NULL DEFAULT 0, so SQLite already populates
|
|
47
|
-
// pre-existing rows; fix_args and last_injected_at are nullable and need
|
|
48
|
-
// no coercion. This hook is belt-and-braces in case a partial DB skipped
|
|
49
|
-
// the defaults.
|
|
50
|
-
"005": (store) => {
|
|
51
|
-
store
|
|
52
|
-
.prepare("UPDATE memories SET recurrence_count = 0 WHERE recurrence_count IS NULL")
|
|
53
|
-
.run();
|
|
54
|
-
},
|
|
55
|
-
// v0.5.0 (K5-002 / plan §6, D5-13) — Glass Box: re-derive the four
|
|
56
|
-
// injection counters from the ledger table instead of incrementing them.
|
|
57
|
-
// The rebuild remapped prior `effective` rows to `inconclusive`, so any
|
|
58
|
-
// pre-existing counter values would otherwise carry the v0.4 confound
|
|
59
|
-
// forward. Re-derivation is idempotent by construction and self-healing
|
|
60
|
-
// on earlier drift. No INSERTs: the rows are seeded by the migration SQL;
|
|
61
|
-
// a missing row makes the UPDATE a harmless no-op.
|
|
62
|
-
"006": (store) => {
|
|
63
|
-
store
|
|
64
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM kevin_injections) WHERE key = 'injections_total'")
|
|
65
|
-
.run();
|
|
66
|
-
store
|
|
67
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM kevin_injections WHERE outcome = 'effective') WHERE key = 'injections_effective'")
|
|
68
|
-
.run();
|
|
69
|
-
store
|
|
70
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM kevin_injections WHERE outcome = 'ineffective') WHERE key = 'injections_ineffective'")
|
|
71
|
-
.run();
|
|
72
|
-
store
|
|
73
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM kevin_injections WHERE outcome = 'inconclusive') WHERE key = 'injections_inconclusive'")
|
|
74
|
-
.run();
|
|
75
|
-
},
|
|
76
|
-
// v0.6.0 (K6-002 / plan §6, D6-08) — Pull: three re-derivations, all
|
|
77
|
-
// idempotent by re-derivation (same discipline as "006", D5-13).
|
|
78
|
-
// 1. Back-fill `inferable = 0` for the four non-`error` types still NULL:
|
|
79
|
-
// they are `non_inferable` by rules 1 and 2 of plan §5.3
|
|
80
|
-
// unconditionally. Guarded by `inferable IS NULL` so a re-run cannot
|
|
81
|
-
// overwrite a classification produced later by inferability.classify().
|
|
82
|
-
// `error` rows are left NULL and classified lazily.
|
|
83
|
-
// 2-3. proposals_created / artifact_writes_total are re-derived from
|
|
84
|
-
// their tables; a missing row makes the UPDATE a harmless no-op.
|
|
85
|
-
"007": (store) => {
|
|
86
|
-
store
|
|
87
|
-
.prepare("UPDATE memories SET inferable = 0 WHERE inferable IS NULL AND type IN ('decision','rule','solution','pattern')")
|
|
88
|
-
.run();
|
|
89
|
-
store
|
|
90
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM curation_proposals) WHERE key = 'proposals_created'")
|
|
91
|
-
.run();
|
|
92
|
-
store
|
|
93
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM artifact_writes WHERE outcome = 'written') WHERE key = 'artifact_writes_total'")
|
|
94
|
-
.run();
|
|
95
|
-
},
|
|
96
|
-
// v0.7.0 (K7-002 / plan §6, D7-02) — Truth: four re-derivations, all
|
|
97
|
-
// idempotent by re-derivation (same discipline as "006"/"007", D5-13).
|
|
98
|
-
// 1. Normalize any NULL truth_penalty to 0.0 (belt-and-braces; the column
|
|
99
|
-
// is NOT NULL DEFAULT 0.0, so SQLite already back-fills on ALTER TABLE).
|
|
100
|
-
// 2-4. repo_facts_scanned / conflicts_detected / memories_contradicted
|
|
101
|
-
// are re-derived from their tables, NOT trusted from the counters, so
|
|
102
|
-
// the metrics survive a database restored from backup or edited by
|
|
103
|
-
// hand. A missing row makes the UPDATE a harmless no-op.
|
|
104
|
-
"008": (store) => {
|
|
105
|
-
store
|
|
106
|
-
.prepare("UPDATE memories SET truth_penalty = 0.0 WHERE truth_penalty IS NULL")
|
|
107
|
-
.run();
|
|
108
|
-
store
|
|
109
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM repo_facts) WHERE key = 'repo_facts_scanned'")
|
|
110
|
-
.run();
|
|
111
|
-
store
|
|
112
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM memory_conflicts) WHERE key = 'conflicts_detected'")
|
|
113
|
-
.run();
|
|
114
|
-
store
|
|
115
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM memories WHERE truth_penalty > 0.0) WHERE key = 'memories_contradicted'")
|
|
116
|
-
.run();
|
|
117
|
-
},
|
|
118
|
-
// v0.8.0 (K8-002 / plan §6.1, D8-03) — Team: three operations, all
|
|
119
|
-
// idempotent by re-derivation (same discipline as "006"/"007"/"008").
|
|
120
|
-
// 1. Back-fill the new scope from the old one: repo_id = project_id
|
|
121
|
-
// keeps every pre-v0.8 row retrievable on the same machine with an
|
|
122
|
-
// identical result set. Guarded by `repo_id IS NULL`; a row whose
|
|
123
|
-
// project_id is also NULL keeps a NULL repo_id — the retrieval
|
|
124
|
-
// path handles NULL as global (K8-007) instead of the hook faking
|
|
125
|
-
// a value. No git-derived identity here: the hook runs inside a
|
|
126
|
-
// migration and must not read the filesystem (K8-002 criterion).
|
|
127
|
-
// 2. Normalize the layer marker for any row written by a concurrent
|
|
128
|
-
// v0.7.0 process between ALTER and hook (belt and braces; the
|
|
129
|
-
// column DEFAULT already covers the ordinary case).
|
|
130
|
-
// 3. Re-derive shared_entries_total from state rather than trusting
|
|
131
|
-
// an incremented value that may predate a crash.
|
|
132
|
-
"009": (store) => {
|
|
133
|
-
store
|
|
134
|
-
.prepare("UPDATE memories SET repo_id = project_id WHERE repo_id IS NULL")
|
|
135
|
-
.run();
|
|
136
|
-
store
|
|
137
|
-
.prepare("UPDATE memories SET layer = 'local' WHERE layer IS NULL OR layer = ''")
|
|
138
|
-
.run();
|
|
139
|
-
store
|
|
140
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM shared_entries) WHERE key = 'shared_entries_total'")
|
|
141
|
-
.run();
|
|
142
|
-
},
|
|
143
|
-
// v0.9.0 (K9-001 / plan §6.1, D9-08) — Native: three operations, all
|
|
144
|
-
// idempotent by design.
|
|
145
|
-
// 1. Seed one hook_liveness row per name in HOOK_NAMES, with the
|
|
146
|
-
// experimental flag derived from the hook's own `experimental.`
|
|
147
|
-
// prefix and every counter at zero. Seeding eagerly (INSERT OR
|
|
148
|
-
// IGNORE) makes a hook that has never fired a visible row with
|
|
149
|
-
// fire_count = 0, not an absent row indistinguishable from a hook
|
|
150
|
-
// Kevin does not register.
|
|
151
|
-
// 2. Re-derive hooks_dead_total from hook_liveness state rather than
|
|
152
|
-
// trusting an incremented value (same discipline as "006"-"009").
|
|
153
|
-
// 3. Normalize any experimental flag that disagrees with its own
|
|
154
|
-
// hook column's prefix — cheap, and it repairs a row hand-edited
|
|
155
|
-
// during debugging.
|
|
156
|
-
"010": (store) => {
|
|
157
|
-
const seed = store.prepare("INSERT OR IGNORE INTO hook_liveness (hook, experimental) VALUES (?, ?)");
|
|
158
|
-
for (const name of HOOK_NAMES) {
|
|
159
|
-
seed.run(name, name.startsWith("experimental.") ? 1 : 0);
|
|
160
|
-
}
|
|
161
|
-
store
|
|
162
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM hook_liveness WHERE dead_since IS NOT NULL) WHERE key = 'hooks_dead_total'")
|
|
163
|
-
.run();
|
|
164
|
-
store
|
|
165
|
-
.prepare("UPDATE hook_liveness SET experimental = CASE WHEN hook LIKE 'experimental.%' THEN 1 ELSE 0 END")
|
|
166
|
-
.run();
|
|
167
|
-
},
|
|
168
|
-
// v1.0.0 (K10-005 / plan §6.1) — Proven: four operations, all idempotent.
|
|
169
|
-
// 1. Seed dispose row defensively.
|
|
170
|
-
// 2-3. Re-derive perf_budget_breaches and bench_runs_total.
|
|
171
|
-
// 4. Normalise NULL within_budget to 1.
|
|
172
|
-
"011": (store) => {
|
|
173
|
-
store
|
|
174
|
-
.prepare("INSERT OR IGNORE INTO hook_liveness (hook) VALUES ('dispose')")
|
|
175
|
-
.run();
|
|
176
|
-
store
|
|
177
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM perf_samples WHERE within_budget = 0) WHERE key = 'perf_budget_breaches'")
|
|
178
|
-
.run();
|
|
179
|
-
store
|
|
180
|
-
.prepare("UPDATE kevin_metrics SET value = (SELECT COUNT(*) FROM bench_runs) WHERE key = 'bench_runs_total'")
|
|
181
|
-
.run();
|
|
182
|
-
store
|
|
183
|
-
.prepare("UPDATE perf_samples SET within_budget = 1 WHERE within_budget IS NULL")
|
|
184
|
-
.run();
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
export class Migrate {
|
|
188
|
-
store;
|
|
189
|
-
migrationsDir;
|
|
190
|
-
postApplyHooks;
|
|
191
|
-
constructor(store, migrationsDir, postApplyHooks) {
|
|
192
|
-
this.store = store;
|
|
193
|
-
this.migrationsDir = migrationsDir;
|
|
194
|
-
this.postApplyHooks = new Map(Object.entries({
|
|
195
|
-
...DEFAULT_POST_APPLY_HOOKS,
|
|
196
|
-
...(postApplyHooks ?? {}),
|
|
197
|
-
}));
|
|
198
|
-
}
|
|
199
|
-
registerPostApply(version, hook) {
|
|
200
|
-
this.postApplyHooks.set(version, hook);
|
|
201
|
-
}
|
|
202
|
-
async run() {
|
|
203
|
-
this.store.exec(`CREATE TABLE IF NOT EXISTS schema_version (
|
|
204
|
-
version TEXT PRIMARY KEY,
|
|
205
|
-
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
206
|
-
);`);
|
|
207
|
-
const currentRow = this.store
|
|
208
|
-
.prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1")
|
|
209
|
-
.get();
|
|
210
|
-
const from = currentRow?.version ?? "000";
|
|
211
|
-
const pending = this.listPending(from);
|
|
212
|
-
if (pending.length === 0) {
|
|
213
|
-
// v0.7.0 (K7-002) — heal drift in the 008 counters on a no-op
|
|
214
|
-
// startup while preserving `applied: []` idempotency. v0.8.0
|
|
215
|
-
// (K8-002 / plan §6.1) extends the same repair to the 009
|
|
216
|
-
// back-fill and the shared_entries_total re-derivation, and
|
|
217
|
-
// v0.9.0 (K9-001 / plan §6.1) to the 010 seeding,
|
|
218
|
-
// hooks_dead_total re-derivation and experimental
|
|
219
|
-
// normalization: all three hooks are idempotent by guarded
|
|
220
|
-
// updates and re-derivation, so a no-op startup can heal a
|
|
221
|
-
// crash that landed between the DDL and the hook without
|
|
222
|
-
// re-applying DDL.
|
|
223
|
-
if (from === "008" ||
|
|
224
|
-
from === "009" ||
|
|
225
|
-
from === "010" ||
|
|
226
|
-
from === "011") {
|
|
227
|
-
const repairHook = this.postApplyHooks.get(from);
|
|
228
|
-
if (repairHook)
|
|
229
|
-
this.store.transaction(() => repairHook(this.store));
|
|
230
|
-
}
|
|
231
|
-
return { from, to: from, applied: [] };
|
|
232
|
-
}
|
|
233
|
-
const insertVersion = this.store.prepare("INSERT OR IGNORE INTO schema_version (version) VALUES (?)");
|
|
234
|
-
for (const migration of pending) {
|
|
235
|
-
const sql = readFileSync(join(this.migrationsDir, migration.file), "utf8");
|
|
236
|
-
this.store.transaction(() => {
|
|
237
|
-
this.store.exec(sql);
|
|
238
|
-
const hook = this.postApplyHooks.get(migration.version);
|
|
239
|
-
if (hook)
|
|
240
|
-
hook(this.store);
|
|
241
|
-
insertVersion.run(migration.version);
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
from,
|
|
246
|
-
to: pending[pending.length - 1].version,
|
|
247
|
-
applied: pending.map((m) => m.version),
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
// v1.1.0 (K11-015) — lexicographic ordering is valid through "999" because
|
|
251
|
-
// versions are zero-padded 3-digit strings ("001" … "999"). Any future
|
|
252
|
-
// migration beyond 999 must use a 4-digit prefix and this comparison must
|
|
253
|
-
// become numeric (parseInt). Until then, string > works and keeps the
|
|
254
|
-
// migration idempotency simple (plan §5.5, D11-??).
|
|
255
|
-
listPending(current) {
|
|
256
|
-
let files = [];
|
|
257
|
-
try {
|
|
258
|
-
files = readdirSync(this.migrationsDir).filter((f) => f.endsWith(".sql"));
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
return [];
|
|
262
|
-
}
|
|
263
|
-
files.sort();
|
|
264
|
-
return files
|
|
265
|
-
.map((file) => {
|
|
266
|
-
const match = file.match(/^(\w+?)_/);
|
|
267
|
-
return match ? { version: match[1], file } : null;
|
|
268
|
-
})
|
|
269
|
-
.filter((m) => m !== null && m.version > current);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
-
import type { Store } from "./Store.js";
|
|
3
|
-
import type { Metrics } from "./metrics.js";
|
|
4
|
-
/**
|
|
5
|
-
* v0.2.0 PatternMiner (K2-021, plan §B6.10 / D2-08).
|
|
6
|
-
*
|
|
7
|
-
* Opt-in deterministic pattern miner. Reads recent `tool_calls` for a given
|
|
8
|
-
* project, groups consecutive ordered 2-grams of `(tool_name)` and 3-grams
|
|
9
|
-
* where the *middle* tool was a failure, and when a group reaches the
|
|
10
|
-
* threshold (default `N ≥ 5` distinct sessions), emits a single
|
|
11
|
-
* `type='pattern'`, `origin='pattern'` memory describing the pattern.
|
|
12
|
-
*
|
|
13
|
-
* Default OFF — must be enabled by setting `kevin_settings.patternminer_enabled`
|
|
14
|
-
* to `'1'`. Idempotent via SELECT-before-INSERT keyed on
|
|
15
|
-
* `(project_id, fingerprint, type='pattern', origin='pattern')`. Migration 003's
|
|
16
|
-
* partial UNIQUE index only covers `type='error' AND origin='reflector'`, so
|
|
17
|
-
* pattern memories cannot rely on a database uniqueness constraint — the
|
|
18
|
-
* SELECT check inside `mine()` is the single idempotency mechanism.
|
|
19
|
-
*
|
|
20
|
-
* NO LLM hop (D2-08). The emitted suggestion is a deterministic template built
|
|
21
|
-
* from the captured tool names.
|
|
22
|
-
*/
|
|
23
|
-
export interface PatternMinerOptions {
|
|
24
|
-
/** Minimum distinct sessions a pattern must appear in before emission.
|
|
25
|
-
* Default 5 (D2-08). */
|
|
26
|
-
threshold?: number;
|
|
27
|
-
}
|
|
28
|
-
export declare class PatternMiner {
|
|
29
|
-
private readonly store;
|
|
30
|
-
private readonly memoryService;
|
|
31
|
-
private readonly metrics;
|
|
32
|
-
private readonly threshold;
|
|
33
|
-
constructor(store: Store, memoryService: MemoryService, metrics?: Metrics | null, options?: PatternMinerOptions);
|
|
34
|
-
/**
|
|
35
|
-
* Mine patterns observed in `tool_calls` for the given project. Returns the
|
|
36
|
-
* number of NEW `pattern` memories emitted this cycle.
|
|
37
|
-
*
|
|
38
|
-
* When the opt-in flag `kevin_settings.patternminer_enabled` is not set to
|
|
39
|
-
* `'1'` (default), this is a no-op and returns 0.
|
|
40
|
-
*
|
|
41
|
-
* When `projectId` is null/undefined, mines tool_calls whose `project_id`
|
|
42
|
-
* IS NULL (legacy / opt-out flow). When `projectId` is a string, mines
|
|
43
|
-
* tool_calls scoped to that project only.
|
|
44
|
-
*/
|
|
45
|
-
mine(projectId?: string | null): number;
|
|
46
|
-
private isEnabled;
|
|
47
|
-
private fetchToolCalls;
|
|
48
|
-
private collectCandidates;
|
|
49
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { fingerprint as computeFingerprint } from "./fingerprint.js";
|
|
2
|
-
const DEFAULT_THRESHOLD = 5;
|
|
3
|
-
const SETTING_KEY = "patternminer_enabled";
|
|
4
|
-
export class PatternMiner {
|
|
5
|
-
store;
|
|
6
|
-
memoryService;
|
|
7
|
-
metrics;
|
|
8
|
-
threshold;
|
|
9
|
-
constructor(store, memoryService, metrics, options) {
|
|
10
|
-
this.store = store;
|
|
11
|
-
this.memoryService = memoryService;
|
|
12
|
-
this.metrics = metrics ?? null;
|
|
13
|
-
this.threshold = options?.threshold ?? DEFAULT_THRESHOLD;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Mine patterns observed in `tool_calls` for the given project. Returns the
|
|
17
|
-
* number of NEW `pattern` memories emitted this cycle.
|
|
18
|
-
*
|
|
19
|
-
* When the opt-in flag `kevin_settings.patternminer_enabled` is not set to
|
|
20
|
-
* `'1'` (default), this is a no-op and returns 0.
|
|
21
|
-
*
|
|
22
|
-
* When `projectId` is null/undefined, mines tool_calls whose `project_id`
|
|
23
|
-
* IS NULL (legacy / opt-out flow). When `projectId` is a string, mines
|
|
24
|
-
* tool_calls scoped to that project only.
|
|
25
|
-
*/
|
|
26
|
-
mine(projectId) {
|
|
27
|
-
if (!this.isEnabled())
|
|
28
|
-
return 0;
|
|
29
|
-
const rows = this.fetchToolCalls(projectId ?? null);
|
|
30
|
-
if (rows.length === 0)
|
|
31
|
-
return 0;
|
|
32
|
-
const candidates = this.collectCandidates(rows);
|
|
33
|
-
if (candidates.length === 0)
|
|
34
|
-
return 0;
|
|
35
|
-
let emitted = 0;
|
|
36
|
-
for (const c of candidates) {
|
|
37
|
-
if (c.sessions.size < this.threshold)
|
|
38
|
-
continue;
|
|
39
|
-
const fp = computeFingerprint(c.content, projectId ?? undefined);
|
|
40
|
-
// Idempotency: migration 003's partial unique only covers
|
|
41
|
-
// (type='error', origin='reflector'). For pattern memories we
|
|
42
|
-
// SELECT to detect a prior emission with the same
|
|
43
|
-
// (project_id, fingerprint, type='pattern', origin='pattern').
|
|
44
|
-
const existing = this.store
|
|
45
|
-
.prepare(`SELECT id FROM memories
|
|
46
|
-
WHERE type = 'pattern' AND origin = 'pattern'
|
|
47
|
-
AND fingerprint = ?
|
|
48
|
-
AND (project_id IS ? OR (project_id IS NULL AND ? IS NULL))
|
|
49
|
-
LIMIT 1`)
|
|
50
|
-
.get(fp, projectId ?? null, projectId ?? null);
|
|
51
|
-
if (existing)
|
|
52
|
-
continue;
|
|
53
|
-
this.memoryService.save({
|
|
54
|
-
type: "pattern",
|
|
55
|
-
origin: "pattern",
|
|
56
|
-
fingerprint: fp,
|
|
57
|
-
content: c.content,
|
|
58
|
-
scope: "project",
|
|
59
|
-
projectId: projectId ?? undefined,
|
|
60
|
-
relevanceScore: 0.5,
|
|
61
|
-
sourceTool: "PatternMiner",
|
|
62
|
-
});
|
|
63
|
-
this.metrics?.incr("patterns_mined", 1);
|
|
64
|
-
emitted += 1;
|
|
65
|
-
}
|
|
66
|
-
return emitted;
|
|
67
|
-
}
|
|
68
|
-
isEnabled() {
|
|
69
|
-
const row = this.store
|
|
70
|
-
.prepare("SELECT value FROM kevin_settings WHERE key = ?")
|
|
71
|
-
.get(SETTING_KEY);
|
|
72
|
-
return row?.value === "1";
|
|
73
|
-
}
|
|
74
|
-
fetchToolCalls(projectId) {
|
|
75
|
-
const nullPid = projectId === null || projectId === undefined;
|
|
76
|
-
const sql = nullPid
|
|
77
|
-
? `SELECT id, session_id, ts, tool, success FROM tool_calls
|
|
78
|
-
WHERE project_id IS NULL
|
|
79
|
-
ORDER BY session_id ASC, ts ASC`
|
|
80
|
-
: `SELECT id, session_id, ts, tool, success FROM tool_calls
|
|
81
|
-
WHERE project_id = ?
|
|
82
|
-
ORDER BY session_id ASC, ts ASC`;
|
|
83
|
-
const stmt = this.store.prepare(sql);
|
|
84
|
-
const rows = (nullPid ? stmt.all() : stmt.all(projectId));
|
|
85
|
-
return rows;
|
|
86
|
-
}
|
|
87
|
-
collectCandidates(rows) {
|
|
88
|
-
// Group rows by session_id preserving arrival order. The SQL already
|
|
89
|
-
// orders by (session_id ASC, ts ASC), so a sequential scan yields each
|
|
90
|
-
// session's tool_calls in execution order.
|
|
91
|
-
const bySession = new Map();
|
|
92
|
-
for (const r of rows) {
|
|
93
|
-
let list = bySession.get(r.session_id);
|
|
94
|
-
if (!list) {
|
|
95
|
-
list = [];
|
|
96
|
-
bySession.set(r.session_id, list);
|
|
97
|
-
}
|
|
98
|
-
list.push(r);
|
|
99
|
-
}
|
|
100
|
-
// 2-grams: ordered pair (a, b) of consecutive tool names per session.
|
|
101
|
-
// 3-grams: ordered triple (a, b, c) where the middle tool b failed
|
|
102
|
-
// (success = 0). These capture the "X→Y fails then Z" lifecycle that
|
|
103
|
-
// the plan §B6.10 calls out.
|
|
104
|
-
const map = new Map();
|
|
105
|
-
const record = (key, content, sessionId) => {
|
|
106
|
-
let cand = map.get(key);
|
|
107
|
-
if (!cand) {
|
|
108
|
-
cand = { key, content, sessions: new Set() };
|
|
109
|
-
map.set(key, cand);
|
|
110
|
-
}
|
|
111
|
-
cand.sessions.add(sessionId);
|
|
112
|
-
};
|
|
113
|
-
for (const [sessionId, list] of bySession) {
|
|
114
|
-
for (let i = 0; i < list.length - 1; i++) {
|
|
115
|
-
const a = list[i];
|
|
116
|
-
const b = list[i + 1];
|
|
117
|
-
const key2 = `2g::${a.tool}::${b.tool}`;
|
|
118
|
-
const content2 = `Pattern: tool "${a.tool}" followed by tool "${b.tool}". Review the ${a.tool}→${b.tool} contract before retrying.`;
|
|
119
|
-
record(key2, content2, sessionId);
|
|
120
|
-
if (i + 2 < list.length) {
|
|
121
|
-
const c = list[i + 2];
|
|
122
|
-
if (b.success === 0) {
|
|
123
|
-
const key3 = `3g::${a.tool}::${b.tool}::${c.tool}`;
|
|
124
|
-
const content3 = `Pattern: tool "${a.tool}" followed by failing tool "${b.tool}" then tool "${c.tool}". Review the ${a.tool}→${b.tool}(failed)→${c.tool} contract before retrying.`;
|
|
125
|
-
record(key3, content3, sessionId);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return Array.from(map.values());
|
|
131
|
-
}
|
|
132
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type { DispatchedLesson } from "./Reflector.js";
|
|
2
|
-
/**
|
|
3
|
-
* v0.4.0 (K4-003/K4-004 / plan §5.1) — QualityGate.
|
|
4
|
-
*
|
|
5
|
-
* Pure, NO-LLM predicates deciding whether a lesson is worth injecting into
|
|
6
|
-
* the model's prompt. Principle 11: never inject noise.
|
|
7
|
-
*
|
|
8
|
-
* - `evaluate` — classifies a lesson (rescued errorType, non-generic
|
|
9
|
-
* suggestion, isActionable, strength).
|
|
10
|
-
* - `rescueErrorType` — a dispatched code overrides the coarse
|
|
11
|
-
* `errorType` (e.g. `'unknown'` → `'TS2304'`), fixing the defect where
|
|
12
|
-
* lessons said "fails with unknown" even when the Reflector DID identify
|
|
13
|
-
* the error code.
|
|
14
|
-
* - `canInject` — per-session + per-fingerprint admission rules (K4-004).
|
|
15
|
-
*/
|
|
16
|
-
/** The v0.1.x fallback suggestions. A lesson whose suggestion is one of these
|
|
17
|
-
* (and has no dispatched code) carries no actionable information. */
|
|
18
|
-
export declare const GENERIC_SUGGESTIONS: ReadonlySet<string>;
|
|
19
|
-
export declare function isGenericSuggestion(suggestion: string): boolean;
|
|
20
|
-
export interface LessonQuality {
|
|
21
|
-
/** Rescued errorType: the dispatched code when one matched, else the
|
|
22
|
-
* coarse errorType (may be `'unknown'`). */
|
|
23
|
-
errorType: string;
|
|
24
|
-
/** The lesson's suggestion text (rule-produced or generic fallback). */
|
|
25
|
-
suggestion: string;
|
|
26
|
-
/** True when the lesson carries actionable information: a dispatched
|
|
27
|
-
* code exists OR the suggestion is not one of the generic fallbacks. */
|
|
28
|
-
isActionable: boolean;
|
|
29
|
-
/** `strong` when a code matched or the rescued errorType is not
|
|
30
|
-
* `'unknown'`; `weak` otherwise. */
|
|
31
|
-
strength: "strong" | "weak";
|
|
32
|
-
}
|
|
33
|
-
export interface QualityLesson {
|
|
34
|
-
errorType: string;
|
|
35
|
-
suggestion: string;
|
|
36
|
-
}
|
|
37
|
-
export interface InjectionContext {
|
|
38
|
-
/** Memory ids already injected in the current session. */
|
|
39
|
-
seenThisSession: Set<string>;
|
|
40
|
-
/** Failing tool_calls count for this fingerprint in the current session
|
|
41
|
-
* (0 = never recurred after injection). */
|
|
42
|
-
recurrenceCount: number;
|
|
43
|
-
/** v0.6.0 (K6-022 / plan §5.8) — the `injection_confidence_floor`
|
|
44
|
-
* setting, read once per plan/inject call by the ContextInjector.
|
|
45
|
-
* When present, a memory whose `confidence` is below it is rejected by
|
|
46
|
-
* the zero branch (before the seen-set). Undefined disables the branch:
|
|
47
|
-
* memories without a computed confidence are never dropped by it. */
|
|
48
|
-
confidenceFloor?: number;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* v0.5.0 (K5-006 / plan §5.2, D5-04) — the reason a memory was rejected
|
|
52
|
-
* (or admitted). Every rejection reason maps 1:1 to an
|
|
53
|
-
* `injections_blocked_*` counter; a silent boolean is an unmeasurable
|
|
54
|
-
* policy (principle 16).
|
|
55
|
-
*/
|
|
56
|
-
export type GateReason = "ok" | "low_confidence" | "seen_this_session" | "ignored" | "not_active" | "recurrence" | "weak";
|
|
57
|
-
export interface GateVerdict {
|
|
58
|
-
readonly allowed: boolean;
|
|
59
|
-
readonly reason: GateReason;
|
|
60
|
-
}
|
|
61
|
-
export declare const QualityGate: {
|
|
62
|
-
/**
|
|
63
|
-
* A dispatched code always overrides the coarse errorType for display:
|
|
64
|
-
* `'unknown'` + `TS2304` → `'TS2304'`.
|
|
65
|
-
*/
|
|
66
|
-
rescueErrorType(dispatch: DispatchedLesson | null, errorType: string): string;
|
|
67
|
-
/** Classify a lesson per §5.1 rule 1 (rescue) and the generic ban. */
|
|
68
|
-
evaluate(lesson: QualityLesson, dispatch: DispatchedLesson | null, errorType: string): LessonQuality;
|
|
69
|
-
/**
|
|
70
|
-
* v0.4.0 (K4-004 / plan §5.1 rules 2-4) — admission gate evaluated at
|
|
71
|
-
* injection time. A lesson is injectable only when:
|
|
72
|
-
*
|
|
73
|
-
* 1. its memory id was not already injected this session (seen-set),
|
|
74
|
-
* 2. its memory is not `stale`,
|
|
75
|
-
* 3. its fingerprint has not recurred since injection (`recurrenceCount
|
|
76
|
-
* === 0` — a lesson that failed to prevent the error leaves the
|
|
77
|
-
* prompt),
|
|
78
|
-
* 4. it is actionable AND strong — weak lessons with generic suggestions
|
|
79
|
-
* are never injected (unless `qualityGateEnabled = false` for debug,
|
|
80
|
-
* K4-023).
|
|
81
|
-
*
|
|
82
|
-
* v0.5.0 (K5-006 / plan §5.2, D5-04) — the boolean form is a thin
|
|
83
|
-
* wrapper over `canInjectVerdict`; the reason is never discarded by
|
|
84
|
-
* callers that use the verdict form.
|
|
85
|
-
*/
|
|
86
|
-
canInject(memory: {
|
|
87
|
-
id: string;
|
|
88
|
-
status?: string;
|
|
89
|
-
strength?: "strong" | "weak";
|
|
90
|
-
isActionable?: boolean;
|
|
91
|
-
}, ctx: InjectionContext, qualityGateEnabled?: boolean): boolean;
|
|
92
|
-
/**
|
|
93
|
-
* v0.5.0 (K5-006 / plan §5.2, D5-04) — the verdict form of `canInject`.
|
|
94
|
-
* Branch order preserves the v0.4.0 sequence with `ignored` inserted
|
|
95
|
-
* second:
|
|
96
|
-
*
|
|
97
|
-
* 0. `confidence < ctx.confidenceFloor` → `low_confidence` (v0.6.0,
|
|
98
|
-
* K6-022 — runs FIRST: it is the cheapest check, and a memory below
|
|
99
|
-
* the floor should not consume a seen-set slot it will never use)
|
|
100
|
-
* 1. seen this session → `seen_this_session`
|
|
101
|
-
* 2. `memory.ignored === true` → `ignored`
|
|
102
|
-
* 3. status !== 'active' → `not_active`
|
|
103
|
-
* 4. recurrence in session → `recurrence`
|
|
104
|
-
* 5. weak / not actionable → `weak` (skipped entirely when
|
|
105
|
-
* `qualityGateEnabled === false` — the debug flag bypasses the
|
|
106
|
-
* quality check only, never the seen/ignored/status/recurrence bans)
|
|
107
|
-
*
|
|
108
|
-
* `ignored` is a human verdict (D5-07) and is enforced even in debug
|
|
109
|
-
* mode: the flag excludes a memory from retrieval entirely, so the gate
|
|
110
|
-
* is only ever asked about it defensively.
|
|
111
|
-
*
|
|
112
|
-
* The floor comparison is `>=`-admissive: a memory exactly AT the floor
|
|
113
|
-
* passes the branch (asserted in K6-022). A floor of '0' admits
|
|
114
|
-
* everything the other five branches allow.
|
|
115
|
-
*/
|
|
116
|
-
canInjectVerdict(memory: {
|
|
117
|
-
id: string;
|
|
118
|
-
status?: string;
|
|
119
|
-
strength?: "strong" | "weak";
|
|
120
|
-
isActionable?: boolean;
|
|
121
|
-
ignored?: boolean;
|
|
122
|
-
confidence?: number;
|
|
123
|
-
}, ctx: InjectionContext, qualityGateEnabled?: boolean): GateVerdict;
|
|
124
|
-
};
|