@jmtrin/opencode-kevin 0.3.0 → 0.5.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 (72) hide show
  1. package/README.md +117 -9
  2. package/dist/migrations/005_v04_signal.sql +57 -0
  3. package/dist/migrations/006_v05_glassbox.sql +118 -0
  4. package/dist/plugin/Archiver.d.ts +42 -0
  5. package/dist/plugin/Archiver.js +98 -0
  6. package/dist/plugin/Archiver.js.map +1 -0
  7. package/dist/plugin/CausalChain.d.ts +13 -2
  8. package/dist/plugin/CausalChain.js +83 -13
  9. package/dist/plugin/CausalChain.js.map +1 -1
  10. package/dist/plugin/ContextInjector.d.ts +152 -4
  11. package/dist/plugin/ContextInjector.js +400 -93
  12. package/dist/plugin/ContextInjector.js.map +1 -1
  13. package/dist/plugin/Feedback.d.ts +67 -0
  14. package/dist/plugin/Feedback.js +137 -0
  15. package/dist/plugin/Feedback.js.map +1 -0
  16. package/dist/plugin/InjectionLedger.d.ts +92 -0
  17. package/dist/plugin/InjectionLedger.js +243 -0
  18. package/dist/plugin/InjectionLedger.js.map +1 -0
  19. package/dist/plugin/LessonFixer.d.ts +44 -0
  20. package/dist/plugin/LessonFixer.js +46 -0
  21. package/dist/plugin/LessonFixer.js.map +1 -0
  22. package/dist/plugin/MemoryService.d.ts +81 -1
  23. package/dist/plugin/MemoryService.js +321 -54
  24. package/dist/plugin/MemoryService.js.map +1 -1
  25. package/dist/plugin/Migrate.js +31 -0
  26. package/dist/plugin/Migrate.js.map +1 -1
  27. package/dist/plugin/QualityGate.d.ts +110 -0
  28. package/dist/plugin/QualityGate.js +108 -0
  29. package/dist/plugin/QualityGate.js.map +1 -0
  30. package/dist/plugin/Reflector.d.ts +17 -0
  31. package/dist/plugin/Reflector.js +81 -26
  32. package/dist/plugin/Reflector.js.map +1 -1
  33. package/dist/plugin/Retrospective.js +22 -1
  34. package/dist/plugin/Retrospective.js.map +1 -1
  35. package/dist/plugin/ToolCallObserver.d.ts +1 -0
  36. package/dist/plugin/ToolCallObserver.js +15 -8
  37. package/dist/plugin/ToolCallObserver.js.map +1 -1
  38. package/dist/plugin/confidence.d.ts +8 -0
  39. package/dist/plugin/confidence.js +35 -0
  40. package/dist/plugin/confidence.js.map +1 -0
  41. package/dist/plugin/index.d.ts +5 -0
  42. package/dist/plugin/index.js +368 -44
  43. package/dist/plugin/index.js.map +1 -1
  44. package/dist/plugin/kevin_audit.d.ts +49 -0
  45. package/dist/plugin/kevin_audit.js +141 -0
  46. package/dist/plugin/kevin_audit.js.map +1 -0
  47. package/dist/plugin/kevin_why.d.ts +4 -0
  48. package/dist/plugin/kevin_why.js +72 -35
  49. package/dist/plugin/kevin_why.js.map +1 -1
  50. package/dist/plugin/memory-format.d.ts +12 -0
  51. package/dist/plugin/memory-format.js +45 -5
  52. package/dist/plugin/memory-format.js.map +1 -1
  53. package/dist/plugin/metrics.d.ts +27 -1
  54. package/dist/plugin/metrics.js +61 -0
  55. package/dist/plugin/metrics.js.map +1 -1
  56. package/dist/plugin/okf-export.js +63 -22
  57. package/dist/plugin/okf-export.js.map +1 -1
  58. package/dist/plugin/okf-import.d.ts +4 -1
  59. package/dist/plugin/okf-import.js +45 -12
  60. package/dist/plugin/okf-import.js.map +1 -1
  61. package/dist/plugin/query-tokenizer.d.ts +13 -0
  62. package/dist/plugin/query-tokenizer.js +86 -0
  63. package/dist/plugin/query-tokenizer.js.map +1 -0
  64. package/dist/plugin/replay-types.d.ts +327 -0
  65. package/dist/plugin/replay-types.js +65 -0
  66. package/dist/plugin/replay-types.js.map +1 -0
  67. package/dist/plugin/replay.d.ts +36 -0
  68. package/dist/plugin/replay.js +193 -0
  69. package/dist/plugin/replay.js.map +1 -0
  70. package/migrations/005_v04_signal.sql +57 -0
  71. package/migrations/006_v05_glassbox.sql +118 -0
  72. package/package.json +3 -2
