@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
@@ -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,7 +24,45 @@ export interface CompactingOutput {
21
24
  export declare class ContextInjector {
22
25
  private memoryService;
23
26
  private metrics;
24
- constructor(memoryService: MemoryService, metrics?: Metrics | null);
27
+ private ledger;
28
+ private lastRecurrenceCount;
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;
39
+ /**
40
+ * v0.3.0 (K3-020) — notify the injector that the negative feedback half
41
+ * fired N times in the last session.idle. The next system.transform or
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.
45
+ */
46
+ setRecurrences(count: number, sessionId?: string): void;
47
+ /**
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.
59
+ */
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;
25
66
  /**
26
67
  * v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
27
68
  * to `MemoryService.getRelevant()` (K2-023, D2-13). The injector does
@@ -30,8 +71,55 @@ export declare class ContextInjector {
30
71
  * callers. Plan §B6.5: "apply the same multiplier as
31
72
  * MemoryService.recall so reflector lessons outrank agent-saved notes
32
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).
33
79
  */
34
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;
35
123
  deriveQuery(messages: ChatMessage[]): string;
36
124
  onSystemTransform(input: SystemTransformInput, output: SystemTransformOutput): void;
37
125
  onCompacting(input: CompactingInput, output: CompactingOutput): void;
@@ -1,79 +1,133 @@
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";
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";
3
7
  const SYSTEM_TRANSFORM_TOKENS = 1500;
4
8
  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
- ]);
68
9
  function isWordChar(ch) {
69
10
  return /[a-z0-9áéíóúüñ]/i.test(ch);
70
11
  }
