@jmtrin/opencode-kevin 0.3.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 (55) hide show
  1. package/README.md +38 -6
  2. package/dist/migrations/005_v04_signal.sql +57 -0
  3. package/dist/plugin/CausalChain.d.ts +13 -2
  4. package/dist/plugin/CausalChain.js +83 -13
  5. package/dist/plugin/CausalChain.js.map +1 -1
  6. package/dist/plugin/ContextInjector.d.ts +80 -4
  7. package/dist/plugin/ContextInjector.js +262 -88
  8. package/dist/plugin/ContextInjector.js.map +1 -1
  9. package/dist/plugin/InjectionLedger.d.ts +85 -0
  10. package/dist/plugin/InjectionLedger.js +189 -0
  11. package/dist/plugin/InjectionLedger.js.map +1 -0
  12. package/dist/plugin/LessonFixer.d.ts +44 -0
  13. package/dist/plugin/LessonFixer.js +46 -0
  14. package/dist/plugin/LessonFixer.js.map +1 -0
  15. package/dist/plugin/MemoryService.d.ts +62 -1
  16. package/dist/plugin/MemoryService.js +206 -43
  17. package/dist/plugin/MemoryService.js.map +1 -1
  18. package/dist/plugin/Migrate.js +10 -0
  19. package/dist/plugin/Migrate.js.map +1 -1
  20. package/dist/plugin/QualityGate.d.ts +71 -0
  21. package/dist/plugin/QualityGate.js +78 -0
  22. package/dist/plugin/QualityGate.js.map +1 -0
  23. package/dist/plugin/Reflector.d.ts +17 -0
  24. package/dist/plugin/Reflector.js +81 -26
  25. package/dist/plugin/Reflector.js.map +1 -1
  26. package/dist/plugin/Retrospective.js +10 -1
  27. package/dist/plugin/Retrospective.js.map +1 -1
  28. package/dist/plugin/ToolCallObserver.d.ts +1 -0
  29. package/dist/plugin/ToolCallObserver.js +15 -8
  30. package/dist/plugin/ToolCallObserver.js.map +1 -1
  31. package/dist/plugin/confidence.d.ts +6 -0
  32. package/dist/plugin/confidence.js +23 -0
  33. package/dist/plugin/confidence.js.map +1 -0
  34. package/dist/plugin/index.d.ts +5 -0
  35. package/dist/plugin/index.js +216 -44
  36. package/dist/plugin/index.js.map +1 -1
  37. package/dist/plugin/kevin_why.d.ts +4 -0
  38. package/dist/plugin/kevin_why.js +51 -35
  39. package/dist/plugin/kevin_why.js.map +1 -1
  40. package/dist/plugin/memory-format.d.ts +12 -0
  41. package/dist/plugin/memory-format.js +45 -5
  42. package/dist/plugin/memory-format.js.map +1 -1
  43. package/dist/plugin/metrics.d.ts +7 -1
  44. package/dist/plugin/metrics.js +16 -0
  45. package/dist/plugin/metrics.js.map +1 -1
  46. package/dist/plugin/okf-export.js +63 -22
  47. package/dist/plugin/okf-export.js.map +1 -1
  48. package/dist/plugin/okf-import.d.ts +4 -1
  49. package/dist/plugin/okf-import.js +45 -12
  50. package/dist/plugin/okf-import.js.map +1 -1
  51. package/dist/plugin/query-tokenizer.d.ts +13 -0
  52. package/dist/plugin/query-tokenizer.js +86 -0
  53. package/dist/plugin/query-tokenizer.js.map +1 -0
  54. package/migrations/005_v04_signal.sql +57 -0
  55. package/package.json +1 -1
package/README.md CHANGED
@@ -7,6 +7,8 @@ Kevin is an [OpenCode](https://opencode.ai) plugin that **observes** every agent
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
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).
10
12
  - **Standalone**: works without any other plugin. With the ecosystem, it learns more richly.
