@jmtrin/opencode-kevin 1.0.0 → 1.2.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -8
  3. package/dist/migrations/001_initial.sql +91 -91
  4. package/dist/migrations/002_indexes.sql +13 -13
  5. package/dist/migrations/003_v02_signal.sql +57 -57
  6. package/dist/migrations/004_v03_knowledge.sql +138 -138
  7. package/dist/migrations/005_v04_signal.sql +57 -57
  8. package/dist/migrations/006_v05_glassbox.sql +118 -118
  9. package/dist/migrations/007_v06_pull.sql +144 -144
  10. package/dist/migrations/012_v11_drift.sql +24 -0
  11. package/dist/plugin/Archiver.js +2 -17
  12. package/dist/plugin/CausalChain.js +32 -13
  13. package/dist/plugin/ChatBridge.d.ts +41 -0
  14. package/dist/plugin/ChatBridge.js +103 -0
  15. package/dist/plugin/ConflictDetector.js +7 -29
  16. package/dist/plugin/DashboardHtml.d.ts +5 -0
  17. package/dist/plugin/DashboardHtml.js +180 -0
  18. package/dist/plugin/Feedback.js +2 -19
  19. package/dist/plugin/HookLiveness.d.ts +1 -0
  20. package/dist/plugin/HookLiveness.js +11 -27
  21. package/dist/plugin/InjectionLedger.js +104 -57
  22. package/dist/plugin/Materializer.js +1 -88
  23. package/dist/plugin/MemoryService.d.ts +59 -1
  24. package/dist/plugin/MemoryService.js +13 -106
  25. package/dist/plugin/Migrate.js +5 -0
  26. package/dist/plugin/Retrospective.js +7 -0
  27. package/dist/plugin/ToolCallObserver.js +18 -5
  28. package/dist/plugin/TuiActions.d.ts +43 -0
  29. package/dist/plugin/TuiActions.js +181 -0
  30. package/dist/plugin/TuiSnapshots.d.ts +24 -0
  31. package/dist/plugin/TuiSnapshots.js +158 -0
  32. package/dist/plugin/capabilities.d.ts +2 -0
  33. package/dist/plugin/capabilities.js +3 -0
  34. package/dist/plugin/columns.d.ts +11 -0
  35. package/dist/plugin/columns.js +54 -0
  36. package/dist/plugin/contract.d.ts +8 -0
  37. package/dist/plugin/contract.js +23 -5
  38. package/dist/plugin/index.d.ts +2 -2
  39. package/dist/plugin/index.js +315 -10
  40. package/dist/plugin/kevin_audit.d.ts +17 -1
  41. package/dist/plugin/kevin_audit.js +69 -1
  42. package/dist/plugin/kevin_forget.d.ts +33 -0
  43. package/dist/plugin/kevin_forget.js +260 -0
  44. package/dist/plugin/kevin_why.js +1 -18
  45. package/dist/plugin/metrics.d.ts +1 -1
  46. package/dist/plugin/metrics.js +8 -0
  47. package/dist/plugin/query-tokenizer.js +56 -8
  48. package/dist/plugin/time-ms.d.ts +1 -0
  49. package/dist/plugin/time-ms.js +16 -0
  50. package/dist/plugin/tui-types.d.ts +59 -0
  51. package/dist/plugin/tui-types.js +4 -0
  52. package/dist/plugin/tui.d.ts +18 -0
  53. package/dist/plugin/tui.js +198 -0
  54. package/package.json +8 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jmtrin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -26,15 +26,19 @@ injects exactly what matters back into the model's context, curates the best
26
26
  of it into files you control, and shares it across a team through one
27
27
  git-friendly file — deterministically, locally, with zero network calls.
28
28
 
