@jmtrin/opencode-kevin 0.2.0 → 0.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.
Files changed (58) hide show
  1. package/README.md +108 -35
  2. package/dist/migrations/004_v03_knowledge.sql +138 -0
  3. package/dist/migrations/005_v04_signal.sql +57 -0
  4. package/dist/plugin/CausalChain.d.ts +22 -0
  5. package/dist/plugin/CausalChain.js +179 -0
  6. package/dist/plugin/CausalChain.js.map +1 -0
  7. package/dist/plugin/ContextInjector.d.ts +89 -1
  8. package/dist/plugin/ContextInjector.js +276 -71
  9. package/dist/plugin/ContextInjector.js.map +1 -1
  10. package/dist/plugin/InjectionLedger.d.ts +85 -0
  11. package/dist/plugin/InjectionLedger.js +189 -0
  12. package/dist/plugin/InjectionLedger.js.map +1 -0
  13. package/dist/plugin/LessonFixer.d.ts +44 -0
  14. package/dist/plugin/LessonFixer.js +46 -0
  15. package/dist/plugin/LessonFixer.js.map +1 -0
  16. package/dist/plugin/MemoryService.d.ts +123 -2
  17. package/dist/plugin/MemoryService.js +439 -31
  18. package/dist/plugin/MemoryService.js.map +1 -1
  19. package/dist/plugin/Migrate.js +22 -0
  20. package/dist/plugin/Migrate.js.map +1 -1
  21. package/dist/plugin/QualityGate.d.ts +71 -0
  22. package/dist/plugin/QualityGate.js +78 -0
  23. package/dist/plugin/QualityGate.js.map +1 -0
  24. package/dist/plugin/Reflector.d.ts +33 -0
  25. package/dist/plugin/Reflector.js +126 -32
  26. package/dist/plugin/Reflector.js.map +1 -1
  27. package/dist/plugin/Retrospective.js +10 -1
  28. package/dist/plugin/Retrospective.js.map +1 -1
  29. package/dist/plugin/ToolCallObserver.d.ts +1 -0
  30. package/dist/plugin/ToolCallObserver.js +16 -9
  31. package/dist/plugin/ToolCallObserver.js.map +1 -1
  32. package/dist/plugin/confidence.d.ts +6 -0
  33. package/dist/plugin/confidence.js +23 -0
  34. package/dist/plugin/confidence.js.map +1 -0
  35. package/dist/plugin/index.d.ts +5 -0
  36. package/dist/plugin/index.js +336 -44
  37. package/dist/plugin/index.js.map +1 -1
  38. package/dist/plugin/kevin_why.d.ts +23 -0
  39. package/dist/plugin/kevin_why.js +108 -0
  40. package/dist/plugin/kevin_why.js.map +1 -0
  41. package/dist/plugin/memory-format.d.ts +12 -0
  42. package/dist/plugin/memory-format.js +45 -5
  43. package/dist/plugin/memory-format.js.map +1 -1
  44. package/dist/plugin/metrics.d.ts +7 -1
  45. package/dist/plugin/metrics.js +19 -0
  46. package/dist/plugin/metrics.js.map +1 -1
  47. package/dist/plugin/okf-export.d.ts +3 -0
  48. package/dist/plugin/okf-export.js +127 -0
  49. package/dist/plugin/okf-export.js.map +1 -0
  50. package/dist/plugin/okf-import.d.ts +76 -0
  51. package/dist/plugin/okf-import.js +272 -0
  52. package/dist/plugin/okf-import.js.map +1 -0
  53. package/dist/plugin/query-tokenizer.d.ts +13 -0
  54. package/dist/plugin/query-tokenizer.js +86 -0
  55. package/dist/plugin/query-tokenizer.js.map +1 -0
  56. package/migrations/004_v03_knowledge.sql +138 -0
  57. package/migrations/005_v04_signal.sql +57 -0
  58. package/package.json +1 -1