@@ -1,110 +1,152 @@
1
- import { formatMemories } from "./memory-format.js";
1
+ import { QualityGate, } from "./QualityGate.js";
2
+ import { formatMemories, formatMemorySnippets } from "./memory-format.js";
2
3
  import { estimateTokens } from "./metrics.js";
3
- const SYSTEM_TRANSFORM_TOKENS = 1500;
4
+ import { STOP_WORDS } from "./query-tokenizer.js";
5
+ export const QUALITY_GATE_SETTING = "quality_gate_enabled";
6
+ export const SNIPPET_INJECTION_SETTING = "lesson_snippet_injection";
7
+ // v0.4.0 default pre-prompt budget. v0.5.0 (K5-017 / plan §8.11, D5-11):
8
+ // kept as the compile-time fallback, but the EFFECTIVE cap is read at call
9
+ // time from the `pre_prompt_budget_tokens` setting (default "900"). The
10
+ // confound fix in K5-005 will very likely show that a large share of
11
+ // injections are `inconclusive` — charging a 1500-token toll per prompt for
12
+ // an unproven benefit is indefensible, so the number is now a setting that
13
+ // measurement can drive instead of a constant.
14
+ const SYSTEM_TRANSFORM_TOKENS = 900;
4
15
  const COMPACTING_TOKENS = 2000;
5
- const STOP_WORDS = new Set([
6
- "a",
7
- "an",
8
- "and",
9
- "are",
10
- "at",
11
- "be",
12
- "been",
13
- "but",
14
- "by",
15
- "did",
16
- "do",
17
- "does",
18
- "el",
19
- "eso",
20
- "for",
21
- "how",
22
- "i",
23
- "if",
24
- "in",
25
- "is",
26
- "it",
27
- "la",
28
- "las",
29
- "los",
30
- "mi",
31
- "my",
32
- "o",
33
- "of",
34
- "on",
35
- "or",
36
- "para",
37
- "por",
38
- "que",
39
- "she",
40
- "su",
41
- "that",
42
- "the",
43
- "this",
44
- "to",
45
- "tu",
46
- "un",
47
- "una",
48
- "we",
49
- "were",
50
- "what",
51
- "when",
52
- "where",
53
- "which",
54
- "who",
55
- "why",
56
- "with",
57
- "y",
58
- "you",
59
- "como",
60
- "con",
61
- "de",
62
- "en",
63
- "he",
64
- "they",
65
- "was",
66
- "sin",
67
- ]);
16
+ // v0.5.0 (K5-007 / plan §8.10, D5-04) — every rejection reason maps 1:1 to
17
+ // an `injections_blocked_*` counter (principle 16: a rejection you did not
18
+ // count did not happen). Single lookup + null-check at the call site; the
19
+ // `ok` reason admits and must never increment anything.
20
+ const BLOCKED_METRIC = {
21
+ ok: null,
22
+ seen_this_session: "injections_blocked_seen",
23
+ ignored: "injections_blocked_ignored",
24
+ not_active: "injections_blocked_stale",
25
+ recurrence: "injections_blocked_recurrence",
26
+ weak: "injections_blocked_weak",
27
+ };
68
28
  function isWordChar(ch) {
69
29
  return /[a-z0-9áéíóúüñ]/i.test(ch);
70
30
  }