29
- ![version](https://img.shields.io/badge/version-1.0.0-blue)
29
+ ![version](https://img.shields.io/badge/version-1.1.0-blue)
30
30
  ![node](https://img.shields.io/badge/node-%E2%89%A522.5-green)
31
- ![tests](https://img.shields.io/badge/tests-1374%20passing-brightgreen)
31
+ ![tests](https://img.shields.io/badge/tests-1380%20passing-brightgreen)
32
32
  ![deps](https://img.shields.io/badge/runtime%20deps-1-orange)
33
33
  ![network](https://img.shields.io/badge/network-zero-black)
34
34
  ![license](https://img.shields.io/badge/license-MIT-lightgrey)
35
35
 
36
36
  </div>
37
37
 
38
+ <!-- uncomment when docs/demo.gif lands
39
+ ![demo](docs/demo.gif)
40
+ -->
41
+
38
42
  > **AI agents are good at solving problems. Kevin makes sure they don't have to solve the same problem twice.**
39
43
 
40
44
  ---
@@ -44,9 +48,10 @@ git-friendly file — deterministically, locally, with zero network calls.
44
48
  - [Why Kevin](#-why-kevin)
45
49
  - [The Kevin loop](#-the-kevin-loop)
46
50
  - [Quick start](#-quick-start)
51
+ - [What's new in 1.1.0](#-whats-new-in-110)
47
52
  - [What's new in 1.0.0](#-whats-new-in-100)
48
53
  - [How it works](#-how-it-works)
49
- - [The 25 tools](#-the-25-tools)
54
+ - [The 26 tools](#-the-26-tools)
50
55
  - [The benchmark: proven, not promised](#-the-benchmark-proven-not-promised)
51
56
  - [Curation: from session noise to AGENTS.md](#-curation-from-session-noise-to-agentsmd)
52
57
  - [Team sharing: one file, zero servers](#-team-sharing-one-file-zero-servers)
@@ -136,7 +141,7 @@ npm install @jmtrin/opencode-kevin
136
141
 
137
142
  ### 2. Restart OpenCode
138
143
 
139
- On first boot Kevin migrates its database to schema version `011` and starts
144
+ On first boot Kevin migrates its database to schema version `012` and starts
140
145
  observing. Nothing else is required.
141
146
 
142
147
  ### 3. Talk to it
@@ -154,7 +159,15 @@ kevin_doctor → health report: hooks, deps, perf, verdict
154
159
  ~/.opencode-kevin/
155
160
  ├── kevin.db ← everything Kevin learns (SQLite, WAL)
156
161
  ├── skills/ ← generated pull channels
157
- └── refs/ ← topic reference bundles
162
+ ├── refs/ ← topic reference bundles
163
+ └── tui/
164
+ ├── proposals.json ← pending proposals projection (512 KiB cap)
165
+ ├── conflicts.json ← open conflicts projection
166
+ ├── health.json ← doctor+perf snapshot
167
+ ├── meta.json ← {generatedAt, versions}
168
+ ├── dashboard.html ← static review surface (file://, zero network)
169
+ ├── actions.json ← mailbox queue (TUI panels → session.idle)
170
+ └── results.json ← last action results (audit)
158
171
 
159
172
  <repo>/.kevin/
160
173
  ├── AGENTS.md ← curated knowledge (marker block, human-approved)
@@ -163,6 +176,38 @@ kevin_doctor → health report: hooks, deps, perf, verdict
163
176
 
164
177
  ---
165
178
 
179
+ ## 🆕 What's new in 1.1.0 — "Drift"
180
+
181
+ > 1.1.0 protects what 1.0.0 proved: a published number without a regression gate is marketing.
182
+
183
+ - 🛡️ **Continuous benchmark gate** — `npm run bench:regress` compares the last two `bench/results` against per-metric thresholds (`precision@k` >0.02, `recall` >0.05, `mrr` >0.05 on the `kevin` arm); CI fails when truth drifts.
184
+ - 🗑️ **Lifecycle closure — `kevin_forget`** — dry-run default, `confirm:true` archives locally and publishes a tombstone through the single write path; second identical run is a `noop`.
185
+ - ⏱️ **Millisecond timestamps** — new `_ms` columns with conservative backfill; `settle()` and `CausalChain` now decide sub-second causality.
186
+ - 🧹 **Debt paid** — one `STOP_WORDS` source, one `readOriginCallId`, `ConflictDetector` via `mapRow`, one column-probe registry; every setting has an on-path test.
187
+ - 📜 **Public hygiene** — `LICENSE` (MIT), `homepage` filled, `docs/DISTRIBUTION.md` checklist, `scripts/release-notes.mjs` for `gh release create`, and `<!-- demo -->` slot.
188
+
189
+ ---
190
+
191
+ ## 🆕 What's new in 1.2.0 — "Surface"
192
+
193
+ > 1.2.0 gives the human-in-the-loop a place to stand: every pending proposal is readable with its diff without opening an editor, and every approval rides the same gated handler.
194
+
195
+ - 🖥️ **Three review surfaces, one projection** — TUI panels (`/kevin` route, `k` to open) where the host renders them, plus a static `dashboard.html` under `~/.opencode-kevin/tui/` that opens via `file://` with zero network, zero fetch, inline CSS/JS and embedded JSON.
196
+ - 💬 **Chat-command bridge `/kevin-*` (universal, immediate)** — `/kevin-approve <id> <token>`, `/kevin-reject <id> <token> [note]`, `/kevin-ack <id>` execute through the existing `kevinApprove` / acknowledge handlers; valid commands are swallowed, invalid/stale ones pass through untouched to the model.
197
+ - 📦 **Mailbox for TUI panels (idle-latency)** — proposals approved from the TUI write `~/.opencode-kevin/tui/actions.json`; the session consumes it at the next `session.idle` before `curator.propose`, then refreshes snapshots (`tui_snapshots_enabled='1'`).
198
+ - ⏱️ **Latency honesty** — chat commands apply this turn; mailbox actions apply at next idle. Both disclosed in-surface (`queued — applies at session idle` toast, copy hint on dashboard).
199
+ - 📦 **Packaging** — new export `opencode-kevin/tui` (`dist/plugin/tui.*`), `engines.opencode ^1.18.0` validated by the host with skip-with-warning.
200
+
201
+ | Surface | Review | Action | Latency |
202
+ |---|---|---|---|
203
+ | TUI panel (`/kevin`) — CLI/TUI host | Proposals / Conflicts / Health tabs, diff dialog, truncation markers | `a` approve / `r` reject / `x` ack → mailbox | next idle |
204
+ | Static dashboard (`dashboard.html`) | Proposals (+escaped diff `<pre>`), conflicts two-column, health banner | Copy `/kevin-*` button → paste into chat input | immediate (chat bridge) |
205
+ | Any client (Desktop, CLI) | — | Type `/kevin-approve …` etc in chat input | immediate |
206
+
207
+ Snapshots + dashboard are capped at 512 KiB (diff truncation with `truncated:true`), written atomically via `tmp`+`rename`, and read best-effort with empty-state explanations (`no snapshots yet — open an opencode session…`) when missing/corrupt/stale-token.
208
+
209
+ ---
210
+
166
211
  ## 🆕 What's new in 1.0.0
167
212
 
168
213
  > 1.0.0 is the **proven release**: the surface is frozen as data, the cost is
@@ -231,7 +276,7 @@ Kevin is an intentionally deterministic pipeline — no LLM in the core loop:
231
276
 
232
277
  ---
233
278
 
234
- ## 🧰 The 25 tools
279
+ ## 🧰 The 26 tools
235
280
 
236
281
  <details open>
237
282
  <summary><b>🧠 Core memory</b></summary>
@@ -472,6 +517,7 @@ client). All values are TEXT — flags compare with `=== "1"`, never truthiness.
472
517
  | `perf_ring_capacity` | `'512'` | Samples per scope (clamped `[64, 8192]`) |
473
518
  | `perf_flush_on_idle` | `'1'` | Persist samples at idle |
474
519
  | `contract_report_enabled` | `'1'` | Contract block in `kevin_audit` |
520
+ | `tui_snapshots_enabled` | `'1'` | Snapshot + dashboard flush at idle (opencode-kevin/tui) |
475
521
 
476
522
  ---
477
523
 
@@ -537,8 +583,8 @@ npm run replay # replay recorded sessions deterministically
537
583
  Project layout:
538
584
 
539
585
  ```
540
- plugin/ 51 modules — Store, MemoryService, Reflector, Perf, Contract, …
541
- migrations/ 001 → 011 — additive, idempotent, forward-only forever
586
+ plugin/ 53 modules — Store, MemoryService, Reflector, Perf, Contract, columns, kevin_forget
587
+ migrations/ 001 → 012 — additive, idempotent, forward-only forever
542
588
  scripts/ bench · gen-corpus · verify-pack · verify-install · …
543
589
  tests/ unit · integration · e2e · replay fixtures
544
590
  bench/ committed corpus + committed results
@@ -1,92 +1,92 @@
1
- -- ============================================================
2
- -- Kevin 0.1.0 — Schema inicial
3
- -- ============================================================
4
-
5
- -- Tabla de versiones para migraciones
6
- CREATE TABLE IF NOT EXISTS schema_version (
7
- version TEXT PRIMARY KEY,
8
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
9
- );
10
-
11
- -- ============================================================
12
- -- memories: lecciones aprendidas
13
- -- ============================================================
14
- CREATE TABLE IF NOT EXISTS memories (
15
- id TEXT PRIMARY KEY,
16
- type TEXT NOT NULL CHECK(type IN ('error', 'pattern', 'decision', 'context')),
17
- content TEXT NOT NULL,
18
- scope TEXT NOT NULL DEFAULT 'project' CHECK(scope IN ('project', 'session')),
19
- relevance_score REAL DEFAULT 0.5,
20
- source_tool TEXT,
21
- source_session TEXT,
22
- metadata TEXT,
23
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
24
- updated_at TEXT NOT NULL DEFAULT (datetime('now')),
25
- expires_at TEXT
26
- );
27
-
28
- CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
29
- CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
30
- CREATE INDEX IF NOT EXISTS idx_memories_relevance ON memories(relevance_score DESC);
31
- CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
32
-
33
- -- FTS5: búsqueda full-text con remoción de diacríticos (mejor para español)
34
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
35
- content,
36
- content='memories',
37
- tokenize='unicode61 remove_diacritics 1'
38
- );
39
-
40
- -- Triggers para mantener FTS5 sincronizado (FTS5 external-content se indexa por rowid)
41
- CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
42
- INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
43
- END;
44
-
45
- CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
46
- INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
47
- END;
48
-
49
- CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
50
- INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
51
- INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
52
- END;
53
-
54
- -- ============================================================
55
- -- tool_calls: observación de tool calls del agente
56
- -- ============================================================
57
- CREATE TABLE IF NOT EXISTS tool_calls (
58
- id TEXT PRIMARY KEY,
59
- session_id TEXT NOT NULL,
60
- ts TEXT NOT NULL DEFAULT (datetime('now')),
61
- tool TEXT NOT NULL,
62
- args_summary TEXT,
63
- success INTEGER NOT NULL CHECK(success IN (0,1)),
64
- duration_ms INTEGER,
65
- agent TEXT,
66
- error_type TEXT,
67
- metadata TEXT
68
- );
69
-
70
- CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);
71
- CREATE INDEX IF NOT EXISTS idx_tool_calls_tool ON tool_calls(tool);
72
- CREATE INDEX IF NOT EXISTS idx_tool_calls_ts ON tool_calls(ts);
73
- CREATE INDEX IF NOT EXISTS idx_tool_calls_success ON tool_calls(success);
74
-
75
- -- ============================================================
76
- -- retrospectives: resúmenes de sesión
77
- -- ============================================================
78
- CREATE TABLE IF NOT EXISTS retrospectives (
79
- id TEXT PRIMARY KEY,
80
- session_id TEXT NOT NULL,
81
- ts TEXT NOT NULL DEFAULT (datetime('now')),
82
- failure_count INTEGER DEFAULT 0,
83
- success_count INTEGER DEFAULT 0,
84
- lessons_count INTEGER DEFAULT 0,
85
- file_path TEXT,
86
- metadata TEXT
87
- );
88
-
89
- -- ============================================================
90
- -- Seed: versión inicial
91
- -- ============================================================
1
+ -- ============================================================
2
+ -- Kevin 0.1.0 — Schema inicial
3
+ -- ============================================================
4
+
5
+ -- Tabla de versiones para migraciones
6
+ CREATE TABLE IF NOT EXISTS schema_version (
7
+ version TEXT PRIMARY KEY,
8
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
9
+ );
10
+
11
+ -- ============================================================
12
+ -- memories: lecciones aprendidas
13
+ -- ============================================================
14
+ CREATE TABLE IF NOT EXISTS memories (
15
+ id TEXT PRIMARY KEY,
16
+ type TEXT NOT NULL CHECK(type IN ('error', 'pattern', 'decision', 'context')),
17
+ content TEXT NOT NULL,
18
+ scope TEXT NOT NULL DEFAULT 'project' CHECK(scope IN ('project', 'session')),
19
+ relevance_score REAL DEFAULT 0.5,
20
+ source_tool TEXT,
21
+ source_session TEXT,
22
+ metadata TEXT,
23
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
24
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
25
+ expires_at TEXT
26
+ );
27
+
28
+ CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
29
+ CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
30
+ CREATE INDEX IF NOT EXISTS idx_memories_relevance ON memories(relevance_score DESC);
31
+ CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
32
+
33
+ -- FTS5: búsqueda full-text con remoción de diacríticos (mejor para español)
34
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
35
+ content,
36
+ content='memories',
37
+ tokenize='unicode61 remove_diacritics 1'
38
+ );
39
+
40
+ -- Triggers para mantener FTS5 sincronizado (FTS5 external-content se indexa por rowid)
41
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
42
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
43
+ END;
44
+
45
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
46
+ INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
47
+ END;
48
+
49
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
50
+ INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
51
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
52
+ END;
53
+
54
+ -- ============================================================
55
+ -- tool_calls: observación de tool calls del agente
56
+ -- ============================================================
57
+ CREATE TABLE IF NOT EXISTS tool_calls (
58
+ id TEXT PRIMARY KEY,
59
+ session_id TEXT NOT NULL,
60
+ ts TEXT NOT NULL DEFAULT (datetime('now')),
61
+ tool TEXT NOT NULL,
62
+ args_summary TEXT,
63
+ success INTEGER NOT NULL CHECK(success IN (0,1)),
64
+ duration_ms INTEGER,
65
+ agent TEXT,
66
+ error_type TEXT,
67
+ metadata TEXT
68
+ );
69
+
70
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);
71
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_tool ON tool_calls(tool);
72
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_ts ON tool_calls(ts);
73
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_success ON tool_calls(success);
74
+
75
+ -- ============================================================
76
+ -- retrospectives: resúmenes de sesión
77
+ -- ============================================================
78
+ CREATE TABLE IF NOT EXISTS retrospectives (
79
+ id TEXT PRIMARY KEY,
80
+ session_id TEXT NOT NULL,
81
+ ts TEXT NOT NULL DEFAULT (datetime('now')),
82
+ failure_count INTEGER DEFAULT 0,
83
+ success_count INTEGER DEFAULT 0,
84
+ lessons_count INTEGER DEFAULT 0,
85
+ file_path TEXT,
86
+ metadata TEXT
87
+ );
88
+
89
+ -- ============================================================
90
+ -- Seed: versión inicial
91
+ -- ============================================================
92
92
  INSERT OR IGNORE INTO schema_version (version) VALUES ('001');
@@ -1,14 +1,14 @@
1
- -- ============================================================
2
- -- Kevin 0.1.1 — Migration 002: indexes adicionales
3
- -- ============================================================
4
-
5
- -- F#29: uniqueness on retrospectives.session_id
6
- -- Prevents duplicate retrospective rows under concurrent session.idle events.
7
- CREATE UNIQUE INDEX IF NOT EXISTS idx_retrospectives_session
8
- ON retrospectives(session_id);
9
-
10
- -- F#31: index on memories.expires_at
11
- -- Every query/queryRelevant/loadAll filters WHERE (expires_at IS NULL OR expires_at > datetime('now')).
12
- -- Without this index the filter is a linear scan on large tables.
13
- CREATE INDEX IF NOT EXISTS idx_memories_expires
1
+ -- ============================================================
2
+ -- Kevin 0.1.1 — Migration 002: indexes adicionales
3
+ -- ============================================================
4
+
5
+ -- F#29: uniqueness on retrospectives.session_id
6
+ -- Prevents duplicate retrospective rows under concurrent session.idle events.
7
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_retrospectives_session
8
+ ON retrospectives(session_id);
9
+
10
+ -- F#31: index on memories.expires_at
11
+ -- Every query/queryRelevant/loadAll filters WHERE (expires_at IS NULL OR expires_at > datetime('now')).
12
+ -- Without this index the filter is a linear scan on large tables.
13
+ CREATE INDEX IF NOT EXISTS idx_memories_expires
14
14
  ON memories(expires_at);
@@ -1,58 +1,58 @@
1
- -- ============================================================
2
- -- Kevin 0.2.0 — Migration 003: Signal Quality (additive)
3
- -- ============================================================
4
- -- Backward-compatible, additive only. All new columns are
5
- -- nullable or carry a NOT NULL DEFAULT so legacy rows keep
6
- -- working without a destructive rebuild. The partial UNIQUE
7
- -- index excludes NULL fingerprints so legacy error memories
8
- -- do not collide. The schema_version table (created by 001)
9
- -- guarantees this file runs at most once per DB.
10
- -- ============================================================
11
-
12
- -- 1. memories: Signal Quality columns.
13
- -- project_id — first-class scoping dimension (D2-11).
14
- -- fingerprint — stable hash of normalized error content, salted by project_id (D2-14).
15
- -- origin — distinguishes reflector-sourced vs agent-sourced memories (D2-06),
16
- -- with a CHECK enum and DEFAULT 'agent' so legacy rows backfill cleanly.
17
- ALTER TABLE memories ADD COLUMN project_id TEXT;
18
- ALTER TABLE memories ADD COLUMN fingerprint TEXT;
19
- ALTER TABLE memories ADD COLUMN origin TEXT NOT NULL DEFAULT 'agent'
20
- CHECK(origin IN ('reflector', 'agent', 'pattern', 'retrospective'));
21
-
22
- -- 2. tool_calls: same scoping/fingerprint columns for dedup and PatternMiner.
23
- ALTER TABLE tool_calls ADD COLUMN project_id TEXT;
24
- ALTER TABLE tool_calls ADD COLUMN fingerprint TEXT;
25
-
26
- -- 3. Partial UNIQUE: one reflector-sourced error memory per (project_id, fingerprint).
27
- -- NULL fingerprints are excluded (no dedup for non-error memories or legacy rows).
28
- CREATE UNIQUE INDEX IF NOT EXISTS uq_memories_error_fp
29
- ON memories (project_id, fingerprint)
30
- WHERE type = 'error' AND fingerprint IS NOT NULL AND origin = 'reflector';
31
-
32
- -- 4. kevin_metrics: seeded counters surfaced by kevin_status (D2-07 / K2-004).
33
- CREATE TABLE IF NOT EXISTS kevin_metrics (
34
- key TEXT PRIMARY KEY,
35
- value INTEGER NOT NULL DEFAULT 0,
36
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
37
- );
38
-
39
- INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
40
- ('tokens_injected_pre_prompt', 0),
41
- ('tokens_injected_compacting', 0),
42
- ('reflections_throttled', 0),
43
- ('duplicate_suppressions', 0),
44
- ('tool_calls_deduped', 0),
45
- ('patterns_mined', 0);
46
-
47
- -- 5. kevin_settings: opt-in feature flags (PatternMiner off, tool_calls dedup off).
48
- CREATE TABLE IF NOT EXISTS kevin_settings (
49
- key TEXT PRIMARY KEY,
50
- value TEXT NOT NULL
51
- );
52
-
53
- INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
54
- ('patternminer_enabled', '0'),
55
- ('tool_calls_dedup_enabled', '0');
56
-
57
- -- 6. Seed version 003.
1
+ -- ============================================================
2
+ -- Kevin 0.2.0 — Migration 003: Signal Quality (additive)
3
+ -- ============================================================
4
+ -- Backward-compatible, additive only. All new columns are
5
+ -- nullable or carry a NOT NULL DEFAULT so legacy rows keep
6
+ -- working without a destructive rebuild. The partial UNIQUE
7
+ -- index excludes NULL fingerprints so legacy error memories
8
+ -- do not collide. The schema_version table (created by 001)
9
+ -- guarantees this file runs at most once per DB.
10
+ -- ============================================================
11
+
12
+ -- 1. memories: Signal Quality columns.
13
+ -- project_id — first-class scoping dimension (D2-11).
14
+ -- fingerprint — stable hash of normalized error content, salted by project_id (D2-14).
15
+ -- origin — distinguishes reflector-sourced vs agent-sourced memories (D2-06),
16
+ -- with a CHECK enum and DEFAULT 'agent' so legacy rows backfill cleanly.
17
+ ALTER TABLE memories ADD COLUMN project_id TEXT;
18
+ ALTER TABLE memories ADD COLUMN fingerprint TEXT;
19
+ ALTER TABLE memories ADD COLUMN origin TEXT NOT NULL DEFAULT 'agent'
20
+ CHECK(origin IN ('reflector', 'agent', 'pattern', 'retrospective'));
21
+
22
+ -- 2. tool_calls: same scoping/fingerprint columns for dedup and PatternMiner.
23
+ ALTER TABLE tool_calls ADD COLUMN project_id TEXT;
24
+ ALTER TABLE tool_calls ADD COLUMN fingerprint TEXT;
25
+
26
+ -- 3. Partial UNIQUE: one reflector-sourced error memory per (project_id, fingerprint).
27
+ -- NULL fingerprints are excluded (no dedup for non-error memories or legacy rows).
28
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_memories_error_fp
29
+ ON memories (project_id, fingerprint)
30
+ WHERE type = 'error' AND fingerprint IS NOT NULL AND origin = 'reflector';
31
+
32
+ -- 4. kevin_metrics: seeded counters surfaced by kevin_status (D2-07 / K2-004).
33
+ CREATE TABLE IF NOT EXISTS kevin_metrics (
34
+ key TEXT PRIMARY KEY,
35
+ value INTEGER NOT NULL DEFAULT 0,
36
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
37
+ );
38
+
39
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
40
+ ('tokens_injected_pre_prompt', 0),
41
+ ('tokens_injected_compacting', 0),
42
+ ('reflections_throttled', 0),
43
+ ('duplicate_suppressions', 0),
44
+ ('tool_calls_deduped', 0),
45
+ ('patterns_mined', 0);
46
+
47
+ -- 5. kevin_settings: opt-in feature flags (PatternMiner off, tool_calls dedup off).
48
+ CREATE TABLE IF NOT EXISTS kevin_settings (
49
+ key TEXT PRIMARY KEY,
50
+ value TEXT NOT NULL
51
+ );
52
+
53
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
54
+ ('patternminer_enabled', '0'),
55
+ ('tool_calls_dedup_enabled', '0');
56
+
57
+ -- 6. Seed version 003.
58
58
  INSERT OR IGNORE INTO schema_version (version) VALUES ('003');