11
13
 
12
14
  ---
@@ -31,7 +33,7 @@ For a **single project**, put the same `plugin` array in `./opencode.json` or `.
31
33
 
32
34
  ### 2. Restart OpenCode
33
35
 
34
- 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 nine tools: `kevin_save`, `kevin_query`, `kevin_get`, `kevin_recall`, `kevin_status`, `kevin_retrospective`, `kevin_why`, `kevin_export`, `kevin_import`.
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`.
35
37
 
36
38
  ### 3. Where data lives
37
39
 
@@ -135,7 +137,7 @@ Use `:memory:` for `dbPath` in tests.
135
137
 
136
138
  ## Tools
137
139
 
138
- Kevin exposes 9 tools callable by the agent:
140
+ Kevin exposes 10 tools callable by the agent:
139
141
 
140
142
  ### `kevin_save`
141
143
 
@@ -171,7 +173,8 @@ kevin_get({ id: "0195a3b2-..." })
171
173
  // → { "id": "...", "type": "error", "content": "...", "scope": "project",
172
174
  // "relevanceScore": 0.55, "origin": "reflector", "fingerprint": "cbf29ce484222325",
173
175
  // "projectId": null, "metadata": null,
174
- // "evidenceCount": 0, "lastVerifiedAt": null, "status": "active" }
176
+ // "evidenceCount": 2, "recurrenceCount": 1, "lastVerifiedAt": "2026-08-01 10:00:00",
177
+ // "status": "active", "confidence": 0.55, "fixArgs": "npm i -g rg" }
175
178
  ```
176
179
 
177
180
  ### `kevin_recall`
@@ -185,7 +188,7 @@ kevin_recall({ query: "auth", limit: 3 })
185
188
 
186
189
  ### `kevin_status`
187
190
 
188
- 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`).
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`.
189
192
 
190
193
  ```
191
194
  kevin_status({})
@@ -194,7 +197,10 @@ kevin_status({})
194
197
  // "metrics": { "tokens_injected_pre_prompt": 51, "tokens_injected_compacting": 0,
195
198
  // "reflections_throttled": 3, "duplicate_suppressions": 2,
196
199
  // "tool_calls_deduped": 0, "patterns_mined": 0,
197
- // "patterns_causal": 1, "causal_links": 2, "memories_superseded": 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 } }
198
204
  ```
199
205
 
200
206
  ### `kevin_retrospective`
@@ -221,12 +227,32 @@ kevin_why({ query: "TS2304 cannot find name" })
221
227
 
222
228
  ### `kevin_export` (v0.3.0)
223
229
 
224
- 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`, `evidence_count`, `last_verified_at`, `fingerprint`.
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.
225
231
 
226
232
  ### `kevin_import` (v0.3.0)
227
233
 