31
+ /**
32
+ * BUG-005 — extract the lesson's `Suggestion:` text (first line after the
33
+ * marker) when present. Returns null for agent-saved notes that carry no
34
+ * suggestion payload. Also matches the truncated snippet-style lesson
35
+ * bodies used by older tests.
36
+ */
37
+ function extractSuggestionText(content) {
38
+ const m = content.match(/\nSuggestion:\s*([^\n]+)/);
39
+ return m ? m[1].trim() : null;
40
+ }
41
+ /**
42
+ * BUG-005 — extract the `fails with X` slot of a lesson. For lessons
43
+ * without a dispatched code this equals the coarse errorType that
44
+ * `QualityGate.evaluate` needs for the strength classification.
45
+ */
46
+ function extractFailsWithErrorType(content) {
47
+ const m = content.match(/\bfails with ([^:]+):/i);
48
+ return m ? m[1].trim() : null;
49
+ }
71
50
  export class ContextInjector {
72
51
  memoryService;
73
52
  metrics;
53
+ ledger;
74
54
  lastRecurrenceCount = 0;
75
- constructor(memoryService, metrics = null) {
55
+ /** v0.4.0 (K4-016) session that produced the last recurrence set. */
56
+ lastRecurredSession = null;
57
+ /** v0.4.0 (K4-017) — per-session seen-set (plan §5.1 rule 3). */
58
+ seenBySession = new Map();
59
+ constructor(memoryService, metrics = null, ledger = null) {
76
60
  this.memoryService = memoryService;
77
61
  this.metrics = metrics;
62
+ this.ledger = ledger;
63
+ }
64
+ /**
65
+ * v0.4.0 (K4-017) — reset the per-session seen-set when a session is
66
+ * created (plan §5.1 rule 3). Wired from the `session.created` event.
67
+ */
68
+ onSessionCreated(sessionId) {
69
+ this.seenBySession.delete(sessionId);
78
70
  }
79
71
  /**
80
72
  * v0.3.0 (K3-020) — notify the injector that the negative feedback half
81
73
  * fired N times in the last session.idle. The next system.transform or
82
74
  * compacting hook will prepend a HITL suggestion block.
75
+ * v0.4.0 (K4-016) — the session id enables the concrete suggestion:
76
+ * the most-recurred fingerprint's pattern + its fix_args.
83
77
  */
84
- setRecurrences(count) {
78
+ setRecurrences(count, sessionId) {
85
79
  this.lastRecurrenceCount = count;
80
+ this.lastRecurredSession = sessionId ?? null;
86
81
  }
87
82
  /**
88
- * v0.3.0 (K3-020) — generate a HITL suggestion block when recurrences
89
- * occurred. Returns the block or empty string. Resets the counter.
83
+ * v0.4.0 (K4-016) — generate a CONCRETE HITL suggestion block when
84
+ * recurrences occurred (plan §5.5): names the most-recurred pattern,
85
+ * its exact recurrence count, observed fix_args and confidence. The
86
+ * AGENTS.md draft line derives from the lesson's `Suggestion:` text —
87
+ * never a canned string, and never the anonymous "the same error
88
+ * pattern" without naming the pattern and count.
89
+ *
90
+ * BUG-012 — emits AT MOST ONCE per session: calling it resets the
91
+ * pending recurrence signal, so whichever hook (system.transform or
92
+ * compacting) runs first in a session consumes the block. Documented
93
+ * behavior; wire index.ts to match.
90
94
  */
91
95
  generateSuggestion() {
92
- if (this.lastRecurrenceCount === 0)
96
+ const count = this.lastRecurrenceCount;
97
+ const sessionId = this.lastRecurredSession;
98
+ this.lastRecurrenceCount = 0;
99
+ this.lastRecurredSession = null;
100
+ if (count === 0)
93
101
  return "";
94
- const block = `<kevin-suggestion>
95
- The same error pattern recurred ${this.lastRecurrenceCount} time(s) this session.
96
- Consider adding a skill or convention to AGENTS.md to prevent this.
97
-
98
- Draft:
99
- \`\`\`markdown
100
- ## Recurring pattern
101
- - ${this.lastRecurrenceCount} recurrence(s) this session
102
- - Kevin's confidence in this pattern is low
103
- - Consider documenting the fix in AGENTS.md
104
- \`\`\`
102
+ const recurrences = this.ledger?.recurrencesFor(sessionId ?? "") ?? new Map();
103
+ let topFp = null;
104
+ let topCount = 0;
105
+ for (const [fp, n] of recurrences) {
106
+ if (n > topCount) {
107
+ topFp = fp;
108
+ topCount = n;
109
+ }
110
+ }
111
+ const pattern = topFp
112
+ ? this.memoryService.getByFingerprint(topFp, "pattern")
113
+ : null;
114
+ // No pattern memory for the recurred fingerprint (or no ledger): a
115
+ // short fallback that still names the count.
116
+ if (!pattern) {
117
+ return `<kevin-suggestion>
118
+ An error pattern recurred ${count} time(s) this session.
119
+ Consider adding a convention to AGENTS.md.
105
120
  </kevin-suggestion>`;
106
- this.lastRecurrenceCount = 0;
107
- return block;
121
+ }
122
+ const summary = pattern.content
123
+ .split("\n")[0]
124
+ .replace(/^Causal pattern:\s*/, "")
125
+ .trim();
126
+ const pct = pattern.confidence != null ? Math.round(pattern.confidence * 100) : 0;
127
+ const evidence = pattern.evidenceCount ?? 0;
128
+ const fixLine = pattern.fixArgs
129
+ ? `Observed fix: ${pattern.fixArgs} (${evidence} confirmed fix${evidence === 1 ? "" : "es"}, confidence ${pct}%).`
130
+ : "";
131
+ return `<kevin-suggestion>
132
+ The error pattern "${summary}" recurred ${topCount} time(s) this session.
133
+ ${fixLine}
134
+ Consider adding this convention to AGENTS.md:
135
+ - ${this.agentsDraftLine(pattern)}
136
+ </kevin-suggestion>`;
137
+ }
138
+ /**
139
+ * v0.4.0 (K4-016) — the AGENTS.md draft line derives from the lesson's
140
+ * `Suggestion:` text (kept verbatim in the pattern's `Original:` block).
141
+ */
142
+ agentsDraftLine(pattern) {
143
+ const m = pattern.content.match(/\nSuggestion: ([^\n]+)/);
144
+ if (m)
145
+ return m[1].trim();
146
+ return `${pattern.content
147
+ .split("\n")[0]
148
+ .replace(/^Causal pattern:\s*/, "")
149
+ .trim()} recurred — document the fix in AGENTS.md`;
108
150
  }
109
151
  /**
110
152
  * v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
@@ -114,28 +156,293 @@ Draft:
114
156
  * callers. Plan §B6.5: "apply the same multiplier as
115
157
  * MemoryService.recall so reflector lessons outrank agent-saved notes
116
158
  * at injection time" — satisfied transitively via the getRelevant call.
159
+ *
160
+ * v0.4.0 (K4-017): after retrieval, every memory is filtered through
161
+ * `QualityGate.canInject` (session seen-set + recurrence + strength),
162
+ * and each admitted memory is recorded in the `InjectionLedger`
163
+ * (plan §5.2 — one row per injected memory).
164
+ *
165
+ * v0.5.0 (K5-014 / plan §8.10) — the pipeline is decomposed into
166
+ * `fetchSlice` (ranked retrieval + budget overflow) + `evaluate`
167
+ * (pure gate verdicts) + this orchestrator, so the read-only `plan()`
168
+ * can mirror it without any side effect (D5-08).
117
169
  */
118
- inject(query, tag, cap, metricKey) {
119
- let memories = this.memoryService.getRelevant({ query, maxTokens: cap });
170
+ inject(query, tag, cap, metricKey, sessionId,
171
+ // v0.5.0 (K5-007 / plan §8.10, D5-08) dry-run mode (kevin_trace)
172
+ // must never move a counter, not even the injections_blocked_* ones.
173
+ dryRun = false) {
174
+ const memories = this.fetchSlice(query, cap, tag);
120
175
  if (memories.length === 0)
121
176
  return "";
122
- const firstBlock = formatMemories(memories, tag);
177
+ const admitted = this.admit(memories, sessionId, dryRun);
178
+ if (admitted.length === 0)
179
+ return "";
180
+ const block = this.format(admitted, tag);
181
+ this.recordInjections(admitted, sessionId, tag, block);
182
+ this.metrics?.incr(metricKey, estimateTokens(block));
183
+ return block;
184
+ }
185
+ /**
186
+ * v0.5.0 (K5-014 / plan §8.10) — the ranked-retrieval stage shared by
187
+ * `inject` and `plan`. With `dry = true` it is a strict read:
188
+ *
189
+ * - the probe fetch never bumps (BUG-016), like the live path;
190
+ * - the overflow retry ALSO fetches with `bump: false` (the live path
191
+ * lets the retry fetch bump once — that is the only difference);
192
+ * - the no-retry bump is skipped.
193
+ *
194
+ * This is what lets `plan()` predict the EXACT slice the live path
195
+ * would inject without mutating a single relevance score.
196
+ */
197
+ fetchSlice(query, cap, tag, dry = false) {
198
+ let memories = this.memoryService.getRelevant({
199
+ query,
200
+ maxTokens: cap,
201
+ bump: false,
202
+ });
203
+ if (memories.length === 0)
204
+ return [];
205
+ const firstBlock = this.format(memories, tag);
123
206
  const aggregateTokens = estimateTokens(firstBlock);
124
207
  const firstRowProtect = memories[0]
125
208
  ?.protect;
126
209
  const noProtectAboveTheFold = firstRowProtect === false;
127
210
  if (aggregateTokens > 0.8 * cap && noProtectAboveTheFold) {
211
+ // Retry = a single fetch with the adjusted budget, ranked by
212
+ // the original scores (the probe never mutated them) — the
213
+ // equivalent of one getRelevant call with maxTokens=lowerCap.
128
214
  const lowerCap = Math.max(1, Math.round(0.8 * cap));
129
215
  memories = this.memoryService.getRelevant({
130
216
  query,
131
217
  maxTokens: lowerCap,
218
+ bump: dry ? false : undefined,
132
219
  });
133
- if (memories.length === 0)
134
- return "";
135
220
  }
136
- const block = formatMemories(memories, tag);
137
- this.metrics?.incr(metricKey, estimateTokens(block));
138
- return block;
221
+ else if (!dry) {
222
+ // No retry: the probe slice IS the injected slice — bump it
223
+ // exactly once here.
224
+ this.memoryService.bumpRelevance(memories.map((m) => m.id));
225
+ }
226
+ return memories;
227
+ }
228
+ /**
229
+ * v0.5.0 (K5-014 / plan §8.10, D5-08) — PUBLIC read-only prediction of
230
+ * what `inject` WOULD do for a query: same retrieval, same gate, zero
231
+ * side effects. Never moves a counter, never writes the seen-set, never
232
+ * bumps relevance, never records ledger rows. `kevin_trace` (K5-015)
233
+ * surfaces this to the agent; tests freeze the clock + settings around
234
+ * it.
235
+ */
236
+ plan(query, options = {}) {
237
+ const tag = options.tag ?? "context";
238
+ const cap = options.cap ??
239
+ (tag === "memory" ? COMPACTING_TOKENS : this.prePromptCap());
240
+ const sessionId = options.sessionId ?? "";
241
+ const memories = this.fetchSlice(query, cap, tag, true);
242
+ const { verdicts } = this.evaluate(memories, sessionId);
243
+ const admitted = [];
244
+ const blocked = [];
245
+ const admittedMemories = [];
246
+ for (const v of verdicts) {
247
+ const base = {
248
+ id: v.memory.id,
249
+ type: v.memory.type,
250
+ tokens: estimateTokens(v.memory.content),
251
+ };
252
+ if (v.allowed) {
253
+ admitted.push({ ...base, decision: "admitted" });
254
+ admittedMemories.push(v.memory);
255
+ }
256
+ else {
257
+ blocked.push({
258
+ ...base,
259
+ decision: "blocked",
260
+ reason: v.reason,
261
+ });
262
+ }
263
+ }
264
+ const wouldInject = admitted.length > 0;
265
+ const totalTokens = wouldInject
266
+ ? estimateTokens(this.format(admittedMemories, tag))
267
+ : 0;
268
+ return {
269
+ query,
270
+ tag,
271
+ cap,
272
+ would_inject: wouldInject,
273
+ total_tokens: totalTokens,
274
+ admitted,
275
+ blocked,
276
+ };
277
+ }
278
+ /**
279
+ * v0.5.0 (K5-014 / plan §8.10) — PURE gate evaluation shared by `admit`
280
+ * (live path) and `plan` (read-only path): returns the verdict for every
281
+ * candidate plus the seen-set as it WOULD look afterwards. Never writes
282
+ * state — the caller decides whether to persist.
283
+ */
284
+ evaluate(memories, sessionId) {
285
+ const qualityGateEnabled = this.memoryService.getSetting(QUALITY_GATE_SETTING, "1") === "1";
286
+ const seen = new Set(this.seenBySession.get(sessionId) ?? []);
287
+ const recurrences = this.ledger?.postInjectionRecurrencesFor(sessionId) ??
288
+ new Map();
289
+ const verdicts = [];
290
+ for (const m of memories) {
291
+ const q = this.lessonQuality(m);
292
+ const verdict = QualityGate.canInjectVerdict({
293
+ id: m.id,
294
+ status: m.status ?? undefined,
295
+ strength: q.strength,
296
+ isActionable: q.isActionable,
297
+ // v0.5.0 (K5-009) — the `ignored` flag is now a first-class
298
+ // Memory field via mapRow (D5-07).
299
+ ignored: m.ignored === true,
300
+ }, {
301
+ seenThisSession: seen,
302
+ // v0.4.0 (K4-025) — plan §5.1 rule 4: a causal pattern
303
+ // re-admits a lesson that the stale error row cannot.
304
+ // The recurrence ban (QualityGate rule 3) is scoped to
305
+ // error lessons — a pattern is the FIXED form of the
306
+ // fingerprint and is exactly what D4-06 wants back in
307
+ // the prompt.
308
+ recurrenceCount: m.type === "pattern"
309
+ ? 0
310
+ : m.fingerprint
311
+ ? (recurrences.get(m.fingerprint) ?? 0)
312
+ : 0,
313
+ }, qualityGateEnabled);
314
+ verdicts.push({ ...verdict, memory: m });
315
+ }
316
+ return { verdicts, seen };
317
+ }
318
+ /**
319
+ * v0.5.0 (K5-017 / plan §8.11, D5-11) — the effective pre-prompt cap,
320
+ * read at call time from `pre_prompt_budget_tokens` (seeded "900" by
321
+ * migration 006). Clamped to [100, 4000]; a non-numeric value falls
322
+ * back to 900. `kevin_trace` reports the value used via `plan().cap`.
323
+ */
324
+ prePromptCap() {
325
+ const raw = this.memoryService.getSetting("pre_prompt_budget_tokens", "900");
326
+ const n = Number(raw);
327
+ if (!Number.isFinite(n))
328
+ return 900;
329
+ return Math.min(4000, Math.max(100, Math.round(n)));
330
+ }
331
+ /**
332
+ * v0.4.0 (K4-017) — QualityGate admission: filters the ranked slice to
333
+ * memories that may be injected this session, updating the session
334
+ * seen-set.
335
+ *
336
+ * v0.5.0 (K5-007 / plan §5.2, D5-04) — uses `canInjectVerdict` and
337
+ * increments the matching `injections_blocked_*` counter for every
338
+ * rejection, so gate policy becomes measurable. When `dryRun === true`
339
+ * the counters stay untouched (D5-08) — and so does the seen-set
340
+ * (a dry run is a strict read, K5-014).
341
+ *
342
+ * BUG-005 — strength/actionability now go through the REAL
343
+ * `QualityGate.evaluate` semantics (plan §5.1 rules 1-2), which this
344
+ * class had only re-derived from `metadata.dispatch`:
345
+ * - dispatched code → strong + actionable (rescued errorType);
346
+ * - generic fallback suggestion + no code → weak, NOT actionable
347
+ * (the generic-suggestion ban is now enforced even for legacy
348
+ * lessons without dispatch metadata, as long as the `Suggestion:`
349
+ * text is available);
350
+ * - specific suggestion without code → strong + actionable;
351
+ * - no `Suggestion:` text at all (agent-saved note) → strong +
352
+ * actionable — the agent explicitly asked to remember it;
353
+ * - causal patterns (type='pattern') → always strong + actionable:
354
+ * they are the FIXED form of a fingerprint (K4-025).
355
+ */
356
+ admit(memories, sessionId, dryRun = false) {
357
+ const { verdicts, seen } = this.evaluate(memories, sessionId);
358
+ const admitted = [];
359
+ for (const v of verdicts) {
360
+ if (v.allowed) {
361
+ admitted.push(v.memory);
362
+ seen.add(v.memory.id);
363
+ }
364
+ else if (!dryRun) {
365
+ const key = BLOCKED_METRIC[v.reason];
366
+ if (key)
367
+ this.metrics?.incr(key, 1);
368
+ }
369
+ }
370
+ if (!dryRun)
371
+ this.seenBySession.set(sessionId, seen);
372
+ return admitted;
373
+ }
374
+ /**
375
+ * BUG-005 — the single source of truth for a memory's lesson quality,
376
+ * shared by `admit` (the gate) and `format` (the K4-023 `(low
377
+ * confidence)` marker). Routes lessons through `QualityGate.evaluate`
378
+ * — the production call site that was previously missing.
379
+ */
380
+ lessonQuality(m) {
381
+ // K4-025: a causal pattern is the fixed form of the fingerprint —
382
+ // never gated by suggestion text.
383
+ if (m.type === "pattern") {
384
+ return { strength: "strong", isActionable: true, weak: false };
385
+ }
386
+ const meta = (m.metadata ?? null);
387
+ const dispatch = meta?.dispatch ?? null;
388
+ const suggestion = extractSuggestionText(m.content);
389
+ if (suggestion === null) {
390
+ // Agent-saved note without a `Suggestion:` line: the agent
391
+ // explicitly asked to remember it → strong + actionable.
392
+ return { strength: "strong", isActionable: true, weak: false };
393
+ }
394
+ // The lesson's `fails with X` slot holds the displayed errorType;
395
+ // when no code was dispatched it equals the coarse errorType that
396
+ // `evaluate` needs for the strength classification.
397
+ const errorType = extractFailsWithErrorType(m.content);
398
+ const q = QualityGate.evaluate({ errorType: errorType ?? "unknown", suggestion }, dispatch, errorType ?? "unknown");
399
+ return {
400
+ strength: q.strength,
401
+ isActionable: q.isActionable,
402
+ weak: q.strength === "weak",
403
+ };
404
+ }
405
+ /**
406
+ * v0.4.0 (K4-017) — one ledger row per admitted memory (plan §5.2).
407
+ * Token attribution uses the memory's share of the final block.
408
+ */
409
+ recordInjections(admitted, sessionId, tag, block) {
410
+ if (this.ledger === null)
411
+ return;
412
+ const blockTokens = estimateTokens(block);
413
+ const measurable = admitted.filter((m) => m.fingerprint);
414
+ const perMemory = Math.max(1, Math.round(blockTokens / admitted.length));
415
+ const hook = tag === "context" ? "pre_prompt" : "compacting";
416
+ for (const m of measurable) {
417
+ this.ledger.record({
418
+ memoryId: m.id,
419
+ fingerprint: m.fingerprint,
420
+ sessionId,
421
+ hook,
422
+ tokens: perMemory,
423
+ });
424
+ }
425
+ }
426
+ /**
427
+ * v0.4.0 (K4-012) — snippet injection payload (plan §5.1 rule 5,
428
+ * D4-05): rows show `id:` + first 2 non-empty lines + `<protect>`
429
+ * instead of the full body. Gated by the `lesson_snippet_injection`
430
+ * setting (default `'1'`); when `'0'`, full content is restored.
431
+ * `escapeInjectedText` is applied to snippet content by the formatter.
432
+ * v0.4.0 (K4-023) — weak lessons admitted in debug mode are flagged so
433
+ * the formatter renders the `(low confidence)` marker.
434
+ */
435
+ format(memories, tag) {
436
+ const snippetsOn = this.memoryService.getSetting(SNIPPET_INJECTION_SETTING, "1") === "1";
437
+ const items = memories.map((m) => ({
438
+ ...m,
439
+ // BUG-005 — the weak marker must mirror the admission decision
440
+ // (K4-023 debug mode), not re-derive from dispatch alone.
441
+ weak: this.lessonQuality(m).weak,
442
+ }));
443
+ return snippetsOn
444
+ ? formatMemorySnippets(items, tag)
445
+ : formatMemories(items, tag);
139
446
  }
140
447
  deriveQuery(messages) {
141
448
  let lastUserContent = "";
@@ -165,7 +472,7 @@ Draft:
165
472
  const query = this.deriveQuery(input.messages);
166
473
  if (!query)
167
474
  return;
168
- const block = this.inject(query, "context", SYSTEM_TRANSFORM_TOKENS, "tokens_injected_pre_prompt");
475
+ const block = this.inject(query, "context", this.prePromptCap(), "tokens_injected_pre_prompt", input.sessionID ?? "");
169
476
  if (block)
170
477
  output.system.push(block);
171
478
  }
@@ -173,7 +480,7 @@ Draft:
173
480
  const query = this.deriveQuery(input.messages);
174
481
  if (!query)
175
482
  return;
176
- const block = this.inject(query, "memory", COMPACTING_TOKENS, "tokens_injected_compacting");
483
+ const block = this.inject(query, "memory", COMPACTING_TOKENS, "tokens_injected_compacting", input.sessionID);
177
484
  if (block)
178
485
  output.context.push(block);
179
486
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ContextInjector.js","sourceRoot":"","sources":["../../plugin/ContextInjector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ContextInjector.js","sourceRoot":"","sources":["../../plugin/ContextInjector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { Store } from "./Store.js";
2
+ import type { Metrics } from "./metrics.js";
3
+ /**
4
+ * v0.5.0 Feedback (K5-009 / plan §5.3, D5-02).
5
+ *
6
+ * The human-judgement half of the glassbox: the agent can report whether a
7
+ * memory was useful, wrong, outdated, or should be ignored. Verdicts are
8
+ * stored in `memory_feedback` (migration 006_v05_glassbox.sql) and folded
9
+ * into the memory's `feedback_positive` / `feedback_negative` counters —
10
+ * kept SEPARATE from `evidence_count` / `recurrence_count` by design: human
11
+ * judgement is evidence about the memory, causal counters are evidence
12
+ * about the world (the confidence-poisoning defect closed in v0.4.0).
13
+ *
14
+ * D5-07 — the `ignore` verdict is a hard lifecycle action, not a soft
15
+ * signal: the memory is stamped `ignored = 1`, which excludes it from
16
+ * retrieval (K5-008) and from the quality gate (K5-007).
17
+ *
18
+ * Schema:
19
+ * memory_feedback(id PK, memory_id, verdict, session_id, note, created_at)
20
+ * memories(feedback_positive, feedback_negative, ignored)
21
+ */
22
+ export type FeedbackVerdict = "useful" | "wrong" | "outdated" | "ignore";
23
+ export interface FeedbackRecordInput {
24
+ memoryId: string;
25
+ verdict: FeedbackVerdict;
26
+ sessionId?: string | null;
27
+ note?: string | null;
28
+ }
29
+ export interface FeedbackRow {
30
+ id: string;
31
+ memoryId: string;
32
+ verdict: FeedbackVerdict;
33
+ sessionId: string | null;
34
+ note: string | null;
35
+ createdAt: string;
36
+ }
37
+ export interface FeedbackCounts {
38
+ positive: number;
39
+ negative: number;
40
+ }
41
+ export declare class Feedback {
42
+ private readonly store;
43
+ private readonly metrics;
44
+ private readonly now;
45
+ constructor(store: Store, metrics?: Metrics | null, now?: () => Date);
46
+ /**
47
+ * Records one human verdict and folds it into the memory's counters.
48
+ * Counters are RECOMPUTED from the table (never incremented) so rows
49
+ * deleted in tests or by hand cannot drift them.
50
+ *
51
+ * The `ignore` verdict also stamps `memories.ignored = 1` (D5-07).
52
+ * Returns the feedback row id.
53
+ */
54
+ record(input: FeedbackRecordInput): string;
55
+ /** Human-judgement counters for one memory (from the row, not the table). */
56
+ countsFor(memoryId: string): FeedbackCounts;
57
+ /**
58
+ * Raw verdict history, newest first. Used by `kevin_feedback` (K5-011)
59
+ * and by the audit tool (K5-016).
60
+ */
61
+ list(memoryId?: string, limit?: number): FeedbackRow[];
62
+ /**
63
+ * Recompute a memory's positive/negative counters straight from the
64
+ * verdict table. Kept private — `record` is the only mutation path.
65
+ */
66
+ private recomputeCounters;
67
+ }