12
+ /**
13
+ * BUG-005 — extract the lesson's `Suggestion:` text (first line after the
14
+ * marker) when present. Returns null for agent-saved notes that carry no
15
+ * suggestion payload. Also matches the truncated snippet-style lesson
16
+ * bodies used by older tests.
17
+ */
18
+ function extractSuggestionText(content) {
19
+ const m = content.match(/\nSuggestion:\s*([^\n]+)/);
20
+ return m ? m[1].trim() : null;
21
+ }
22
+ /**
23
+ * BUG-005 — extract the `fails with X` slot of a lesson. For lessons
24
+ * without a dispatched code this equals the coarse errorType that
25
+ * `QualityGate.evaluate` needs for the strength classification.
26
+ */
27
+ function extractFailsWithErrorType(content) {
28
+ const m = content.match(/\bfails with ([^:]+):/i);
29
+ return m ? m[1].trim() : null;
30
+ }
71
31
  export class ContextInjector {
72
32
  memoryService;
73
33
  metrics;
74
- constructor(memoryService, metrics = null) {
34
+ ledger;
35
+ lastRecurrenceCount = 0;
36
+ /** v0.4.0 (K4-016) — session that produced the last recurrence set. */
37
+ lastRecurredSession = null;
38
+ /** v0.4.0 (K4-017) — per-session seen-set (plan §5.1 rule 3). */
39
+ seenBySession = new Map();
40
+ constructor(memoryService, metrics = null, ledger = null) {
75
41
  this.memoryService = memoryService;
76
42
  this.metrics = metrics;
43
+ this.ledger = ledger;
44
+ }
45
+ /**
46
+ * v0.4.0 (K4-017) — reset the per-session seen-set when a session is
47
+ * created (plan §5.1 rule 3). Wired from the `session.created` event.
48
+ */
49
+ onSessionCreated(sessionId) {
50
+ this.seenBySession.delete(sessionId);
51
+ }
52
+ /**
53
+ * v0.3.0 (K3-020) — notify the injector that the negative feedback half
54
+ * fired N times in the last session.idle. The next system.transform or
55
+ * compacting hook will prepend a HITL suggestion block.
56
+ * v0.4.0 (K4-016) — the session id enables the concrete suggestion:
57
+ * the most-recurred fingerprint's pattern + its fix_args.
58
+ */
59
+ setRecurrences(count, sessionId) {
60
+ this.lastRecurrenceCount = count;
61
+ this.lastRecurredSession = sessionId ?? null;
62
+ }
63
+ /**
64
+ * v0.4.0 (K4-016) — generate a CONCRETE HITL suggestion block when
65
+ * recurrences occurred (plan §5.5): names the most-recurred pattern,
66
+ * its exact recurrence count, observed fix_args and confidence. The
67
+ * AGENTS.md draft line derives from the lesson's `Suggestion:` text —
68
+ * never a canned string, and never the anonymous "the same error
69
+ * pattern" without naming the pattern and count.
70
+ *
71
+ * BUG-012 — emits AT MOST ONCE per session: calling it resets the
72
+ * pending recurrence signal, so whichever hook (system.transform or
73
+ * compacting) runs first in a session consumes the block. Documented
74
+ * behavior; wire index.ts to match.
75
+ */
76
+ generateSuggestion() {
77
+ const count = this.lastRecurrenceCount;
78
+ const sessionId = this.lastRecurredSession;
79
+ this.lastRecurrenceCount = 0;
80
+ this.lastRecurredSession = null;
81
+ if (count === 0)
82
+ return "";
83
+ const recurrences = this.ledger?.recurrencesFor(sessionId ?? "") ?? new Map();
84
+ let topFp = null;
85
+ let topCount = 0;
86
+ for (const [fp, n] of recurrences) {
87
+ if (n > topCount) {
88
+ topFp = fp;
89
+ topCount = n;
90
+ }
91
+ }
92
+ const pattern = topFp
93
+ ? this.memoryService.getByFingerprint(topFp, "pattern")
94
+ : null;
95
+ // No pattern memory for the recurred fingerprint (or no ledger): a
96
+ // short fallback that still names the count.
97
+ if (!pattern) {
98
+ return `<kevin-suggestion>
99
+ An error pattern recurred ${count} time(s) this session.
100
+ Consider adding a convention to AGENTS.md.
101
+ </kevin-suggestion>`;
102
+ }
103
+ const summary = pattern.content
104
+ .split("\n")[0]
105
+ .replace(/^Causal pattern:\s*/, "")
106
+ .trim();
107
+ const pct = pattern.confidence != null ? Math.round(pattern.confidence * 100) : 0;
108
+ const evidence = pattern.evidenceCount ?? 0;
109
+ const fixLine = pattern.fixArgs
110
+ ? `Observed fix: ${pattern.fixArgs} (${evidence} confirmed fix${evidence === 1 ? "" : "es"}, confidence ${pct}%).`
111
+ : "";
112
+ return `<kevin-suggestion>
113
+ The error pattern "${summary}" recurred ${topCount} time(s) this session.
114
+ ${fixLine}
115
+ Consider adding this convention to AGENTS.md:
116
+ - ${this.agentsDraftLine(pattern)}
117
+ </kevin-suggestion>`;
118
+ }
119
+ /**
120
+ * v0.4.0 (K4-016) — the AGENTS.md draft line derives from the lesson's
121
+ * `Suggestion:` text (kept verbatim in the pattern's `Original:` block).
122
+ */
123
+ agentsDraftLine(pattern) {
124
+ const m = pattern.content.match(/\nSuggestion: ([^\n]+)/);
125
+ if (m)
126
+ return m[1].trim();
127
+ return `${pattern.content
128
+ .split("\n")[0]
129
+ .replace(/^Causal pattern:\s*/, "")
130
+ .trim()} recurred — document the fix in AGENTS.md`;
77
131
  }
78
132
  /**
79
133
  * v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
@@ -83,17 +137,33 @@ export class ContextInjector {
83
137
  * callers. Plan §B6.5: "apply the same multiplier as
84
138
  * MemoryService.recall so reflector lessons outrank agent-saved notes
85
139
  * at injection time" — satisfied transitively via the getRelevant call.
140
+ *
141
+ * v0.4.0 (K4-017): after retrieval, every memory is filtered through
142
+ * `QualityGate.canInject` (session seen-set + recurrence + strength),
143
+ * and each admitted memory is recorded in the `InjectionLedger`
144
+ * (plan §5.2 — one row per injected memory).
86
145
  */
87
- inject(query, tag, cap, metricKey) {
88
- let memories = this.memoryService.getRelevant({ query, maxTokens: cap });
146
+ inject(query, tag, cap, metricKey, sessionId) {
147
+ // BUG-016 probe WITHOUT bumping so the overflow decision and any
148
+ // retry both see the ORIGINAL ranking. The single relevance bump
149
+ // (K2-023) is applied exactly once, to the slice that actually
150
+ // produces the injected block — see below.
151
+ let memories = this.memoryService.getRelevant({
152
+ query,
153
+ maxTokens: cap,
154
+ bump: false,
155
+ });
89
156
  if (memories.length === 0)
90
157
  return "";
91
- const firstBlock = formatMemories(memories, tag);
158
+ const firstBlock = this.format(memories, tag);
92
159
  const aggregateTokens = estimateTokens(firstBlock);
93
160
  const firstRowProtect = memories[0]
94
161
  ?.protect;
95
162
  const noProtectAboveTheFold = firstRowProtect === false;
96
163
  if (aggregateTokens > 0.8 * cap && noProtectAboveTheFold) {
164
+ // Retry = a single fetch with the adjusted budget, ranked by
165
+ // the original scores (the probe never mutated them) — the
166
+ // equivalent of one getRelevant call with maxTokens=lowerCap.
97
167
  const lowerCap = Math.max(1, Math.round(0.8 * cap));
98
168
  memories = this.memoryService.getRelevant({
99
169
  query,
@@ -102,10 +172,145 @@ export class ContextInjector {
102
172
  if (memories.length === 0)
103
173
  return "";
104
174
  }
105
- const block = formatMemories(memories, tag);
175
+ else {
176
+ // No retry: the probe slice IS the injected slice — bump it
177
+ // exactly once here.
178
+ this.memoryService.bumpRelevance(memories.map((m) => m.id));
179
+ }
180
+ const admitted = this.admit(memories, sessionId);
181
+ if (admitted.length === 0)
182
+ return "";
183
+ const block = this.format(admitted, tag);
184
+ this.recordInjections(admitted, sessionId, tag, block);
106
185
  this.metrics?.incr(metricKey, estimateTokens(block));
107
186
  return block;
108
187
  }
188
+ /**
189
+ * v0.4.0 (K4-017) — QualityGate admission: filters the ranked slice to
190
+ * memories that may be injected this session, updating the session
191
+ * seen-set.
192
+ *
193
+ * BUG-005 — strength/actionability now go through the REAL
194
+ * `QualityGate.evaluate` semantics (plan §5.1 rules 1-2), which this
195
+ * class had only re-derived from `metadata.dispatch`:
196
+ * - dispatched code → strong + actionable (rescued errorType);
197
+ * - generic fallback suggestion + no code → weak, NOT actionable
198
+ * (the generic-suggestion ban is now enforced even for legacy
199
+ * lessons without dispatch metadata, as long as the `Suggestion:`
200
+ * text is available);
201
+ * - specific suggestion without code → strong + actionable;
202
+ * - no `Suggestion:` text at all (agent-saved note) → strong +
203
+ * actionable — the agent explicitly asked to remember it;
204
+ * - causal patterns (type='pattern') → always strong + actionable:
205
+ * they are the FIXED form of a fingerprint (K4-025).
206
+ */
207
+ admit(memories, sessionId) {
208
+ const qualityGateEnabled = this.memoryService.getSetting(QUALITY_GATE_SETTING, "1") === "1";
209
+ const seen = this.seenBySession.get(sessionId) ?? new Set();
210
+ const recurrences = this.ledger?.postInjectionRecurrencesFor(sessionId) ??
211
+ new Map();
212
+ const admitted = [];
213
+ for (const m of memories) {
214
+ const q = this.lessonQuality(m);
215
+ if (QualityGate.canInject({
216
+ id: m.id,
217
+ status: m.status ?? undefined,
218
+ strength: q.strength,
219
+ isActionable: q.isActionable,
220
+ }, {
221
+ seenThisSession: seen,
222
+ // v0.4.0 (K4-025) — plan §5.1 rule 4: a causal pattern
223
+ // re-admits a lesson that the stale error row cannot.
224
+ // The recurrence ban (QualityGate rule 3) is scoped to
225
+ // error lessons — a pattern is the FIXED form of the
226
+ // fingerprint and is exactly what D4-06 wants back in
227
+ // the prompt.
228
+ recurrenceCount: m.type === "pattern"
229
+ ? 0
230
+ : m.fingerprint
231
+ ? (recurrences.get(m.fingerprint) ?? 0)
232
+ : 0,
233
+ }, qualityGateEnabled)) {
234
+ admitted.push(m);
235
+ seen.add(m.id);
236
+ }
237
+ }
238
+ this.seenBySession.set(sessionId, seen);
239
+ return admitted;
240
+ }
241
+ /**
242
+ * BUG-005 — the single source of truth for a memory's lesson quality,
243
+ * shared by `admit` (the gate) and `format` (the K4-023 `(low
244
+ * confidence)` marker). Routes lessons through `QualityGate.evaluate`
245
+ * — the production call site that was previously missing.
246
+ */
247
+ lessonQuality(m) {
248
+ // K4-025: a causal pattern is the fixed form of the fingerprint —
249
+ // never gated by suggestion text.
250
+ if (m.type === "pattern") {
251
+ return { strength: "strong", isActionable: true, weak: false };
252
+ }
253
+ const meta = (m.metadata ?? null);
254
+ const dispatch = meta?.dispatch ?? null;
255
+ const suggestion = extractSuggestionText(m.content);
256
+ if (suggestion === null) {
257
+ // Agent-saved note without a `Suggestion:` line: the agent
258
+ // explicitly asked to remember it → strong + actionable.
259
+ return { strength: "strong", isActionable: true, weak: false };
260
+ }
261
+ // The lesson's `fails with X` slot holds the displayed errorType;
262
+ // when no code was dispatched it equals the coarse errorType that
263
+ // `evaluate` needs for the strength classification.
264
+ const errorType = extractFailsWithErrorType(m.content);
265
+ const q = QualityGate.evaluate({ errorType: errorType ?? "unknown", suggestion }, dispatch, errorType ?? "unknown");
266
+ return {
267
+ strength: q.strength,
268
+ isActionable: q.isActionable,
269
+ weak: q.strength === "weak",
270
+ };
271
+ }
272
+ /**
273
+ * v0.4.0 (K4-017) — one ledger row per admitted memory (plan §5.2).
274
+ * Token attribution uses the memory's share of the final block.
275
+ */
276
+ recordInjections(admitted, sessionId, tag, block) {
277
+ if (this.ledger === null)
278
+ return;
279
+ const blockTokens = estimateTokens(block);
280
+ const measurable = admitted.filter((m) => m.fingerprint);
281
+ const perMemory = Math.max(1, Math.round(blockTokens / admitted.length));
282
+ const hook = tag === "context" ? "pre_prompt" : "compacting";
283
+ for (const m of measurable) {
284
+ this.ledger.record({
285
+ memoryId: m.id,
286
+ fingerprint: m.fingerprint,
287
+ sessionId,
288
+ hook,
289
+ tokens: perMemory,
290
+ });
291
+ }
292
+ }
293
+ /**
294
+ * v0.4.0 (K4-012) — snippet injection payload (plan §5.1 rule 5,
295
+ * D4-05): rows show `id:` + first 2 non-empty lines + `<protect>`
296
+ * instead of the full body. Gated by the `lesson_snippet_injection`
297
+ * setting (default `'1'`); when `'0'`, full content is restored.
298
+ * `escapeInjectedText` is applied to snippet content by the formatter.
299
+ * v0.4.0 (K4-023) — weak lessons admitted in debug mode are flagged so
300
+ * the formatter renders the `(low confidence)` marker.
301
+ */
302
+ format(memories, tag) {
303
+ const snippetsOn = this.memoryService.getSetting(SNIPPET_INJECTION_SETTING, "1") === "1";
304
+ const items = memories.map((m) => ({
305
+ ...m,
306
+ // BUG-005 — the weak marker must mirror the admission decision
307
+ // (K4-023 debug mode), not re-derive from dispatch alone.
308
+ weak: this.lessonQuality(m).weak,
309
+ }));
310
+ return snippetsOn
311
+ ? formatMemorySnippets(items, tag)
312
+ : formatMemories(items, tag);
313
+ }
109
314
  deriveQuery(messages) {
110
315
  let lastUserContent = "";
111
316
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -134,7 +339,7 @@ export class ContextInjector {
134
339
  const query = this.deriveQuery(input.messages);
135
340
  if (!query)
136
341
  return;
137
- const block = this.inject(query, "context", SYSTEM_TRANSFORM_TOKENS, "tokens_injected_pre_prompt");
342
+ const block = this.inject(query, "context", SYSTEM_TRANSFORM_TOKENS, "tokens_injected_pre_prompt", input.sessionID ?? "");
138
343
  if (block)
139
344
  output.system.push(block);
140
345
  }
@@ -142,7 +347,7 @@ export class ContextInjector {
142
347
  const query = this.deriveQuery(input.messages);
143
348
  if (!query)
144
349
  return;
145
- const block = this.inject(query, "memory", COMPACTING_TOKENS, "tokens_injected_compacting");
350
+ const block = this.inject(query, "memory", COMPACTING_TOKENS, "tokens_injected_compacting", input.sessionID);
146
351
  if (block)
147
352
  output.context.push(block);
148
353
  }
@@ -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,85 @@
1
+ import type { Store } from "./Store.js";
2
+ import type { Metrics } from "./metrics.js";
3
+ /**
4
+ * v0.4.0 InjectionLedger (K4-006/K4-007, plan §5.2, D4-04).
5
+ *
6
+ * The measurement half of the closed feedback loop: every lesson injected
7
+ * into a prompt is recorded in `kevin_injections`, and at `session.idle` the
8
+ * ledger settles each unmeasured row — if the same fingerprint failed again
9
+ * after the injection, the lesson is `ineffective`; otherwise `effective`.
10
+ * `precision_rate = effective / total` is the honest, measured value of the
11
+ * injection system (replacing Kevin_Token_Impact-style estimates).
12
+ *
13
+ * Schema (migration 005_v04_signal.sql):
14
+ * kevin_injections(id PK, memory_id, fingerprint, session_id, hook,
15
+ * tokens, injected_at, outcome)
16
+ *
17
+ * BUG-015 — a ledger row is only meaningful when its fingerprint can be
18
+ * matched against failing tool_calls (`COALESCE(error_fingerprint,
19
+ * fingerprint)`). Memories WITHOUT a fingerprint (agent-saved notes) can
20
+ * never match, so they are NOT recorded: settle() would otherwise mark
21
+ * them `effective` forever and inflate `precision_rate`. The caller
22
+ * (ContextInjector.recordInjections) skips them.
23
+ */
24
+ export type InjectionHook = "pre_prompt" | "compacting";
25
+ export type InjectionOutcome = "unmeasured" | "effective" | "ineffective";
26
+ export interface InjectionRecordInput {
27
+ memoryId: string;
28
+ fingerprint: string;
29
+ sessionId: string;
30
+ hook: InjectionHook;
31
+ tokens: number;
32
+ }
33
+ interface InjectionRow {
34
+ id: string;
35
+ memory_id: string;
36
+ fingerprint: string;
37
+ session_id: string;
38
+ hook: InjectionHook;
39
+ tokens: number;
40
+ injected_at: string;
41
+ outcome: InjectionOutcome;
42
+ }
43
+ export declare class InjectionLedger {
44
+ private readonly store;
45
+ private readonly metrics;
46
+ constructor(store: Store, metrics?: Metrics | null);
47
+ /**
48
+ * Records one injected memory. Idempotent at the row level (UUID PK);
49
+ * duplicates are expected only via the caller's per-session seen-set.
50
+ */
51
+ record(input: InjectionRecordInput): void;
52
+ /**
53
+ * Settles every unmeasured injection of the session: a fingerprint that
54
+ * failed again (as a failing tool_call) after the injection is
55
+ * `ineffective`, otherwise `effective`. Idempotent — only
56
+ * `outcome = 'unmeasured'` rows are flipped, and the recurrence charge
57
+ * is `MAX(recurrence_count, n)` where n = all failing calls of the
58
+ * fingerprint after `injected_at` (a later idle re-computes n and
59
+ * catches up — plan §5.1 rule 4: 3 recurrences → stale).
60
+ *
61
+ * An ineffective injection also bumps the target memory's
62
+ * `recurrence_count` (negative evidence, plan §5.3) and stamps
63
+ * `last_injected_at`.
64
+ */
65
+ settle(sessionId: string): void;
66
+ /**
67
+ * Per-fingerprint failing tool-call counts for the session. Feeds
68
+ * QualityGate.canInject and the HITL suggestion block.
69
+ */
70
+ recurrencesFor(sessionId: string): Map<string, number>;
71
+ /**
72
+ * v0.4.0 (K4-017) — recurrence counts for the QualityGate at
73
+ * injection time: only failing calls that happened AFTER the
74
+ * fingerprint was already injected this session count. The failure
75
+ * that *created* a lesson is not a recurrence — it precedes any
76
+ * injection (plan §5.1 rule 4, same `ts >= injected_at` semantics
77
+ * `settle` uses).
78
+ */
79
+ postInjectionRecurrencesFor(sessionId: string): Map<string, number>;
80
+ /** Number of rows for the session not yet settled (drives tests and settle). */
81
+ unsettledForSession(sessionId: string): number;
82
+ /** Latest ledger rows for a session, newest first (used by tests/tools). */
83
+ rowsForSession(sessionId: string): InjectionRow[];
84
+ }
85
+ export {};