228
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 }`.
229
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
+
230
256
  ---
231
257
 
232
258
  ## Hooks
@@ -301,13 +327,18 @@ plugin/
301
327
  Reflector.ts # Heuristic lessons + per-fingerprint throttle + lesson v2 + LLM enrich
302
328
  CausalChain.ts # v0.3.0 — links fixes to failures + promotes causal patterns
303
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
304
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)
305
335
  fingerprint.ts # FNV-1a 64-bit (in-house, no node:crypto)
306
336
  metrics.ts # In-memory counters + debounced flush to kevin_metrics
307
337
  PatternMiner.ts # Opt-in deterministic 2-gram/3-gram miner
308
338
  kevin_why.ts # v0.3.0 — kevin_why tool: failure→fix traces + related rules
309
339
  okf-export.ts # v0.3.0 — kevin_export: OKF/markdown export
310
340
  okf-import.ts # v0.3.0 — kevin_import: bundle parser + import
341
+ query-tokenizer.ts # v0.4.0 — FTS5 tokenizer for query sanitization
311
342
  memory-format.ts # escapeInjectedText, formatMemories, <protect> + id: line wrappers
312
343
  redact.ts # redactPaths + stripPrivate
313
344
  uuid.ts # UUIDv7
@@ -316,6 +347,7 @@ migrations/
316
347
  002_indexes.sql # FTS5 + indexes
317
348
  003_v02_signal.sql # v0.2.0 Signal Quality: fingerprint, origin, metrics, dedup indexes
318
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
319
351
  tests/{unit,integration,e2e}/
320
352
  scripts/
321
353
  copy-migrations.mjs # build step: copies *.sql to dist/migrations
@@ -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');
@@ -1,3 +1,4 @@
1
+ import { type EnrichFn } from "./LessonFixer.js";
1
2
  import type { MemoryService } from "./MemoryService.js";
2
3
  import type { Store } from "./Store.js";
3
4
  import type { Metrics } from "./metrics.js";
@@ -5,7 +6,17 @@ export declare class CausalChain {
5
6
  private store;
6
7
  private memoryService;
7
8
  private metrics;
8
- constructor(store: Store, memoryService: MemoryService, metrics: Metrics | null);
9
+ private enrichFn?;
10
+ constructor(store: Store, memoryService: MemoryService, metrics: Metrics | null, enrichFn?: EnrichFn | undefined);
9
11
  onSuccess(_tool: string, _args: Record<string, unknown>, _projectId: string | null, sessionId: string): void;
10
- onSessionIdle(sessionId: string): number;
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;
11
22
  }
@@ -1,13 +1,20 @@
1
+ import { extractFixArgs, } from "./LessonFixer.js";
1
2
  /** K3-007 — a success only links to a failure within this many calls. */
2
3
  const MAX_LINK_DISTANCE = 10;
3
4
  export class CausalChain {
4
5
  store;
5
6
  memoryService;
6
7
  metrics;
7
- constructor(store, memoryService, 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) {
8
14
  this.store = store;
9
15
  this.memoryService = memoryService;
10
16
  this.metrics = metrics;
17
+ this.enrichFn = enrichFn;
11
18
  }
12
19
  // K3-007: link a success to the failing fingerprint only when it
13
20
  // occurred within MAX_LINK_DISTANCE tool calls of the failure (plan
@@ -18,7 +25,7 @@ export class CausalChain {
18
25
  // typecheck failure) was stamped as the fix for that error.
19
26
  onSuccess(_tool, _args, _projectId, sessionId) {
20
27
  const successRow = this.store
21
- .prepare(`SELECT rowid FROM tool_calls
28
+ .prepare(`SELECT rowid, tool, args_summary FROM tool_calls
22
29
  WHERE session_id = ? AND success = 1
23
30
  ORDER BY rowid DESC LIMIT 1`)
24
31
  .get(sessionId);
@@ -50,7 +57,7 @@ export class CausalChain {
50
57
  const mem = this.store
51
58
  .prepare(`SELECT 1 FROM memories
52
59
  WHERE fingerprint = ? AND type = 'error'
53
- AND origin = 'reflector' AND status = 'active'
60
+ AND origin = 'reflector' AND status IN ('active', 'stale')
54
61
  AND created_at > datetime('now', '-24 hours')
55
62
  LIMIT 1`)
56
63
  .get(fail.fp);
@@ -59,13 +66,27 @@ export class CausalChain {
59
66
  this.store
60
67
  .prepare("UPDATE tool_calls SET fix_for_fingerprint = ? WHERE rowid = ?")
61
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
+ }
62
83
  this.metrics?.incr("causal_links", 1);
63
84
  return;
64
85
  }
65
86
  }
