@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,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,9 @@
1
+ import type { InjectionLedger } from "./InjectionLedger.js";
1
2
  import type { MemoryService } from "./MemoryService.js";
3
+ import { type GateReason } from "./QualityGate.js";
2
4
  import { type Metrics } from "./metrics.js";
5
+ export declare const QUALITY_GATE_SETTING = "quality_gate_enabled";
6
+ export declare const SNIPPET_INJECTION_SETTING = "lesson_snippet_injection";
3
7
  export interface ChatMessage {
4
8
  role: string;
5
9
  content: string;
@@ -18,22 +22,68 @@ export interface CompactingInput {
18
22
  export interface CompactingOutput {
19
23
  context: string[];
20
24
  }
25
+ export interface InjectionPlanItem {
26
+ id: string;
27
+ type: string;
28
+ decision: "admitted" | "blocked";
29
+ /** Gate reason when blocked; null for admitted items. */
30
+ reason?: GateReason;
31
+ /** Estimated tokens this memory would contribute to the block. */
32
+ tokens: number;
33
+ }
34
+ export interface InjectionPlan {
35
+ query: string;
36
+ tag: "context" | "memory";
37
+ cap: number;
38
+ would_inject: boolean;
39
+ /** Estimated tokens of the block that WOULD be produced (0 when
40
+ * nothing would be injected). */
41
+ total_tokens: number;
42
+ admitted: InjectionPlanItem[];
43
+ blocked: InjectionPlanItem[];
44
+ }
21
45
  export declare class ContextInjector {
22
46
  private memoryService;
23
47
  private metrics;
48
+ private ledger;
24
49
  private lastRecurrenceCount;
25
- constructor(memoryService: MemoryService, metrics?: Metrics | null);
50
+ /** v0.4.0 (K4-016) session that produced the last recurrence set. */
51
+ private lastRecurredSession;
52
+ /** v0.4.0 (K4-017) — per-session seen-set (plan §5.1 rule 3). */
53
+ private readonly seenBySession;
54
+ constructor(memoryService: MemoryService, metrics?: Metrics | null, ledger?: InjectionLedger | null);
55
+ /**
56
+ * v0.4.0 (K4-017) — reset the per-session seen-set when a session is
57
+ * created (plan §5.1 rule 3). Wired from the `session.created` event.
58
+ */
59
+ onSessionCreated(sessionId: string): void;
26
60
  /**
27
61
  * v0.3.0 (K3-020) — notify the injector that the negative feedback half
28
62
  * fired N times in the last session.idle. The next system.transform or
29
63
  * compacting hook will prepend a HITL suggestion block.
64
+ * v0.4.0 (K4-016) — the session id enables the concrete suggestion:
65
+ * the most-recurred fingerprint's pattern + its fix_args.
30
66
  */
31
- setRecurrences(count: number): void;
67
+ setRecurrences(count: number, sessionId?: string): void;
32
68
  /**
33
- * v0.3.0 (K3-020) — generate a HITL suggestion block when recurrences
34
- * occurred. Returns the block or empty string. Resets the counter.
69
+ * v0.4.0 (K4-016) — generate a CONCRETE HITL suggestion block when
70
+ * recurrences occurred (plan §5.5): names the most-recurred pattern,
71
+ * its exact recurrence count, observed fix_args and confidence. The
72
+ * AGENTS.md draft line derives from the lesson's `Suggestion:` text —
73
+ * never a canned string, and never the anonymous "the same error
74
+ * pattern" without naming the pattern and count.
75
+ *
76
+ * BUG-012 — emits AT MOST ONCE per session: calling it resets the
77
+ * pending recurrence signal, so whichever hook (system.transform or
78
+ * compacting) runs first in a session consumes the block. Documented
79
+ * behavior; wire index.ts to match.
35
80
  */
36
81
  generateSuggestion(): string;
82
+ /**
83
+ * v0.4.0 (K4-016) — the AGENTS.md draft line derives from the lesson's
84
+ * `Suggestion:` text (kept verbatim in the pattern's `Original:` block).
85
+ */
86
+ private agentsDraftLine;
37
87
  /**
38
88
  * v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
39
89
  * to `MemoryService.getRelevant()` (K2-023, D2-13). The injector does
@@ -42,8 +92,106 @@ export declare class ContextInjector {
42
92
  * callers. Plan §B6.5: "apply the same multiplier as
43
93
  * MemoryService.recall so reflector lessons outrank agent-saved notes
44
94
  * at injection time" — satisfied transitively via the getRelevant call.
95
+ *
96
+ * v0.4.0 (K4-017): after retrieval, every memory is filtered through
97
+ * `QualityGate.canInject` (session seen-set + recurrence + strength),
98
+ * and each admitted memory is recorded in the `InjectionLedger`
99
+ * (plan §5.2 — one row per injected memory).
100
+ *
101
+ * v0.5.0 (K5-014 / plan §8.10) — the pipeline is decomposed into
102
+ * `fetchSlice` (ranked retrieval + budget overflow) + `evaluate`
103
+ * (pure gate verdicts) + this orchestrator, so the read-only `plan()`
104
+ * can mirror it without any side effect (D5-08).
45
105
  */
46
106
  private inject;
107
+ /**
108
+ * v0.5.0 (K5-014 / plan §8.10) — the ranked-retrieval stage shared by
109
+ * `inject` and `plan`. With `dry = true` it is a strict read:
110
+ *
111
+ * - the probe fetch never bumps (BUG-016), like the live path;
112
+ * - the overflow retry ALSO fetches with `bump: false` (the live path
113
+ * lets the retry fetch bump once — that is the only difference);
114
+ * - the no-retry bump is skipped.
115
+ *
116
+ * This is what lets `plan()` predict the EXACT slice the live path
117
+ * would inject without mutating a single relevance score.
118
+ */
119
+ private fetchSlice;
120
+ /**
121
+ * v0.5.0 (K5-014 / plan §8.10, D5-08) — PUBLIC read-only prediction of
122
+ * what `inject` WOULD do for a query: same retrieval, same gate, zero
123
+ * side effects. Never moves a counter, never writes the seen-set, never
124
+ * bumps relevance, never records ledger rows. `kevin_trace` (K5-015)
125
+ * surfaces this to the agent; tests freeze the clock + settings around
126
+ * it.
127
+ */
128
+ plan(query: string, options?: {
129
+ tag?: "context" | "memory";
130
+ cap?: number;
131
+ sessionId?: string;
132
+ }): InjectionPlan;
133
+ /**
134
+ * v0.5.0 (K5-014 / plan §8.10) — PURE gate evaluation shared by `admit`
135
+ * (live path) and `plan` (read-only path): returns the verdict for every
136
+ * candidate plus the seen-set as it WOULD look afterwards. Never writes
137
+ * state — the caller decides whether to persist.
138
+ */
139
+ private evaluate;
140
+ /**
141
+ * v0.5.0 (K5-017 / plan §8.11, D5-11) — the effective pre-prompt cap,
142
+ * read at call time from `pre_prompt_budget_tokens` (seeded "900" by
143
+ * migration 006). Clamped to [100, 4000]; a non-numeric value falls
144
+ * back to 900. `kevin_trace` reports the value used via `plan().cap`.
145
+ */
146
+ private prePromptCap;
147
+ /**
148
+ * v0.4.0 (K4-017) — QualityGate admission: filters the ranked slice to
149
+ * memories that may be injected this session, updating the session
150
+ * seen-set.
151
+ *
152
+ * v0.5.0 (K5-007 / plan §5.2, D5-04) — uses `canInjectVerdict` and
153
+ * increments the matching `injections_blocked_*` counter for every
154
+ * rejection, so gate policy becomes measurable. When `dryRun === true`
155
+ * the counters stay untouched (D5-08) — and so does the seen-set
156
+ * (a dry run is a strict read, K5-014).
157
+ *
158
+ * BUG-005 — strength/actionability now go through the REAL
159
+ * `QualityGate.evaluate` semantics (plan §5.1 rules 1-2), which this
160
+ * class had only re-derived from `metadata.dispatch`:
161
+ * - dispatched code → strong + actionable (rescued errorType);
162
+ * - generic fallback suggestion + no code → weak, NOT actionable
163
+ * (the generic-suggestion ban is now enforced even for legacy
164
+ * lessons without dispatch metadata, as long as the `Suggestion:`
165
+ * text is available);
166
+ * - specific suggestion without code → strong + actionable;
167
+ * - no `Suggestion:` text at all (agent-saved note) → strong +
168
+ * actionable — the agent explicitly asked to remember it;
169
+ * - causal patterns (type='pattern') → always strong + actionable:
170
+ * they are the FIXED form of a fingerprint (K4-025).
171
+ */
172
+ private admit;
173
+ /**
174
+ * BUG-005 — the single source of truth for a memory's lesson quality,
175
+ * shared by `admit` (the gate) and `format` (the K4-023 `(low
176
+ * confidence)` marker). Routes lessons through `QualityGate.evaluate`
177
+ * — the production call site that was previously missing.
178
+ */
179
+ private lessonQuality;
180
+ /**
181
+ * v0.4.0 (K4-017) — one ledger row per admitted memory (plan §5.2).
182
+ * Token attribution uses the memory's share of the final block.
183
+ */
184
+ private recordInjections;
185
+ /**
186
+ * v0.4.0 (K4-012) — snippet injection payload (plan §5.1 rule 5,
187
+ * D4-05): rows show `id:` + first 2 non-empty lines + `<protect>`
188
+ * instead of the full body. Gated by the `lesson_snippet_injection`
189
+ * setting (default `'1'`); when `'0'`, full content is restored.
190
+ * `escapeInjectedText` is applied to snippet content by the formatter.
191
+ * v0.4.0 (K4-023) — weak lessons admitted in debug mode are flagged so
192
+ * the formatter renders the `(low confidence)` marker.
193
+ */
194
+ private format;
47
195
  deriveQuery(messages: ChatMessage[]): string;
48
196
  onSystemTransform(input: SystemTransformInput, output: SystemTransformOutput): void;
49
197
  onCompacting(input: CompactingInput, output: CompactingOutput): void;