@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
@@ -0,0 +1,137 @@
1
+ import { uuidv7 } from "./uuid.js";
2
+ const POSITIVE_VERDICTS = ["useful"];
3
+ const NEGATIVE_VERDICTS = [
4
+ "wrong",
5
+ "outdated",
6
+ "ignore",
7
+ ];
8
+ // v0.5.0 — pre-006 DBs lack `memory_feedback`; record() fails loudly with a
9
+ // descriptive error instead of a bare "no such table" (the tool layer turns
10
+ // it into a graceful message).
11
+ const feedbackTableCache = new WeakMap();
12
+ function hasFeedbackTable(store) {
13
+ const cached = feedbackTableCache.get(store);
14
+ if (cached !== undefined)
15
+ return cached;
16
+ try {
17
+ store.prepare("SELECT COUNT(*) FROM memory_feedback").get();
18
+ feedbackTableCache.set(store, true);
19
+ return true;
20
+ }
21
+ catch {
22
+ feedbackTableCache.set(store, false);
23
+ return false;
24
+ }
25
+ }
26
+ export class Feedback {
27
+ store;
28
+ metrics;
29
+ now;
30
+ constructor(store, metrics, now = () => new Date()) {
31
+ this.store = store;
32
+ this.metrics = metrics ?? null;
33
+ this.now = now;
34
+ }
35
+ /**
36
+ * Records one human verdict and folds it into the memory's counters.
37
+ * Counters are RECOMPUTED from the table (never incremented) so rows
38
+ * deleted in tests or by hand cannot drift them.
39
+ *
40
+ * The `ignore` verdict also stamps `memories.ignored = 1` (D5-07).
41
+ * Returns the feedback row id.
42
+ */
43
+ record(input) {
44
+ if (!hasFeedbackTable(this.store)) {
45
+ throw new Error("kevin_feedback requires migration 006_v05_glassbox.sql (missing table memory_feedback)");
46
+ }
47
+ const id = uuidv7();
48
+ this.store
49
+ .prepare(`INSERT INTO memory_feedback
50
+ (id, memory_id, verdict, session_id, note)
51
+ VALUES (?, ?, ?, ?, ?)`)
52
+ .run(id, input.memoryId, input.verdict, input.sessionId ?? null, input.note ?? null);
53
+ this.recomputeCounters(input.memoryId);
54
+ if (input.verdict === "ignore") {
55
+ this.store
56
+ .prepare("UPDATE memories SET ignored = 1 WHERE id = ?")
57
+ .run(input.memoryId);
58
+ }
59
+ // v0.5.0 (K5-023 / plan §5.3, D5-06) — lifecycle action of a
60
+ // negative verdict. `wrong` is an opinion and deserves a second
61
+ // opinion: it demotes only at feedback_negative >= 2. `outdated`
62
+ // is a self-verifying claim about the world and acts at once.
63
+ // `useful` confirms the memory and refreshes its verification.
64
+ if (input.verdict === "outdated") {
65
+ this.store
66
+ .prepare("UPDATE memories SET status = 'stale' WHERE id = ?")
67
+ .run(input.memoryId);
68
+ }
69
+ else if (input.verdict === "wrong") {
70
+ const counts = this.countsFor(input.memoryId);
71
+ if (counts.negative >= 2) {
72
+ this.store
73
+ .prepare("UPDATE memories SET status = 'stale' WHERE id = ?")
74
+ .run(input.memoryId);
75
+ }
76
+ }
77
+ else if (input.verdict === "useful") {
78
+ this.store
79
+ .prepare("UPDATE memories SET last_verified_at = datetime('now') WHERE id = ?")
80
+ .run(input.memoryId);
81
+ }
82
+ if (POSITIVE_VERDICTS.includes(input.verdict)) {
83
+ this.metrics?.incr("feedback_positive_total", 1);
84
+ }
85
+ else if (NEGATIVE_VERDICTS.includes(input.verdict)) {
86
+ this.metrics?.incr("feedback_negative_total", 1);
87
+ }
88
+ return id;
89
+ }
90
+ /** Human-judgement counters for one memory (from the row, not the table). */
91
+ countsFor(memoryId) {
92
+ const row = this.store
93
+ .prepare("SELECT feedback_positive, feedback_negative FROM memories WHERE id = ?")
94
+ .get(memoryId);
95
+ if (!row)
96
+ return { positive: 0, negative: 0 };
97
+ return { positive: row.feedback_positive, negative: row.feedback_negative };
98
+ }
99
+ /**
100
+ * Raw verdict history, newest first. Used by `kevin_feedback` (K5-011)
101
+ * and by the audit tool (K5-016).
102
+ */
103
+ list(memoryId, limit = 50) {
104
+ const rows = this.store
105
+ .prepare(`SELECT id, memory_id, verdict, session_id, note, created_at
106
+ FROM memory_feedback
107
+ ${memoryId ? "WHERE memory_id = ?" : ""}
108
+ ORDER BY created_at DESC, rowid DESC
109
+ LIMIT ?`)
110
+ .all(...(memoryId ? [memoryId, limit] : [limit]));
111
+ return rows.map((r) => ({
112
+ id: r.id,
113
+ memoryId: r.memory_id,
114
+ verdict: r.verdict,
115
+ sessionId: r.session_id,
116
+ note: r.note,
117
+ createdAt: r.created_at,
118
+ }));
119
+ }
120
+ /**
121
+ * Recompute a memory's positive/negative counters straight from the
122
+ * verdict table. Kept private — `record` is the only mutation path.
123
+ */
124
+ recomputeCounters(memoryId) {
125
+ this.store
126
+ .prepare(`UPDATE memories SET
127
+ feedback_positive = (
128
+ SELECT COUNT(*) FROM memory_feedback
129
+ WHERE memory_id = ? AND verdict = 'useful'),
130
+ feedback_negative = (
131
+ SELECT COUNT(*) FROM memory_feedback
132
+ WHERE memory_id = ? AND verdict IN ('wrong','outdated','ignore'))
133
+ WHERE id = ?`)
134
+ .run(memoryId, memoryId, memoryId);
135
+ }
136
+ }
137
+ //# sourceMappingURL=Feedback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Feedback.js","sourceRoot":"","sources":["../../plugin/Feedback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
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" | "inconclusive";
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` (monotonically — an older row can never
64
+ * regress a newer injection's timestamp; the column means "most
65
+ * recent injection").
66
+ */
67
+ settle(sessionId: string): void;
68
+ /**
69
+ * Per-fingerprint failing tool-call counts for the session. Feeds
70
+ * QualityGate.canInject and the HITL suggestion block.
71
+ */
72
+ recurrencesFor(sessionId: string): Map<string, number>;
73
+ /**
74
+ * v0.4.0 (K4-017) — recurrence counts for the QualityGate at
75
+ * injection time: only failing calls that happened AFTER the
76
+ * fingerprint was already injected this session count. The failure
77
+ * that *created* a lesson is not a recurrence — it precedes any
78
+ * injection (plan §5.1 rule 4, same `ts >= injected_at` semantics
79
+ * `settle` uses).
80
+ */
81
+ postInjectionRecurrencesFor(sessionId: string): Map<string, number>;
82
+ /** Number of rows for the session not yet settled (drives tests and settle). */
83
+ unsettledForSession(sessionId: string): number;
84
+ /** Latest ledger rows for a session, newest first (used by tests/tools). */
85
+ rowsForSession(sessionId: string): InjectionRow[];
86
+ /**
87
+ * v0.5.0 (K5-005 / plan §8.4) — one grouped rollup over the whole
88
+ * ledger, zero-filled for every outcome. Consumed by `kevin_audit`.
89
+ */
90
+ outcomeCounts(): Record<InjectionOutcome, number>;
91
+ }
92
+ export {};
@@ -0,0 +1,243 @@
1
+ import { uuidv7 } from "./uuid.js";
2
+ export class InjectionLedger {
3
+ store;
4
+ metrics;
5
+ constructor(store, metrics) {
6
+ this.store = store;
7
+ this.metrics = metrics ?? null;
8
+ }
9
+ /**
10
+ * Records one injected memory. Idempotent at the row level (UUID PK);
11
+ * duplicates are expected only via the caller's per-session seen-set.
12
+ */
13
+ record(input) {
14
+ this.store
15
+ .prepare(`INSERT INTO kevin_injections
16
+ (id, memory_id, fingerprint, session_id, hook, tokens, outcome)
17
+ VALUES (?, ?, ?, ?, ?, ?, 'unmeasured')`)
18
+ .run(uuidv7(), input.memoryId, input.fingerprint, input.sessionId, input.hook, input.tokens);
19
+ this.metrics?.incr("injections_total", 1);
20
+ }
21
+ /**
22
+ * Settles every unmeasured injection of the session: a fingerprint that
23
+ * failed again (as a failing tool_call) after the injection is
24
+ * `ineffective`, otherwise `effective`. Idempotent — only
25
+ * `outcome = 'unmeasured'` rows are flipped, and the recurrence charge
26
+ * is `MAX(recurrence_count, n)` where n = all failing calls of the
27
+ * fingerprint after `injected_at` (a later idle re-computes n and
28
+ * catches up — plan §5.1 rule 4: 3 recurrences → stale).
29
+ *
30
+ * An ineffective injection also bumps the target memory's
31
+ * `recurrence_count` (negative evidence, plan §5.3) and stamps
32
+ * `last_injected_at` (monotonically — an older row can never
33
+ * regress a newer injection's timestamp; the column means "most
34
+ * recent injection").
35
+ */
36
+ settle(sessionId) {
37
+ const injections = this.store
38
+ .prepare(`SELECT id, memory_id, fingerprint, injected_at, outcome
39
+ FROM kevin_injections
40
+ WHERE session_id = ?
41
+ ORDER BY injected_at ASC, id ASC`)
42
+ .all(sessionId);
43
+ for (const inj of injections) {
44
+ // Same identity dimension CausalChain uses: the failing call's
45
+ // `error_fingerprint` (stamped by Reflector) or the legacy
46
+ // `fingerprint` hash. `ts` and `injected_at` are both
47
+ // `datetime('now')` text → lexicographic comparison is valid.
48
+ // COUNT (not LIMIT 1): every failing call after the injection
49
+ // is a recurrence — the charge must reach 3 so D4-06 expels
50
+ // the lesson.
51
+ //
52
+ // BUG-003 — the exemption is now bounded to the lesson's OWN
53
+ // creating call (memories.metadata.origin_call_id, stamped by
54
+ // Reflector). The old code excluded the session's FIRST failing
55
+ // call of the fingerprint, which is only the creating call when
56
+ // the lesson was born in THIS session; a lesson created in an
57
+ // earlier session had its first in-session failure (a genuine
58
+ // post-injection recurrence) wrongly exempted, inflating
59
+ // precision. Memories without a tracked creating call (agent-
60
+ // saved, test fixtures) get no exemption: only the `ts >=
61
+ // injected_at` bound applies.
62
+ const originCallId = readOriginCallId(this.store, inj.memory_id);
63
+ const countRow = this.store
64
+ .prepare(`SELECT COUNT(*) AS n FROM tool_calls
65
+ WHERE session_id = ?
66
+ AND success = 0
67
+ AND COALESCE(error_fingerprint, fingerprint) = ?
68
+ AND ts >= ?
69
+ AND (? IS NULL OR id != ?)
70
+ LIMIT 1`)
71
+ .get(sessionId, inj.fingerprint, inj.injected_at, originCallId, originCallId);
72
+ const n = countRow.n;
73
+ // v0.5.0 (K5-005 / plan §5.1, D5-01) — three-way settlement:
74
+ // recurrences >= 1 → ineffective (existing side effects unchanged)
75
+ // else fixes >= 1 → effective (a linked fix was OBSERVED)
76
+ // else → inconclusive (new majority bucket; excluded
77
+ // from the precision denominator)
78
+ if (n >= 1) {
79
+ if (inj.outcome === "unmeasured") {
80
+ this.store
81
+ .prepare(`UPDATE kevin_injections SET outcome = 'ineffective'
82
+ WHERE id = ?`)
83
+ .run(inj.id);
84
+ this.metrics?.incr("injections_ineffective", 1);
85
+ }
86
+ this.store
87
+ .prepare(`UPDATE memories
88
+ SET recurrence_count = MAX(recurrence_count, ?),
89
+ last_injected_at = CASE
90
+ WHEN last_injected_at IS NULL
91
+ OR ? > last_injected_at
92
+ THEN ? ELSE last_injected_at END
93
+ WHERE fingerprint = ? AND id = ?`)
94
+ .run(countRow.n, inj.injected_at, inj.injected_at, inj.fingerprint, inj.memory_id);
95
+ // v0.4.0 (K4-025 / plan §5.1 rule 4, D4-06) — recurrence
96
+ // expels: a fingerprint at `recurrence_count >= 3` is
97
+ // demoted to `status='stale'` and never injected again
98
+ // (only a new causal pattern — from a linked fix —
99
+ // re-admits the lesson, not the stale error row).
100
+ this.store
101
+ .prepare(`UPDATE memories SET status = 'stale'
102
+ WHERE id = ? AND recurrence_count >= 3`)
103
+ .run(inj.memory_id);
104
+ }
105
+ else if (inj.outcome === "unmeasured") {
106
+ // Mirror of the recurrence predicate with the success flag
107
+ // inverted and the fingerprint matched on
108
+ // `fix_for_fingerprint` (populated by CausalChain.onSuccess,
109
+ // indexed by idx_tool_calls_fix_fp since migration 004). The
110
+ // `ts >= injected_at` bound and `session_id = ?` filter are
111
+ // kept; there is no `origin_call_id` exemption for fixes —
112
+ // a fix is not the creating call.
113
+ const fixRow = this.store
114
+ .prepare(`SELECT COUNT(*) AS m FROM tool_calls
115
+ WHERE session_id = ?
116
+ AND success = 1
117
+ AND fix_for_fingerprint = ?
118
+ AND ts >= ?
119
+ LIMIT 1`)
120
+ .get(sessionId, inj.fingerprint, inj.injected_at);
121
+ if (fixRow.m >= 1) {
122
+ this.store
123
+ .prepare(`UPDATE kevin_injections SET outcome = 'effective'
124
+ WHERE id = ?`)
125
+ .run(inj.id);
126
+ this.metrics?.incr("injections_effective", 1);
127
+ }
128
+ else {
129
+ this.store
130
+ .prepare(`UPDATE kevin_injections SET outcome = 'inconclusive'
131
+ WHERE id = ?`)
132
+ .run(inj.id);
133
+ this.metrics?.incr("injections_inconclusive", 1);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ /**
139
+ * Per-fingerprint failing tool-call counts for the session. Feeds
140
+ * QualityGate.canInject and the HITL suggestion block.
141
+ */
142
+ recurrencesFor(sessionId) {
143
+ const rows = this.store
144
+ .prepare(`SELECT COALESCE(error_fingerprint, fingerprint) AS fp
145
+ FROM tool_calls
146
+ WHERE session_id = ? AND success = 0
147
+ AND (error_fingerprint IS NOT NULL OR fingerprint IS NOT NULL)`)
148
+ .all(sessionId);
149
+ const out = new Map();
150
+ for (const r of rows) {
151
+ if (!r.fp)
152
+ continue;
153
+ out.set(r.fp, (out.get(r.fp) ?? 0) + 1);
154
+ }
155
+ return out;
156
+ }
157
+ /**
158
+ * v0.4.0 (K4-017) — recurrence counts for the QualityGate at
159
+ * injection time: only failing calls that happened AFTER the
160
+ * fingerprint was already injected this session count. The failure
161
+ * that *created* a lesson is not a recurrence — it precedes any
162
+ * injection (plan §5.1 rule 4, same `ts >= injected_at` semantics
163
+ * `settle` uses).
164
+ */
165
+ postInjectionRecurrencesFor(sessionId) {
166
+ const rows = this.store
167
+ .prepare(`SELECT COALESCE(t.error_fingerprint, t.fingerprint) AS fp
168
+ FROM tool_calls t
169
+ WHERE t.session_id = ? AND t.success = 0
170
+ AND (t.error_fingerprint IS NOT NULL OR t.fingerprint IS NOT NULL)
171
+ AND t.ts >= COALESCE(
172
+ (SELECT MAX(injected_at) FROM kevin_injections
173
+ WHERE session_id = t.session_id
174
+ AND fingerprint = COALESCE(t.error_fingerprint, t.fingerprint)),
175
+ '9999-12-31')`)
176
+ .all(sessionId);
177
+ const out = new Map();
178
+ for (const r of rows) {
179
+ if (!r.fp)
180
+ continue;
181
+ out.set(r.fp, (out.get(r.fp) ?? 0) + 1);
182
+ }
183
+ return out;
184
+ }
185
+ /** Number of rows for the session not yet settled (drives tests and settle). */
186
+ unsettledForSession(sessionId) {
187
+ const row = this.store
188
+ .prepare(`SELECT COUNT(*) AS n FROM kevin_injections
189
+ WHERE session_id = ? AND outcome = 'unmeasured'`)
190
+ .get(sessionId);
191
+ return row.n;
192
+ }
193
+ /** Latest ledger rows for a session, newest first (used by tests/tools). */
194
+ rowsForSession(sessionId) {
195
+ return this.store
196
+ .prepare(`SELECT id, memory_id, fingerprint, session_id, hook, tokens,
197
+ injected_at, outcome
198
+ FROM kevin_injections
199
+ WHERE session_id = ?
200
+ ORDER BY injected_at ASC, id ASC`)
201
+ .all(sessionId);
202
+ }
203
+ /**
204
+ * v0.5.0 (K5-005 / plan §8.4) — one grouped rollup over the whole
205
+ * ledger, zero-filled for every outcome. Consumed by `kevin_audit`.
206
+ */
207
+ outcomeCounts() {
208
+ const rows = this.store
209
+ .prepare("SELECT outcome, COUNT(*) AS n FROM kevin_injections GROUP BY outcome")
210
+ .all();
211
+ const out = {
212
+ unmeasured: 0,
213
+ effective: 0,
214
+ ineffective: 0,
215
+ inconclusive: 0,
216
+ };
217
+ for (const r of rows) {
218
+ out[r.outcome] = r.n;
219
+ }
220
+ return out;
221
+ }
222
+ }
223
+ /**
224
+ * BUG-003 — read `origin_call_id` (the failing tool_call that CREATED the
225
+ * memory) from memories.metadata, mirroring the feedback loop's
226
+ * `readOriginCallId` in MemoryService. Returns null when absent/malformed.
227
+ */
228
+ function readOriginCallId(store, memoryId) {
229
+ const row = store
230
+ .prepare("SELECT metadata FROM memories WHERE id = ?")
231
+ .get(memoryId);
232
+ if (!row?.metadata)
233
+ return null;
234
+ try {
235
+ const parsed = JSON.parse(row.metadata);
236
+ const id = parsed?.origin_call_id;
237
+ return typeof id === "string" && id.length > 0 ? id : null;
238
+ }
239
+ catch {
240
+ return null;
241
+ }
242
+ }
243
+ //# sourceMappingURL=InjectionLedger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InjectionLedger.js","sourceRoot":"","sources":["../../plugin/InjectionLedger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * v0.4.0 (K4-014) — Deterministic fix capture (plan §5.4 / D4-07).
3
+ *
4
+ * Zero-cost "Fixed by:" raw material from local data: when CausalChain
5
+ * links a success to a failing fingerprint, the linked tool_call's
6
+ * `args_summary` becomes a deterministic fix string stored in
7
+ * `memories.fix_args`. The default promotion path is pure local data —
8
+ * no LLM calls (principle 14).
9
+ */
10
+ /** Max length of the `args_summary` embedded in a fix_args string. */
11
+ export declare const FIX_ARGS_TRUNCATE = 120;
12
+ export interface LinkedToolCall {
13
+ tool: string;
14
+ args_summary: string | null;
15
+ }
16
+ /**
17
+ * `"bash" with args "npm i -g rg"` style. Returns null when the linked
18
+ * call carries no args_summary (nothing to say).
19
+ */
20
+ export declare function extractFixArgs(call: LinkedToolCall): string | null;
21
+ export interface EnrichInput {
22
+ lesson: string;
23
+ fixArgs: string | null;
24
+ originalError: string | null;
25
+ }
26
+ /**
27
+ * Opt-in LLM phrasing hook (K4-015): receives the lesson, the
28
+ * deterministic fix_args and the original error; returns a one-line
29
+ * `Fix:` phrasing, or null to fall back to the deterministic text.
30
+ * May be async; never runs on the failure hot path.
31
+ */
32
+ export type EnrichFn = (input: EnrichInput) => Promise<string | null>;
33
+ export interface PatternLike {
34
+ content: string;
35
+ fixArgs: string | null;
36
+ }
37
+ /** Deterministic `Fixed by: {fix_args}` line (or "" when there is none). */
38
+ export declare function deterministicFixLine(pattern: PatternLike): string;
39
+ /**
40
+ * Promotion-time fix phrasing. Default path returns the deterministic
41
+ * `Fixed by: {fix_args}` line (or "" when there is none). When an
42
+ * `enrichFn` is supplied and returns a phrase, that phrase wins.
43
+ */
44
+ export declare function enrichAtPromotion(pattern: PatternLike, enrichFn?: EnrichFn): Promise<string>;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * v0.4.0 (K4-014) — Deterministic fix capture (plan §5.4 / D4-07).
3
+ *
4
+ * Zero-cost "Fixed by:" raw material from local data: when CausalChain
5
+ * links a success to a failing fingerprint, the linked tool_call's
6
+ * `args_summary` becomes a deterministic fix string stored in
7
+ * `memories.fix_args`. The default promotion path is pure local data —
8
+ * no LLM calls (principle 14).
9
+ */
10
+ /** Max length of the `args_summary` embedded in a fix_args string. */
11
+ export const FIX_ARGS_TRUNCATE = 120;
12
+ /**
13
+ * `"bash" with args "npm i -g rg"` style. Returns null when the linked
14
+ * call carries no args_summary (nothing to say).
15
+ */
16
+ export function extractFixArgs(call) {
17
+ const raw = call.args_summary?.trim();
18
+ if (!raw)
19
+ return null;
20
+ const truncated = raw.length > FIX_ARGS_TRUNCATE
21
+ ? `${raw.slice(0, FIX_ARGS_TRUNCATE)}…`
22
+ : raw;
23
+ return `${call.tool} with args "${truncated}"`;
24
+ }
25
+ /** Deterministic `Fixed by: {fix_args}` line (or "" when there is none). */
26
+ export function deterministicFixLine(pattern) {
27
+ return pattern.fixArgs ? `Fixed by: ${pattern.fixArgs}` : "";
28
+ }
29
+ /**
30
+ * Promotion-time fix phrasing. Default path returns the deterministic
31
+ * `Fixed by: {fix_args}` line (or "" when there is none). When an
32
+ * `enrichFn` is supplied and returns a phrase, that phrase wins.
33
+ */
34
+ export async function enrichAtPromotion(pattern, enrichFn) {
35
+ if (enrichFn) {
36
+ const phrased = await enrichFn({
37
+ lesson: pattern.content,
38
+ fixArgs: pattern.fixArgs,
39
+ originalError: null,
40
+ });
41
+ if (phrased)
42
+ return phrased;
43
+ }
44
+ return deterministicFixLine(pattern);
45
+ }
46
+ //# sourceMappingURL=LessonFixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LessonFixer.js","sourceRoot":"","sources":["../../plugin/LessonFixer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,sEAAsE;AACtE,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAOrC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAoB;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IACtC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,SAAS,GACd,GAAG,CAAC,MAAM,GAAG,iBAAiB;QAC7B,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,GAAG;QACvC,CAAC,CAAC,GAAG,CAAC;IACR,OAAO,GAAG,IAAI,CAAC,IAAI,eAAe,SAAS,GAAG,CAAC;AAChD,CAAC;AAqBD,4EAA4E;AAC5E,MAAM,UAAU,oBAAoB,CAAC,OAAoB;IACxD,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,OAAoB,EACpB,QAAmB;IAEnB,IAAI,QAAQ,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC;YAC9B,MAAM,EAAE,OAAO,CAAC,OAAO;YACvB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,aAAa,EAAE,IAAI;SACnB,CAAC,CAAC;QACH,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC7B,CAAC;IACD,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC"}