66
- onSessionIdle(sessionId) {
87
+ async onSessionIdle(sessionId) {
67
88
  const linkedErrors = this.store
68
- .prepare(`SELECT m.id, m.fingerprint,
89
+ .prepare(`SELECT m.id, m.fingerprint, m.recurrence_count,
69
90
  (SELECT COUNT(*)
70
91
  FROM tool_calls tc_all
71
92
  WHERE tc_all.fix_for_fingerprint = m.fingerprint) as evidence_count
@@ -77,26 +98,38 @@ export class CausalChain {
77
98
  FROM tool_calls
78
99
  WHERE session_id = ? AND fix_for_fingerprint IS NOT NULL
79
100
  )
80
- AND m.status = 'active'
101
+ AND m.status IN ('active', 'stale')
81
102
  GROUP BY m.fingerprint
82
103
  HAVING (
83
- SELECT MAX(tc.rowid) FROM tool_calls tc
104
+ SELECT MAX(tc.ts) FROM tool_calls tc
84
105
  WHERE tc.fix_for_fingerprint = m.fingerprint
85
- ) > COALESCE(
86
- (SELECT MAX(m2.rowid) FROM memories m2
106
+ ) >= COALESCE(
107
+ (SELECT MAX(m2.updated_at) FROM memories m2
87
108
  WHERE m2.fingerprint = m.fingerprint
88
109
  AND m2.type = 'pattern'
89
110
  AND m2.origin = 'causal'),
90
- 0
111
+ '1970-01-01'
91
112
  )`)
92
113
  .all(sessionId);
93
114
  let promoted = 0;
94
115
  for (const err of linkedErrors) {
95
116
  try {
96
- const id = this.memoryService.promoteToPattern(err.id, err.evidence_count);
97
- if (id) {
117
+ const result = this.memoryService.promoteToPattern(err.id, err.evidence_count, err.recurrence_count ?? 0);
118
+ if (result) {
98
119
  promoted++;
99
- this.metrics?.incr("patterns_causal", 1);
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
+ }
100
133
  }
101
134
  }
102
135
  catch {
@@ -105,5 +138,42 @@ export class CausalChain {
105
138
  }
106
139
  return promoted;
107
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
+ }
108
178
  }
109
179
  //# sourceMappingURL=CausalChain.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CausalChain.js","sourceRoot":"","sources":["../../plugin/CausalChain.ts"],"names":[],"mappings":"AAIA,yEAAyE;AACzE,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAM,OAAO,WAAW;IAEd;IACA;IACA;IAHT,YACS,KAAY,EACZ,aAA4B,EAC5B,OAAuB;QAFvB,UAAK,GAAL,KAAK,CAAO;QACZ,kBAAa,GAAb,aAAa,CAAe;QAC5B,YAAO,GAAP,OAAO,CAAgB;IAC7B,CAAC;IAEJ,iEAAiE;IACjE,oEAAoE;IACpE,8DAA8D;IAC9D,oEAAoE;IACpE,iEAAiE;IACjE,8DAA8D;IAC9D,4DAA4D;IAC5D,SAAS,CACR,KAAa,EACb,KAA8B,EAC9B,UAAyB,EACzB,SAAiB;QAEjB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK;aAC3B,OAAO,CACP;;iCAE6B,CAC7B;aACA,GAAG,CAAC,SAAS,CAAkC,CAAC;QAClD,IAAI,CAAC,UAAU;YAAE,OAAO;QAExB,MAAM,SAAS,GAAG,IAAI,GAAG,CAEvB,IAAI,CAAC,KAAK;aACR,OAAO,CACP;gEAC0D,CAC1D;aACA,GAAG,CAAC,SAAS,CACf,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC,CACnC,CAAC;QAEF,+DAA+D;QAC/D,+DAA+D;QAC/D,+DAA+D;QAC/D,+DAA+D;QAC/D,iDAAiD;QACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK;aACzB,OAAO,CACP;;;;;iCAK6B,CAC7B;aACA,GAAG,CAAC,SAAS,EAAE,iBAAiB,CAG/B,CAAC;QAEJ,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,SAAS;YACjD,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YAC3C,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,iBAAiB;gBAAE,SAAS;YAEpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK;iBACpB,OAAO,CACP;;;;cAIS,CACT;iBACA,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,IAAI,CAAC,GAAG;gBAAE,SAAS;YAEnB,IAAI,CAAC,KAAK;iBACR,OAAO,CACP,+DAA+D,CAC/D;iBACA,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;YACtC,OAAO;QACR,CAAC;IACF,CAAC;IAED,aAAa,CAAC,SAAiB;QAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK;aAC7B,OAAO,CACP;;;;;;;;;;;;;;;;;;;;;;;OAuBG,CACH;aACA,GAAG,CAAC,SAAS,CAIZ,CAAC;QAEJ,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAChC,IAAI,CAAC;gBACJ,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAC7C,GAAG,CAAC,EAAE,EACN,GAAG,CAAC,cAAc,CAClB,CAAC;gBACF,IAAI,EAAE,EAAE,CAAC;oBACR,QAAQ,EAAE,CAAC;oBACX,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC;gBAC1C,CAAC;YACF,CAAC;YAAC,MAAM,CAAC;gBACR,4DAA4D;YAC7D,CAAC;QACF,CAAC;QAED,OAAO,QAAQ,CAAC;IACjB,CAAC;CACD"}
1
+ {"version":3,"file":"CausalChain.js","sourceRoot":"","sources":["../../plugin/CausalChain.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,8 @@
1
+ import type { InjectionLedger } from "./InjectionLedger.js";
1
2
  import type { MemoryService } from "./MemoryService.js";
2
3
  import { type Metrics } from "./metrics.js";
4
+ export declare const QUALITY_GATE_SETTING = "quality_gate_enabled";
5
+ export declare const SNIPPET_INJECTION_SETTING = "lesson_snippet_injection";
3
6
  export interface ChatMessage {
4
7
  role: string;
5
8
  content: string;
@@ -21,19 +24,45 @@ export interface CompactingOutput {
21
24
  export declare class ContextInjector {
22
25
  private memoryService;
23
26
  private metrics;
27
+ private ledger;
24
28
  private lastRecurrenceCount;
25
- constructor(memoryService: MemoryService, metrics?: Metrics | null);
29
+ /** v0.4.0 (K4-016) session that produced the last recurrence set. */
30
+ private lastRecurredSession;
31
+ /** v0.4.0 (K4-017) — per-session seen-set (plan §5.1 rule 3). */
32
+ private readonly seenBySession;
33
+ constructor(memoryService: MemoryService, metrics?: Metrics | null, ledger?: InjectionLedger | null);
34
+ /**
35
+ * v0.4.0 (K4-017) — reset the per-session seen-set when a session is
36
+ * created (plan §5.1 rule 3). Wired from the `session.created` event.
37
+ */
38
+ onSessionCreated(sessionId: string): void;
26
39
  /**
27
40
  * v0.3.0 (K3-020) — notify the injector that the negative feedback half
28
41
  * fired N times in the last session.idle. The next system.transform or
29
42
  * compacting hook will prepend a HITL suggestion block.
43
+ * v0.4.0 (K4-016) — the session id enables the concrete suggestion:
44
+ * the most-recurred fingerprint's pattern + its fix_args.
30
45
  */
31
- setRecurrences(count: number): void;
46
+ setRecurrences(count: number, sessionId?: string): void;
32
47
  /**
33
- * v0.3.0 (K3-020) — generate a HITL suggestion block when recurrences
34
- * occurred. Returns the block or empty string. Resets the counter.
48
+ * v0.4.0 (K4-016) — generate a CONCRETE HITL suggestion block when
49
+ * recurrences occurred (plan §5.5): names the most-recurred pattern,
50
+ * its exact recurrence count, observed fix_args and confidence. The
51
+ * AGENTS.md draft line derives from the lesson's `Suggestion:` text —
52
+ * never a canned string, and never the anonymous "the same error
53
+ * pattern" without naming the pattern and count.
54
+ *
55
+ * BUG-012 — emits AT MOST ONCE per session: calling it resets the
56
+ * pending recurrence signal, so whichever hook (system.transform or
57
+ * compacting) runs first in a session consumes the block. Documented
58
+ * behavior; wire index.ts to match.
35
59
  */
36
60
  generateSuggestion(): string;
61
+ /**
62
+ * v0.4.0 (K4-016) — the AGENTS.md draft line derives from the lesson's
63
+ * `Suggestion:` text (kept verbatim in the pattern's `Original:` block).
64
+ */
65
+ private agentsDraftLine;
37
66
  /**
38
67
  * v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
39
68
  * to `MemoryService.getRelevant()` (K2-023, D2-13). The injector does
@@ -42,8 +71,55 @@ export declare class ContextInjector {
42
71
  * callers. Plan §B6.5: "apply the same multiplier as
43
72
  * MemoryService.recall so reflector lessons outrank agent-saved notes
44
73
  * at injection time" — satisfied transitively via the getRelevant call.
74
+ *
75
+ * v0.4.0 (K4-017): after retrieval, every memory is filtered through
76
+ * `QualityGate.canInject` (session seen-set + recurrence + strength),
77
+ * and each admitted memory is recorded in the `InjectionLedger`
78
+ * (plan §5.2 — one row per injected memory).
45
79
  */
46
80
  private inject;
81
+ /**
82
+ * v0.4.0 (K4-017) — QualityGate admission: filters the ranked slice to
83
+ * memories that may be injected this session, updating the session
84
+ * seen-set.
85
+ *
86
+ * BUG-005 — strength/actionability now go through the REAL
87
+ * `QualityGate.evaluate` semantics (plan §5.1 rules 1-2), which this
88
+ * class had only re-derived from `metadata.dispatch`:
89
+ * - dispatched code → strong + actionable (rescued errorType);
90
+ * - generic fallback suggestion + no code → weak, NOT actionable
91
+ * (the generic-suggestion ban is now enforced even for legacy
92
+ * lessons without dispatch metadata, as long as the `Suggestion:`
93
+ * text is available);
94
+ * - specific suggestion without code → strong + actionable;
95
+ * - no `Suggestion:` text at all (agent-saved note) → strong +
96
+ * actionable — the agent explicitly asked to remember it;
97
+ * - causal patterns (type='pattern') → always strong + actionable:
98
+ * they are the FIXED form of a fingerprint (K4-025).
99
+ */
100
+ private admit;
101
+ /**
102
+ * BUG-005 — the single source of truth for a memory's lesson quality,
103
+ * shared by `admit` (the gate) and `format` (the K4-023 `(low
104
+ * confidence)` marker). Routes lessons through `QualityGate.evaluate`
105
+ * — the production call site that was previously missing.
106
+ */
107
+ private lessonQuality;
108
+ /**
109
+ * v0.4.0 (K4-017) — one ledger row per admitted memory (plan §5.2).
110
+ * Token attribution uses the memory's share of the final block.
111
+ */
112
+ private recordInjections;
113
+ /**
114
+ * v0.4.0 (K4-012) — snippet injection payload (plan §5.1 rule 5,
115
+ * D4-05): rows show `id:` + first 2 non-empty lines + `<protect>`
116
+ * instead of the full body. Gated by the `lesson_snippet_injection`
117
+ * setting (default `'1'`); when `'0'`, full content is restored.
118
+ * `escapeInjectedText` is applied to snippet content by the formatter.
119
+ * v0.4.0 (K4-023) — weak lessons admitted in debug mode are flagged so
120
+ * the formatter renders the `(low confidence)` marker.
121
+ */
122
+ private format;
47
123
  deriveQuery(messages: ChatMessage[]): string;
48
124
  onSystemTransform(input: SystemTransformInput, output: SystemTransformOutput): void;
49
125
  onCompacting(input: CompactingInput, output: CompactingOutput): void;