package/README.md CHANGED
@@ -6,6 +6,9 @@ Kevin is an [OpenCode](https://opencode.ai) plugin that **observes** every agent
6
6
 
7
7
  - **Local-first**: SQLite + FTS5, no external services, no network calls.
8
8
  - **Global memory**: a single `~/.opencode-kevin/kevin.db` shared across all your projects (WAL mode → safe for concurrent sessions). No per-project folders.
9
+ - **Knowledge + Causality (v0.3.0)**: causal failure→fix chains, `kevin_why` explanations, OKF export/import, a supersede model, and human-in-the-loop AGENTS.md suggestions.
10
+ - **Signal over Noise (v0.4.0)**: a quality gate that stores weak lessons without injecting them, an injection ledger with honest `precision_rate`, two-sided confidence, and a fixed compacting hook.
11
+ - **Audited**: the v0.4.0 bug catalog (`docs/Kevin_v0.4.0_Bugs.md`) is fully closed — 16/16 bugs fixed and regression-tested (evidence in `kevin_query`/`kevin_get`, OKF round-trip fidelity, causal refresh guard, redaction precision, cross-session isolation).
9
12
  - **Standalone**: works without any other plugin. With the ecosystem, it learns more richly.
10
13
 
11
14
  ---
@@ -30,7 +33,7 @@ For a **single project**, put the same `plugin` array in `./opencode.json` or `.
30
33
 
31
34
  ### 2. Restart OpenCode
32
35
 
33
- Config is loaded once at startup and is **not hot-reloaded** — quit and reopen OpenCode after editing. On start, OpenCode resolves the npm spec, caches the plugin in `~/.cache/opencode/packages/@jmtrin/opencode-kevin/`, and exposes six tools: `kevin_save`, `kevin_query`, `kevin_get`, `kevin_recall`, `kevin_status`, `kevin_retrospective`.
36
+ Config is loaded once at startup and is **not hot-reloaded** — quit and reopen OpenCode after editing. On start, OpenCode resolves the npm spec, caches the plugin in `~/.cache/opencode/packages/@jmtrin/opencode-kevin/`, and exposes ten tools: `kevin_save`, `kevin_query`, `kevin_get`, `kevin_recall`, `kevin_status`, `kevin_retrospective`, `kevin_why`, `kevin_export`, `kevin_import`, `kevin_config`.
34
37
 
35
38
  ### 3. Where data lives
36
39
 
@@ -89,32 +92,43 @@ Use `:memory:` for `dbPath` in tests.
89
92
 
90
93
  ┌─────────────────┐
91
94
  │ OBSERVE │ ToolCallObserver records every call
92
- │ ToolCallObserver│ (tool, args redacted, success, duration, error_type)
93
- │ │ + stripPrivate blocks + opt-in dedup (v0.2.0)
95
+ │ ToolCallObserver│ (tool, args redacted, success, duration, error_type,
96
+ │ │ id = callID) + stripPrivate + opt-in dedup (v0.2.0)
94
97
  └────────┬────────┘
95
- │ on failure
96
-
97
- ┌─────────────────┐
98
- │ LEARN │ Reflector generates a heuristic lesson
99
- │ Reflector │ per-error-code code table (v0.2.0 lesson v2),
100
- per-fingerprint throttle, origin='reflector',
101
- fingerprint=FNV-1a 64-bit
102
- └────────┬────────┘
103
-
104
-
105
- ┌─────────────────┐
106
- SHARE │ ContextInjector injects relevant lessons
107
- ContextInjector │ pre-prompt (1500 tokens) and on compacting (2000 tokens)
108
- + <protect> wrapper + id: line (v0.2.0)
98
+ │ on failure │ on success
99
+
100
+ ┌─────────────────┐ ┌─────────────────────────┐
101
+ │ LEARN │ CAUSAL CHAIN (v0.3.0) │
102
+ │ Reflector │ CausalChain.onSuccess │
103
+ heuristic lesson links the fix to the │
104
+ per-error-code failure within 10 tool │
105
+ │ rule table │ │ calls (error_fingerprint)
106
+ (v0.2.0 v2), │ └───────────┬─────────────┘
107
+ │ per-fingerprint │ │
108
+ │ throttle BEFORE │ │ session.idle
109
+ LLM enrich │ ▼
110
+ (opt-in v0.3.0),│ ┌─────────────────────────┐
111
+ stamps CausalChain.onSessionIdle│
112
+ │ error_fingerprint│ │ promotes recurring errors│
113
+ └────────┬────────┘ │ → causal patterns (idempotent,
114
+ │ │ cumulative evidence) │
115
+ ▼ └───────────┬───────────────┘
116
+ ┌─────────────────┐ │
117
+ │ SHARE │◄──────────────┘
118
+ │ ContextInjector │ injects relevant lessons pre-prompt
119
+ │ │ (1500 tokens) + on compacting (2000)
120
+ │ │ + <protect> + id: line (v0.2.0)
109
121
  │ │ + origin-aware rank (v0.2.0)
110
- │ │ + conditional budget (v0.2.0)
122
+ │ │ + <kevin-suggestion> after negative
123
+ │ │ feedback half (HITL, v0.3.0)
111
124
  └────────┬────────┘
112
125
  │ session.idle
113
126
 
114
127
  ┌─────────────────┐
115
- │ RETROSPECTIVE │ Retrospective generates ~/.opencode-kevin/retrospectives/<session>.md
116
- │ │ with origin labels, false-positive recap, metrics snapshot (v0.2.0)
128
+ │ RETROSPECTIVE │ generates ~/.opencode-kevin/retrospectives/<session>.md
129
+ │ │ with origin labels, FP recap, metrics snapshot (v0.2.0)
117
130
  │ │ + boostPositiveReflectors (v0.2.0)
131
+ │ │ + penalizeRecurringReflectors (v0.3.0)
118
132
  │ │ + PatternMiner.mine (opt-in, v0.2.0)
119
133
  └─────────────────┘
120
134
  ```
@@ -123,7 +137,7 @@ Use `:memory:` for `dbPath` in tests.
123
137
 
124
138
  ## Tools
125
139
 
126
- Kevin exposes 6 tools callable by the agent:
140
+ Kevin exposes 10 tools callable by the agent:
127
141
 
128
142
  ### `kevin_save`
129
143
 
@@ -134,11 +148,13 @@ kevin_save({ type: "decision", content: "We use vitest for tests", scope: "proje
134
148
  // → { "id": "0195a3b2-..." }
135
149
  ```
136
150
 
137
- `type`: `error` | `pattern` | `decision` | `context`. `scope`: `project` (persists) | `session` (TTL 24h).
151
+ `type`: `error` | `pattern` | `decision` | `context` | `rule` | `solution` (v0.3.0). `scope`: `project` (persists) | `session` (TTL 24h).
152
+
153
+ Saving a `decision` or `rule` with the same `fingerprint` as an existing active row supersedes the old one (v0.3.0 — `status='superseded'`, hidden from default queries).
138
154
 
139
155
  ### `kevin_query`
140
156
 
141
- Searches memories by text (FTS5 + bm25). Returns a **slim** payload by default (v0.2.0). Pass `full: true` for the v0.1.x full content body.
157
+ Searches memories by text (FTS5 + bm25). Returns a **slim** payload by default (v0.2.0). Pass `full: true` for the v0.1.x full content body, or `evidence: true` (v0.3.0) to include `confidence`, `evidence_count`, `last_verified_at` in the slim payload.
142
158
 
143
159
  ```
144
160
  kevin_query({ query: "typecheck", type: "error", limit: 5 })
@@ -156,12 +172,14 @@ Fetches a **single full memory** by id (v0.2.0 — progressive disclosure). Use
156
172
  kevin_get({ id: "0195a3b2-..." })
157
173
  // → { "id": "...", "type": "error", "content": "...", "scope": "project",
158
174
  // "relevanceScore": 0.55, "origin": "reflector", "fingerprint": "cbf29ce484222325",
159
- // "projectId": null, "metadata": null }
175
+ // "projectId": null, "metadata": null,
176
+ // "evidenceCount": 2, "recurrenceCount": 1, "lastVerifiedAt": "2026-08-01 10:00:00",
177
+ // "status": "active", "confidence": 0.55, "fixArgs": "npm i -g rg" }
160
178
  ```
161
179
 
162
180
  ### `kevin_recall`
163
181
 
164
- Retrieves relevant memories (greedy fill by relevance). Without `query`, returns all memories in scope.
182
+ Retrieves relevant memories (greedy fill by relevance). Without `query`, returns all memories in scope. Pass `includeSuperseded: true` to include superseded rows (v0.3.0).
165
183
 
166
184
  ```
167
185
  kevin_recall({ query: "auth", limit: 3 })
@@ -170,15 +188,19 @@ kevin_recall({ query: "auth", limit: 3 })
170
188
 
171
189
  ### `kevin_status`
172
190
 
173
- Global counts and metrics (v0.2.0 adds `memories_reflector`, `memories_agent`, `memories_pattern`, and a `metrics` object with 6 seeded counters).
191
+ Global counts and metrics. v0.2.0 adds `memories_reflector`, `memories_agent`, `memories_pattern` and a `metrics` object; v0.3.0 adds `memories_causal` and 3 more seeded counters (`patterns_causal`, `causal_links`, `memories_superseded`); v0.4.0 adds the precision block: `injections_total`, `injections_effective`, `injections_ineffective`, `precision_rate`, `patterns_promoted_new`, and per-origin `recurrence_by_origin`.
174
192
 
175
193
  ```
176
194
  kevin_status({})
177
195
  // → { "memories": 42, "memories_reflector": 12, "memories_agent": 30, "memories_pattern": 0,
178
- // "tool_calls": 318, "retrospectives": 7,
196
+ // "memories_causal": 1, "tool_calls": 318, "retrospectives": 7,
179
197
  // "metrics": { "tokens_injected_pre_prompt": 51, "tokens_injected_compacting": 0,
180
198
  // "reflections_throttled": 3, "duplicate_suppressions": 2,
181
- // "tool_calls_deduped": 0, "patterns_mined": 0 } }
199
+ // "tool_calls_deduped": 0, "patterns_mined": 0,
200
+ // "patterns_causal": 1, "causal_links": 2, "memories_superseded": 0 },
201
+ // "injections_total": 14, "injections_effective": 11, "injections_ineffective": 3,
202
+ // "precision_rate": 0.79, "patterns_promoted_new": 2,
203
+ // "recurrence_by_origin": { "reflector": 3, "causal": 1 } }
182
204
  ```
183
205
 
184
206
  ### `kevin_retrospective`
@@ -191,6 +213,46 @@ kevin_retrospective({ session_id: "sess-abc" })
191
213
  // or → { "message": "No failures in session sess-abc." }
192
214
  ```
193
215
 
216
+ ### `kevin_why` (v0.3.0)
217
+
218
+ Explains *why* a failure keeps happening: looks up causal patterns for the query and builds a failure → fix trace from memories + tool_calls, including related TypeScript error-code rules.
219
+
220
+ ```
221
+ kevin_why({ query: "TS2304 cannot find name" })
222
+ // → { "summary": "TS2304 recurs because ... Confirmed by 2 fixes.",
223
+ // "confidence": 0.7, "evidence_count": 2, "last_verified": "2026-08-01 10:00:00",
224
+ // "trace": [ { "type": "error", "summary": "..." }, { "type": "fix", "tool": "bash" } ],
225
+ // "related_rules": [ { "code": "TS2304", "suggestion": "import or typo" } ] }
226
+ ```
227
+
228
+ ### `kevin_export` (v0.3.0)
229
+
230
+ Exports knowledge for sharing: `decision`/`rule`/`pattern` memories (active only, no raw errors) as YAML-frontmatter blocks (`format: "okf"`) or markdown (`format: "markdown"`). Includes `id`, `type`, `confidence` (two-sided v0.4.0 formula), `evidence_count`, `recurrence_count`, `last_verified_at`, `fingerprint`. Timestamps are treated as UTC — a re-import reproduces the exact source values.
231
+
232
+ ### `kevin_import` (v0.3.0)
233
+
234
+ Ingests an exported bundle. Each entry becomes a `context` memory with `origin='imported'`; a fingerprint collision with an existing `decision`/`rule` supersedes the old row. Returns `{ imported, superseded }`.
235
+
236
+ ### `kevin_config` (v0.4.0)
237
+
238
+ Reads/writes `kevin_settings` without SQL. `action: "list"` returns every setting; `action: "set"` upserts a value (default `"1"` when omitted) and rejects unknown keys unless `strict: false`.
239
+
240
+ ```
241
+ kevin_config({ action: "list" })
242
+ // → { "quality_gate_enabled": "1", "lesson_snippet_injection": "1", "llm_reflection_enabled": "0", ... }
243
+
244
+ kevin_config({ action: "set", key: "quality_gate_enabled", value: "0" })
245
+ // → { "ok": true }
246
+ ```
247
+
248
+ Known keys: `quality_gate_enabled`, `lesson_snippet_injection`, `llm_reflection_enabled`, `cross_project_enabled`, `patternminer_enabled`, `tool_calls_dedup_enabled` (v0.4.0).
249
+
250
+ ---
251
+
252
+ ## Precision (v0.4.0)
253
+
254
+ Weak lessons — errors the reflector cannot dispatch to a deterministic rule — are **stored but never injected** while `quality_gate_enabled = '1'` (default). Injection now goes through a ledger: every pre-prompt/compacting injection is recorded and settled as effective or ineffective at session idle, so `kevin_status` reports the honest picture (`injections_total`, `injections_effective/ineffective`, `precision_rate`, `patterns_promoted_new`) instead of raw "lessons shared" counts. Recurrences demote lessons (`recurrence_count` → `stale`) and lower confidence. Debug mode: `kevin_config({ action: "set", key: "quality_gate_enabled", value: "0" })` re-injects weak lessons with a `(low confidence)` marker.
255
+
194
256
  ---
195
257
 
196
258
  ## Hooks
@@ -200,11 +262,11 @@ Kevin subscribes to 6 OpenCode hooks:
200
262
  | Hook | What Kevin does |
201
263
  |---|---|
202
264
  | `tool.execute.before` | Records tool call start (callID + redacted args) |
203
- | `tool.execute.after` | Records result; on failure → Reflector.invoke async (throttled) |
204
- | `experimental.chat.system.transform` | Injects relevant lessons in `<kevin-context>` (1500 tokens) |
205
- | `experimental.session.compacting` | Re-injects lessons in `<kevin-memory>` after compacting (2000 tokens) |
265
+ | `tool.execute.after` | Records result (id = callID); on failure → Reflector.invoke async (throttled); on success → CausalChain.onSuccess links the fix (v0.3.0) |
266
+ | `experimental.chat.system.transform` | Injects relevant lessons in `<kevin-context>` (1500 tokens) + optional `<kevin-suggestion>` (v0.3.0) |
267
+ | `experimental.session.compacting` | Re-injects lessons in `<kevin-memory>` after compacting (2000 tokens) + optional `<kevin-suggestion>` |
206
268
  | `event` (`session.created`) | Captures current `sessionID` |
207
- | `event` (`session.idle`) | Generates retrospective.md for the session |
269
+ | `event` (`session.idle`) | Generates retrospective.md; boosts positive lessons (v0.2.0); penalizes recurring failures (v0.3.0); promotes causal patterns + mines patterns (opt-in); flushes metrics |
208
270
 
209
271
  **Redaction**: absolute paths (`C:\Users\...`, `/home/...`) → `<path>` and secrets (`API_KEY=`, `Bearer`, `token`) → `<redacted>` before persisting anything. v0.2.0 adds `<private>…</private>` block redaction: sweeps tool call args and output before persistence, replaces with `<private: redacted N chars>`.
210
272
 
@@ -260,14 +322,23 @@ plugin/
260
322
  index.ts # Entry point: KevinPlugin
261
323
  Store.ts # Wrapper SQLite (node:sqlite / bun:sqlite / better-sqlite3 fallback)
262
324
  Migrate.ts # Idempotent migrations + post-apply hooks
263
- MemoryService.ts # save/query/getRelevant (FTS5 + bm25 + origin-aware rank)
325
+ MemoryService.ts # save/query/getRelevant (FTS5 + bm25 + origin-aware rank + supersede)
264
326
  ToolCallObserver.ts # onBefore/onAfter + redact + inferErrorType + dedup (opt-in)
265
- Reflector.ts # Heuristic lessons + per-fingerprint throttle + lesson v2
266
- ContextInjector.ts # deriveQuery + pre-prompt/compacting injection + conditional budget
327
+ Reflector.ts # Heuristic lessons + per-fingerprint throttle + lesson v2 + LLM enrich
328
+ CausalChain.ts # v0.3.0 links fixes to failures + promotes causal patterns
329
+ ContextInjector.ts # deriveQuery + pre-prompt/compacting injection + <kevin-suggestion>
330
+ QualityGate.ts # v0.4.0 — weak-lesson gate (stored, not injected by default)
331
+ InjectionLedger.ts # v0.4.0 — injection ledger + settle → precision_rate
267
332
  Retrospective.ts # Generates retrospective.md + FP recap + metrics snapshot
333
+ LessonFixer.ts # v0.4.0 — deterministic fix_args capture + promotion enrichment
334
+ confidence.ts # v0.4.0 — two-sided computeConfidence (evidence + recurrence)
268
335
  fingerprint.ts # FNV-1a 64-bit (in-house, no node:crypto)
269
336
  metrics.ts # In-memory counters + debounced flush to kevin_metrics
270
337
  PatternMiner.ts # Opt-in deterministic 2-gram/3-gram miner
338
+ kevin_why.ts # v0.3.0 — kevin_why tool: failure→fix traces + related rules
339
+ okf-export.ts # v0.3.0 — kevin_export: OKF/markdown export
340
+ okf-import.ts # v0.3.0 — kevin_import: bundle parser + import
341
+ query-tokenizer.ts # v0.4.0 — FTS5 tokenizer for query sanitization
271
342
  memory-format.ts # escapeInjectedText, formatMemories, <protect> + id: line wrappers
272
343
  redact.ts # redactPaths + stripPrivate
273
344
  uuid.ts # UUIDv7
@@ -275,6 +346,8 @@ migrations/
275
346
  001_initial.sql # schema: memories, tool_calls, retrospectives
276
347
  002_indexes.sql # FTS5 + indexes
277
348
  003_v02_signal.sql # v0.2.0 Signal Quality: fingerprint, origin, metrics, dedup indexes
349
+ 004_v03_knowledge.sql # v0.3.0 Knowledge + Causality: evidence/status/supersede, error_fingerprint
350
+ 005_v04_signal.sql # v0.4.0 Signal over Noise: recurrence_count, fix_args, last_injected_at
278
351
  tests/{unit,integration,e2e}/
279
352
  scripts/
280
353
  copy-migrations.mjs # build step: copies *.sql to dist/migrations
@@ -0,0 +1,138 @@
1
+ -- ============================================================
2
+ -- Kevin 0.3.0 — Migration 004: Knowledge + Causality (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.
7
+ -- ============================================================
8
+
9
+ -- 1. memories: evidence + lifecycle columns.
10
+ -- evidence_count — how many times this fingerprint was confirmed as fixed.
11
+ -- last_verified_at — timestamp of the most recent causal confirmation.
12
+ -- status — lifecycle state; default 'active'. Superseded rows are hidden.
13
+ ALTER TABLE memories ADD COLUMN evidence_count INTEGER NOT NULL DEFAULT 0;
14
+ ALTER TABLE memories ADD COLUMN last_verified_at TEXT;
15
+ ALTER TABLE memories ADD COLUMN status TEXT NOT NULL DEFAULT 'active'
16
+ CHECK (status IN ('active', 'superseded', 'stale', 'archived'));
17
+
18
+ -- 2. tool_calls: causal link + feedback-loop link columns.
19
+ -- fix_for_fingerprint — set when this successful call resolved a prior failure
20
+ -- with the given fingerprint. NULL for tool calls that were not fixes.
21
+ -- error_fingerprint — set by Reflector (via onLinkError callback) when a call
22
+ -- FAILS, to the stderr-based fingerprint the matching error memory uses.
23
+ -- This fixes the v0.2.0/v0.3.0 feedback-loop fingerprint mismatch bug:
24
+ -- tool_calls.fingerprint is hashed from "tool|args|success" by ToolCallObserver,
25
+ -- while memories.fingerprint is hashed from stderr text by Reflector — they
26
+ -- never agreed, so boost/penalize queries silently mismatched. The new column
27
+ -- stores the SAME identity dimension the error memory uses.
28
+ ALTER TABLE tool_calls ADD COLUMN fix_for_fingerprint TEXT;
29
+ ALTER TABLE tool_calls ADD COLUMN error_fingerprint TEXT;
30
+
31
+ -- 3. Index: causal linkage by fingerprint. Used by CausalChain.onSuccess
32
+ -- and kevin_why to materialize traces.
33
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_fix_fp
34
+ ON tool_calls(fix_for_fingerprint)
35
+ WHERE fix_for_fingerprint IS NOT NULL;
36
+
37
+ -- 3b. Index: feedback-loop linkage by error_fingerprint. Used by
38
+ -- boostPositiveReflectors / penalizeRecurringReflectors to count
39
+ -- recurrences by the same identity dimension the error memory uses.
40
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_error_fp
41
+ ON tool_calls(error_fingerprint)
42
+ WHERE error_fingerprint IS NOT NULL;
43
+
44
+ -- 4. Index: memories by fingerprint for promotion + supersede queries.
45
+ CREATE INDEX IF NOT EXISTS idx_memories_fp
46
+ ON memories(fingerprint)
47
+ WHERE fingerprint IS NOT NULL;
48
+
49
+ -- 5. kevin_metrics: seed new v0.3 counters.
50
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
51
+ ('patterns_causal', 0),
52
+ ('causal_links', 0),
53
+ ('memories_superseded', 0);
54
+
55
+ -- 6. kevin_settings: seed new opt-in flags.
56
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
57
+ ('llm_reflection_enabled', '0'),
58
+ ('cross_project_enabled', '0');
59
+
60
+ -- 7. Seed version 004.
61
+ INSERT OR IGNORE INTO schema_version (version) VALUES ('004');
62
+
63
+ -- ============================================================
64
+ -- 8. Rebuild memories table with expanded CHECK constraints.
65
+ -- v0.3.0 introduces new types (rule, solution) and origins
66
+ -- (causal, imported). SQLite cannot ALTER a CHECK constraint,
67
+ -- so we rebuild via a temporary table. FTS5 external-content
68
+ -- table references the content table by name, so we drop+recreate
69
+ -- the FTS5 triggers after the rebuild.
70
+ -- ============================================================
71
+
72
+ -- Step 1: Create new table with correct constraints
73
+ CREATE TABLE memories_v04 (
74
+ id TEXT PRIMARY KEY,
75
+ type TEXT NOT NULL CHECK(type IN ('error','pattern','decision','context','rule','solution')),
76
+ content TEXT NOT NULL,
77
+ scope TEXT NOT NULL DEFAULT 'project' CHECK(scope IN ('project','session')),
78
+ relevance_score REAL DEFAULT 0.5,
79
+ source_tool TEXT,
80
+ source_session TEXT,
81
+ metadata TEXT,
82
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
83
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
84
+ expires_at TEXT,
85
+ project_id TEXT,
86
+ fingerprint TEXT,
87
+ origin TEXT NOT NULL DEFAULT 'agent'
88
+ CHECK(origin IN ('reflector','agent','pattern','retrospective','causal','imported')),
89
+ evidence_count INTEGER NOT NULL DEFAULT 0,
90
+ last_verified_at TEXT,
91
+ status TEXT NOT NULL DEFAULT 'active'
92
+ CHECK(status IN ('active','superseded','stale','archived'))
93
+ );
94
+
95
+ -- Step 2: Copy data preserving rowids (FTS5 content sync relies on rowid)
96
+ INSERT INTO memories_v04
97
+ (rowid, id, type, content, scope, relevance_score, source_tool,
98
+ source_session, metadata, created_at, updated_at, expires_at,
99
+ project_id, fingerprint, origin, evidence_count, last_verified_at, status)
100
+ SELECT rowid, id, type, content, scope, relevance_score, source_tool,
101
+ source_session, metadata, created_at, updated_at, expires_at,
102
+ project_id, fingerprint, origin, evidence_count, last_verified_at, status
103
+ FROM memories;
104
+
105
+ -- Step 3: Drop old table and FTS triggers
106
+ DROP TRIGGER IF EXISTS memories_ai;
107
+ DROP TRIGGER IF EXISTS memories_ad;
108
+ DROP TRIGGER IF EXISTS memories_au;
109
+ DROP TABLE memories;
110
+
111
+ -- Step 4: Rename new table
112
+ ALTER TABLE memories_v04 RENAME TO memories;
113
+
114
+ -- Step 5: Recreate FTS triggers (memories_fts is content=external, survives DROP of content table)
115
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
116
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
117
+ END;
118
+
119
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
120
+ INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
121
+ END;
122
+
123
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
124
+ INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
125
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
126
+ END;
127
+
128
+ -- Step 6: Recreate indexes
129
+ CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
130
+ CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
131
+ CREATE INDEX IF NOT EXISTS idx_memories_relevance ON memories(relevance_score DESC);
132
+ CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
133
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_memories_error_fp
134
+ ON memories(project_id, fingerprint)
135
+ WHERE type = 'error' AND fingerprint IS NOT NULL AND origin = 'reflector';
136
+ CREATE INDEX IF NOT EXISTS idx_memories_fp
137
+ ON memories(fingerprint)
138
+ WHERE fingerprint IS NOT NULL;
@@ -0,0 +1,57 @@
1
+ -- ============================================================
2
+ -- Kevin 0.4.0 — Migration 005: Signal over Noise (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.
7
+ -- ============================================================
8
+
9
+ -- 1. memories: positive/negative evidence split (D4-03).
10
+ -- recurrence_count — how many times this fingerprint recurred AFTER
11
+ -- injection (negative evidence; lowers confidence).
12
+ -- fix_args — deterministic capture of the linked success call's
13
+ -- args_summary ("Fixed by:" raw material, D4-07).
14
+ -- last_injected_at — timestamp of the most recent injection of this memory.
15
+ ALTER TABLE memories ADD COLUMN recurrence_count INTEGER NOT NULL DEFAULT 0;
16
+ ALTER TABLE memories ADD COLUMN fix_args TEXT;
17
+ ALTER TABLE memories ADD COLUMN last_injected_at TEXT;
18
+
19
+ -- 2. kevin_injections: the injection ledger (D4-04). One row per injected
20
+ -- memory per prompt/compaction, settled at session.idle.
21
+ CREATE TABLE IF NOT EXISTS kevin_injections (
22
+ id TEXT PRIMARY KEY,
23
+ memory_id TEXT NOT NULL,
24
+ fingerprint TEXT NOT NULL,
25
+ session_id TEXT NOT NULL,
26
+ hook TEXT NOT NULL CHECK (hook IN ('pre_prompt', 'compacting')),
27
+ tokens INTEGER NOT NULL,
28
+ injected_at TEXT NOT NULL DEFAULT (datetime('now')),
29
+ outcome TEXT CHECK (outcome IN ('unmeasured', 'effective', 'ineffective'))
30
+ NOT NULL DEFAULT 'unmeasured'
31
+ );
32
+
33
+ -- 2b. Indexes: settlement by session, recurrence lookups by fingerprint,
34
+ -- and outcome rollups for precision_rate.
35
+ CREATE INDEX IF NOT EXISTS idx_injections_fp
36
+ ON kevin_injections(fingerprint);
37
+ CREATE INDEX IF NOT EXISTS idx_injections_session
38
+ ON kevin_injections(session_id);
39
+ CREATE INDEX IF NOT EXISTS idx_injections_outcome
40
+ ON kevin_injections(outcome);
41
+
42
+ -- 3. kevin_metrics: seed new v0.4 counters.
43
+ -- patterns_promoted_new replaces patterns_causal (which was inflated by
44
+ -- idempotent refreshes); the latter stays for compat but is frozen.
45
+ INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
46
+ ('injections_total', 0),
47
+ ('injections_effective', 0),
48
+ ('injections_ineffective', 0),
49
+ ('patterns_promoted_new', 0);
50
+
51
+ -- 4. kevin_settings: seed new v0.4 flags.
52
+ INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
53
+ ('quality_gate_enabled', '1'),
54
+ ('lesson_snippet_injection','1');
55
+
56
+ -- 5. Seed version 005.
57
+ INSERT OR IGNORE INTO schema_version (version) VALUES ('005');
@@ -0,0 +1,22 @@
1
+ import { type EnrichFn } from "./LessonFixer.js";
2
+ import type { MemoryService } from "./MemoryService.js";
3
+ import type { Store } from "./Store.js";
4
+ import type { Metrics } from "./metrics.js";
5
+ export declare class CausalChain {
6
+ private store;
7
+ private memoryService;
8
+ private metrics;
9
+ private enrichFn?;
10
+ constructor(store: Store, memoryService: MemoryService, metrics: Metrics | null, enrichFn?: EnrichFn | undefined);
11
+ onSuccess(_tool: string, _args: Record<string, unknown>, _projectId: string | null, sessionId: string): void;
12
+ onSessionIdle(sessionId: string): Promise<number>;
13
+ /**
14
+ * v0.4.0 (K4-015) — fire the opt-in enrich hook at most once per
15
+ * promoted pattern. The hook's one-line phrase replaces the
16
+ * deterministic `Fixed by:` line; null keeps it. A call (phrase or
17
+ * not) stamps `metadata.enriched` so repeated idle cycles stay at
18
+ * one LLM call per pattern.
19
+ */
20
+ private enrichIfEnabled;
21
+ private isLlmReflectionEnabled;
22
+ }
@@ -0,0 +1,179 @@
1
+ import { extractFixArgs, } from "./LessonFixer.js";
2
+ /** K3-007 — a success only links to a failure within this many calls. */
3
+ const MAX_LINK_DISTANCE = 10;
4
+ export class CausalChain {
5
+ store;
6
+ memoryService;
7
+ metrics;
8
+ enrichFn;
9
+ constructor(store, memoryService, metrics,
10
+ // v0.4.0 (K4-015) — opt-in promotion-time LLM phrasing. Absent in
11
+ // production (zero network calls by default); injected by tests or
12
+ // by a future settings-driven wiring.
13
+ enrichFn) {
14
+ this.store = store;
15
+ this.memoryService = memoryService;
16
+ this.metrics = metrics;
17
+ this.enrichFn = enrichFn;
18
+ }
19
+ // K3-007: link a success to the failing fingerprint only when it
20
+ // occurred within MAX_LINK_DISTANCE tool calls of the failure (plan
21
+ // §K3-007 acceptance: "within 24h and within 10 tool calls").
22
+ // v0.3.0 fix (bug #3) — the old code linked the most recent session
23
+ // success to the most recent unlinked error memory regardless of
24
+ // distance, so an unrelated success (e.g. an `ls` run after a
25
+ // typecheck failure) was stamped as the fix for that error.
26
+ onSuccess(_tool, _args, _projectId, sessionId) {
27
+ const successRow = this.store
28
+ .prepare(`SELECT rowid, tool, args_summary FROM tool_calls
29
+ WHERE session_id = ? AND success = 1
30
+ ORDER BY rowid DESC LIMIT 1`)
31
+ .get(sessionId);
32
+ if (!successRow)
33
+ return;
34
+ const linkedFps = new Set(this.store
35
+ .prepare(`SELECT DISTINCT fix_for_fingerprint FROM tool_calls
36
+ WHERE session_id = ? AND fix_for_fingerprint IS NOT NULL`)
37
+ .all(sessionId).map((r) => r.fix_for_fingerprint));
38
+ // Most recent failing calls in this session, newest first. The
39
+ // failing call's `error_fingerprint` (stamped by Reflector via
40
+ // onLinkError) is the SAME identity dimension the error memory
41
+ // uses; `fingerprint` is the legacy tool|args|success hash and
42
+ // simply never matches a reflector error memory.
43
+ const failRows = this.store
44
+ .prepare(`SELECT rowid, COALESCE(error_fingerprint, fingerprint) AS fp
45
+ FROM tool_calls
46
+ WHERE session_id = ?
47
+ AND success = 0
48
+ AND (error_fingerprint IS NOT NULL OR fingerprint IS NOT NULL)
49
+ ORDER BY rowid DESC LIMIT ?`)
50
+ .all(sessionId, MAX_LINK_DISTANCE);
51
+ for (const fail of failRows) {
52
+ if (!fail.fp || linkedFps.has(fail.fp))
53
+ continue;
54
+ const dist = successRow.rowid - fail.rowid;
55
+ if (dist <= 0 || dist > MAX_LINK_DISTANCE)
56
+ continue;
57
+ const mem = this.store
58
+ .prepare(`SELECT 1 FROM memories
59
+ WHERE fingerprint = ? AND type = 'error'
60
+ AND origin = 'reflector' AND status IN ('active', 'stale')
61
+ AND created_at > datetime('now', '-24 hours')
62
+ LIMIT 1`)
63
+ .get(fail.fp);
64
+ if (!mem)
65
+ continue;
66
+ this.store
67
+ .prepare("UPDATE tool_calls SET fix_for_fingerprint = ? WHERE rowid = ?")
68
+ .run(fail.fp, successRow.rowid);
69
+ // v0.4.0 (K4-014) — deterministic "Fixed by:" raw material
70
+ // (plan §5.4 / D4-07): copy the linked success call's
71
+ // args_summary into memories.fix_args for every active row of
72
+ // that fingerprint (error + pattern), zero LLM cost.
73
+ const fixArgs = extractFixArgs({
74
+ tool: successRow.tool,
75
+ args_summary: successRow.args_summary ?? null,
76
+ });
77
+ if (fixArgs) {
78
+ this.store
79
+ .prepare(`UPDATE memories SET fix_args = ?
80
+ WHERE fingerprint = ? AND status IN ('active', 'stale')`)
81
+ .run(fixArgs, fail.fp);
82
+ }
83
+ this.metrics?.incr("causal_links", 1);
84
+ return;
85
+ }
86
+ }
87
+ async onSessionIdle(sessionId) {
88
+ const linkedErrors = this.store
89
+ .prepare(`SELECT m.id, m.fingerprint, m.recurrence_count,
90
+ (SELECT COUNT(*)
91
+ FROM tool_calls tc_all
92
+ WHERE tc_all.fix_for_fingerprint = m.fingerprint) as evidence_count
93
+ FROM memories m
94
+ WHERE m.type = 'error'
95
+ AND m.origin = 'reflector'
96
+ AND m.fingerprint IN (
97
+ SELECT DISTINCT fix_for_fingerprint
98
+ FROM tool_calls
99
+ WHERE session_id = ? AND fix_for_fingerprint IS NOT NULL
100
+ )
101
+ AND m.status IN ('active', 'stale')
102
+ GROUP BY m.fingerprint
103
+ HAVING (
104
+ SELECT MAX(tc.ts) FROM tool_calls tc
105
+ WHERE tc.fix_for_fingerprint = m.fingerprint
106
+ ) >= COALESCE(
107
+ (SELECT MAX(m2.updated_at) FROM memories m2
108
+ WHERE m2.fingerprint = m.fingerprint
109
+ AND m2.type = 'pattern'
110
+ AND m2.origin = 'causal'),
111
+ '1970-01-01'
112
+ )`)
113
+ .all(sessionId);
114
+ let promoted = 0;
115
+ for (const err of linkedErrors) {
116
+ try {
117
+ const result = this.memoryService.promoteToPattern(err.id, err.evidence_count, err.recurrence_count ?? 0);
118
+ if (result) {
119
+ promoted++;
120
+ // v0.4.0 (K4-009) — only a NEW pattern row counts as a
121
+ // promotion; the idempotent refresh path no longer
122
+ // inflates the metric. `patterns_causal` is deprecated
123
+ // (key kept for compat, never incremented).
124
+ if (result.created) {
125
+ this.metrics?.incr("patterns_promoted_new", 1);
126
+ // v0.4.0 (K4-015) — promotion-time LLM enrichment:
127
+ // at most one call per NEW pattern, gated on
128
+ // `kevin_settings.llm_reflection_enabled` and the
129
+ // per-pattern `metadata.enriched` marker. Never on
130
+ // the failure hot path.
131
+ await this.enrichIfEnabled(err.id, result.id);
132
+ }
133
+ }
134
+ }
135
+ catch {
136
+ // promoteToPattern may fail if the error memory was removed
137
+ }
138
+ }
139
+ return promoted;
140
+ }
141
+ /**
142
+ * v0.4.0 (K4-015) — fire the opt-in enrich hook at most once per
143
+ * promoted pattern. The hook's one-line phrase replaces the
144
+ * deterministic `Fixed by:` line; null keeps it. A call (phrase or
145
+ * not) stamps `metadata.enriched` so repeated idle cycles stay at
146
+ * one LLM call per pattern.
147
+ */
148
+ async enrichIfEnabled(errorId, patternId) {
149
+ if (!this.enrichFn || !this.isLlmReflectionEnabled())
150
+ return;
151
+ const pattern = this.memoryService.getById(patternId);
152
+ if (!pattern)
153
+ return;
154
+ const meta = (pattern.metadata ?? {});
155
+ if (meta.enriched === true)
156
+ return;
157
+ const phrase = await this.enrichFn({
158
+ lesson: pattern.content,
159
+ fixArgs: pattern.fixArgs ?? null,
160
+ originalError: this.memoryService.getById(errorId)?.content ?? null,
161
+ });
162
+ const content = phrase
163
+ ? pattern.content.includes("\nFixed by: ")
164
+ ? pattern.content.replace(/\nFixed by: .+$/s, `\n${phrase}`)
165
+ : `${pattern.content}\n${phrase}`
166
+ : pattern.content;
167
+ this.memoryService.update(patternId, {
168
+ content,
169
+ metadata: { ...meta, enriched: true },
170
+ });
171
+ }
172
+ isLlmReflectionEnabled() {
173
+ const row = this.store
174
+ .prepare("SELECT value FROM kevin_settings WHERE key = ?")
175
+ .get("llm_reflection_enabled");
176
+ return row?.value === "1";
177
+ }
178
+ }
179
+ //# sourceMappingURL=CausalChain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CausalChain.js","sourceRoot":"","sources":["../../plugin/CausalChain.ts"],"names":[],"mappings":"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"}