@jmtrin/opencode-kevin 1.1.0 → 1.3.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 (131) hide show
  1. package/dist/plugin/capabilities.d.ts +2 -0
  2. package/dist/plugin/capabilities.js +3 -0
  3. package/dist/plugin/index.d.ts +5 -4
  4. package/dist/plugin/index.js +401 -108
  5. package/dist/plugin/native.d.ts +2 -2
  6. package/dist/plugin/native.js +2 -2
  7. package/package.json +56 -63
  8. package/LICENSE +0 -21
  9. package/README.md +0 -576
  10. package/dist/migrations/001_initial.sql +0 -92
  11. package/dist/migrations/002_indexes.sql +0 -14
  12. package/dist/migrations/003_v02_signal.sql +0 -58
  13. package/dist/migrations/004_v03_knowledge.sql +0 -138
  14. package/dist/migrations/005_v04_signal.sql +0 -57
  15. package/dist/migrations/006_v05_glassbox.sql +0 -118
  16. package/dist/migrations/007_v06_pull.sql +0 -145
  17. package/dist/migrations/008_v07_truth.sql +0 -124
  18. package/dist/migrations/009_v08_team.sql +0 -100
  19. package/dist/migrations/010_v09_native.sql +0 -78
  20. package/dist/migrations/011_v10_proven.sql +0 -69
  21. package/dist/migrations/012_v11_drift.sql +0 -24
  22. package/dist/plugin/Archiver.d.ts +0 -42
  23. package/dist/plugin/Archiver.js +0 -83
  24. package/dist/plugin/ArtifactWriter.d.ts +0 -93
  25. package/dist/plugin/ArtifactWriter.js +0 -291
  26. package/dist/plugin/CausalChain.d.ts +0 -22
  27. package/dist/plugin/CausalChain.js +0 -197
  28. package/dist/plugin/ConflictDetector.d.ts +0 -35
  29. package/dist/plugin/ConflictDetector.js +0 -260
  30. package/dist/plugin/ContextInjector.d.ts +0 -222
  31. package/dist/plugin/ContextInjector.js +0 -533
  32. package/dist/plugin/ConventionMiner.d.ts +0 -35
  33. package/dist/plugin/ConventionMiner.js +0 -242
  34. package/dist/plugin/Curator.d.ts +0 -116
  35. package/dist/plugin/Curator.js +0 -324
  36. package/dist/plugin/Feedback.d.ts +0 -67
  37. package/dist/plugin/Feedback.js +0 -120
  38. package/dist/plugin/HookLiveness.d.ts +0 -88
  39. package/dist/plugin/HookLiveness.js +0 -322
  40. package/dist/plugin/InjectionLedger.d.ts +0 -98
  41. package/dist/plugin/InjectionLedger.js +0 -295
  42. package/dist/plugin/LessonFixer.d.ts +0 -44
  43. package/dist/plugin/LessonFixer.js +0 -45
  44. package/dist/plugin/Materializer.d.ts +0 -83
  45. package/dist/plugin/Materializer.js +0 -181
  46. package/dist/plugin/MemoryService.d.ts +0 -360
  47. package/dist/plugin/MemoryService.js +0 -1242
  48. package/dist/plugin/Migrate.d.ts +0 -17
  49. package/dist/plugin/Migrate.js +0 -271
  50. package/dist/plugin/PatternMiner.d.ts +0 -49
  51. package/dist/plugin/PatternMiner.js +0 -132
  52. package/dist/plugin/QualityGate.d.ts +0 -124
  53. package/dist/plugin/QualityGate.js +0 -119
  54. package/dist/plugin/Reflector.d.ts +0 -91
  55. package/dist/plugin/Reflector.js +0 -307
  56. package/dist/plugin/RepoIdentity.d.ts +0 -124
  57. package/dist/plugin/RepoIdentity.js +0 -300
  58. package/dist/plugin/RepoTruth.d.ts +0 -80
  59. package/dist/plugin/RepoTruth.js +0 -599
  60. package/dist/plugin/Retrospective.d.ts +0 -17
  61. package/dist/plugin/Retrospective.js +0 -213
  62. package/dist/plugin/SharedLayer.d.ts +0 -159
  63. package/dist/plugin/SharedLayer.js +0 -462
  64. package/dist/plugin/Store.d.ts +0 -29
  65. package/dist/plugin/Store.js +0 -50
  66. package/dist/plugin/ToolCallObserver.d.ts +0 -32
  67. package/dist/plugin/ToolCallObserver.js +0 -201
  68. package/dist/plugin/columns.d.ts +0 -11
  69. package/dist/plugin/columns.js +0 -54
  70. package/dist/plugin/confidence.d.ts +0 -8
  71. package/dist/plugin/confidence.js +0 -34
  72. package/dist/plugin/contract.d.ts +0 -54
  73. package/dist/plugin/contract.js +0 -383
  74. package/dist/plugin/diff.d.ts +0 -8
  75. package/dist/plugin/diff.js +0 -182
  76. package/dist/plugin/escape.d.ts +0 -39
  77. package/dist/plugin/escape.js +0 -60
  78. package/dist/plugin/fingerprint.d.ts +0 -27
  79. package/dist/plugin/fingerprint.js +0 -73
  80. package/dist/plugin/inferability.d.ts +0 -32
  81. package/dist/plugin/inferability.js +0 -88
  82. package/dist/plugin/kevin_approve.d.ts +0 -34
  83. package/dist/plugin/kevin_approve.js +0 -52
  84. package/dist/plugin/kevin_audit.d.ts +0 -213
  85. package/dist/plugin/kevin_audit.js +0 -563
  86. package/dist/plugin/kevin_bench.d.ts +0 -10
  87. package/dist/plugin/kevin_bench.js +0 -82
  88. package/dist/plugin/kevin_conflicts.d.ts +0 -9
  89. package/dist/plugin/kevin_conflicts.js +0 -50
  90. package/dist/plugin/kevin_contract.d.ts +0 -8
  91. package/dist/plugin/kevin_contract.js +0 -59
  92. package/dist/plugin/kevin_doctor.d.ts +0 -62
  93. package/dist/plugin/kevin_doctor.js +0 -196
  94. package/dist/plugin/kevin_facts.d.ts +0 -42
  95. package/dist/plugin/kevin_facts.js +0 -36
  96. package/dist/plugin/kevin_forget.d.ts +0 -33
  97. package/dist/plugin/kevin_forget.js +0 -260
  98. package/dist/plugin/kevin_native.d.ts +0 -29
  99. package/dist/plugin/kevin_native.js +0 -79
  100. package/dist/plugin/kevin_propose.d.ts +0 -23
  101. package/dist/plugin/kevin_propose.js +0 -14
  102. package/dist/plugin/kevin_publish.d.ts +0 -38
  103. package/dist/plugin/kevin_publish.js +0 -18
  104. package/dist/plugin/kevin_why.d.ts +0 -23
  105. package/dist/plugin/kevin_why.js +0 -111
  106. package/dist/plugin/memory-format.d.ts +0 -20
  107. package/dist/plugin/memory-format.js +0 -59
  108. package/dist/plugin/metrics.d.ts +0 -97
  109. package/dist/plugin/metrics.js +0 -284
  110. package/dist/plugin/okf-export.d.ts +0 -3
  111. package/dist/plugin/okf-export.js +0 -134
  112. package/dist/plugin/okf-import.d.ts +0 -76
  113. package/dist/plugin/okf-import.js +0 -271
  114. package/dist/plugin/okf.d.ts +0 -107
  115. package/dist/plugin/okf.js +0 -303
  116. package/dist/plugin/perf.d.ts +0 -35
  117. package/dist/plugin/perf.js +0 -174
  118. package/dist/plugin/query-tokenizer.d.ts +0 -13
  119. package/dist/plugin/query-tokenizer.js +0 -133
  120. package/dist/plugin/redact.d.ts +0 -2
  121. package/dist/plugin/redact.js +0 -18
  122. package/dist/plugin/replay-types.d.ts +0 -69
  123. package/dist/plugin/replay-types.js +0 -156
  124. package/dist/plugin/replay.d.ts +0 -36
  125. package/dist/plugin/replay.js +0 -202
  126. package/dist/plugin/sqlite-adapter.d.ts +0 -12
  127. package/dist/plugin/sqlite-adapter.js +0 -81
  128. package/dist/plugin/time-ms.d.ts +0 -1
  129. package/dist/plugin/time-ms.js +0 -16
  130. package/dist/plugin/uuid.d.ts +0 -1
  131. package/dist/plugin/uuid.js +0 -50
@@ -1,98 +0,0 @@
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
- * v0.8.0 (K8-024 / plan §5.7) — the memory's layer, passed by the
34
- * caller (the injector already knows it — no lookup on the hot path).
35
- * Drives the `injections_from_shared` counter.
36
- */
37
- layer?: string | null;
38
- }
39
- interface InjectionRow {
40
- id: string;
41
- memory_id: string;
42
- fingerprint: string;
43
- session_id: string;
44
- hook: InjectionHook;
45
- tokens: number;
46
- injected_at: string;
47
- outcome: InjectionOutcome;
48
- }
49
- export declare class InjectionLedger {
50
- private readonly store;
51
- private readonly metrics;
52
- constructor(store: Store, metrics?: Metrics | null);
53
- /**
54
- * Records one injected memory. Idempotent at the row level (UUID PK);
55
- * duplicates are expected only via the caller's per-session seen-set.
56
- */
57
- record(input: InjectionRecordInput): void;
58
- /**
59
- * Settles every unmeasured injection of the session: a fingerprint that
60
- * failed again (as a failing tool_call) after the injection is
61
- * `ineffective`, otherwise `effective`. Idempotent — only
62
- * `outcome = 'unmeasured'` rows are flipped, and the recurrence charge
63
- * is `MAX(recurrence_count, n)` where n = all failing calls of the
64
- * fingerprint after `injected_at` (a later idle re-computes n and
65
- * catches up — plan §5.1 rule 4: 3 recurrences → stale).
66
- *
67
- * An ineffective injection also bumps the target memory's
68
- * `recurrence_count` (negative evidence, plan §5.3) and stamps
69
- * `last_injected_at` (monotonically — an older row can never
70
- * regress a newer injection's timestamp; the column means "most
71
- * recent injection").
72
- */
73
- settle(sessionId: string): void;
74
- /**
75
- * Per-fingerprint failing tool-call counts for the session. Feeds
76
- * QualityGate.canInject and the HITL suggestion block.
77
- */
78
- recurrencesFor(sessionId: string): Map<string, number>;
79
- /**
80
- * v0.4.0 (K4-017) — recurrence counts for the QualityGate at
81
- * injection time: only failing calls that happened AFTER the
82
- * fingerprint was already injected this session count. The failure
83
- * that *created* a lesson is not a recurrence — it precedes any
84
- * injection (plan §5.1 rule 4, same `ts >= injected_at` semantics
85
- * `settle` uses).
86
- */
87
- postInjectionRecurrencesFor(sessionId: string): Map<string, number>;
88
- /** Number of rows for the session not yet settled (drives tests and settle). */
89
- unsettledForSession(sessionId: string): number;
90
- /** Latest ledger rows for a session, newest first (used by tests/tools). */
91
- rowsForSession(sessionId: string): InjectionRow[];
92
- /**
93
- * v0.5.0 (K5-005 / plan §8.4) — one grouped rollup over the whole
94
- * ledger, zero-filled for every outcome. Consumed by `kevin_audit`.
95
- */
96
- outcomeCounts(): Record<InjectionOutcome, number>;
97
- }
98
- export {};
@@ -1,295 +0,0 @@
1
- import { readOriginCallId } from "./MemoryService.js";
2
- import { hasColumn } from "./columns.js";
3
- import { toMs } from "./time-ms.js";
4
- import { uuidv7 } from "./uuid.js";
5
- export class InjectionLedger {
6
- store;
7
- metrics;
8
- constructor(store, metrics) {
9
- this.store = store;
10
- this.metrics = metrics ?? null;
11
- }
12
- /**
13
- * Records one injected memory. Idempotent at the row level (UUID PK);
14
- * duplicates are expected only via the caller's per-session seen-set.
15
- */
16
- record(input) {
17
- // v1.1.0 (K11-003 / plan §5.2, D11-01) — dual-write injected_at_ms when column exists
18
- if (hasColumn(this.store, "kevin_injections", "injected_at_ms")) {
19
- this.store
20
- .prepare(`INSERT INTO kevin_injections
21
- (id, memory_id, fingerprint, session_id, hook, tokens, outcome, injected_at_ms)
22
- VALUES (?, ?, ?, ?, ?, ?, 'unmeasured', ?)`)
23
- .run(uuidv7(), input.memoryId, input.fingerprint, input.sessionId, input.hook, input.tokens, Date.now());
24
- }
25
- else {
26
- this.store
27
- .prepare(`INSERT INTO kevin_injections
28
- (id, memory_id, fingerprint, session_id, hook, tokens, outcome)
29
- VALUES (?, ?, ?, ?, ?, ?, 'unmeasured')`)
30
- .run(uuidv7(), input.memoryId, input.fingerprint, input.sessionId, input.hook, input.tokens);
31
- }
32
- this.metrics?.incr("injections_total", 1);
33
- // v0.8.0 (K8-024 / plan §5.7) — shared-layer consumption is counted
34
- // separately so the audit can tell how much of the push channel
35
- // came from teammates' entries.
36
- if (input.layer === "shared") {
37
- this.metrics?.incr("injections_from_shared", 1);
38
- }
39
- }
40
- /**
41
- * Settles every unmeasured injection of the session: a fingerprint that
42
- * failed again (as a failing tool_call) after the injection is
43
- * `ineffective`, otherwise `effective`. Idempotent — only
44
- * `outcome = 'unmeasured'` rows are flipped, and the recurrence charge
45
- * is `MAX(recurrence_count, n)` where n = all failing calls of the
46
- * fingerprint after `injected_at` (a later idle re-computes n and
47
- * catches up — plan §5.1 rule 4: 3 recurrences → stale).
48
- *
49
- * An ineffective injection also bumps the target memory's
50
- * `recurrence_count` (negative evidence, plan §5.3) and stamps
51
- * `last_injected_at` (monotonically — an older row can never
52
- * regress a newer injection's timestamp; the column means "most
53
- * recent injection").
54
- */
55
- settle(sessionId) {
56
- // v1.1.0 (K11-003 / plan §5.2, D11-01/D11-07) — ms-aware settle: readers
57
- // prefer _ms and fall back to legacy string. Column probes are cached.
58
- const hasInjMs = hasColumn(this.store, "kevin_injections", "injected_at_ms");
59
- const hasToolMs = hasColumn(this.store, "tool_calls", "ts_ms");
60
- const injections = (hasInjMs
61
- ? this.store.prepare(`SELECT id, memory_id, fingerprint, injected_at, injected_at_ms, outcome
62
- FROM kevin_injections
63
- WHERE session_id = ?
64
- ORDER BY injected_at ASC, id ASC`)
65
- : this.store.prepare(`SELECT id, memory_id, fingerprint, injected_at, outcome
66
- FROM kevin_injections
67
- WHERE session_id = ?
68
- ORDER BY injected_at ASC, id ASC`)).all(sessionId);
69
- for (const inj of injections) {
70
- // Same identity dimension CausalChain uses: the failing call's
71
- // `error_fingerprint` (stamped by Reflector) or the legacy
72
- // `fingerprint` hash.
73
- // v1.1.0 — time comparison uses toMs helper (prefers _ms).
74
- //
75
- // BUG-003 — the exemption is now bounded to the lesson's OWN
76
- // creating call (memories.metadata.origin_call_id, stamped by
77
- // Reflector). The old code excluded the session's FIRST failing
78
- // call of the fingerprint, which is only the creating call when
79
- // the lesson was born in THIS session; a lesson created in an
80
- // earlier session had its first in-session failure (a genuine
81
- // post-injection recurrence) wrongly exempted, inflating
82
- // precision. Memories without a tracked creating call (agent-
83
- // saved, test fixtures) get no exemption: only the `ts >=
84
- // injected_at` bound applies.
85
- const metaRow = this.store
86
- .prepare("SELECT metadata FROM memories WHERE id = ?")
87
- .get(inj.memory_id);
88
- const originCallId = readOriginCallId(metaRow?.metadata ?? null);
89
- // v1.1.0 — heuristic: when legacy string and _ms diverge by >2s
90
- // (manual UPDATE of injected_at in tests), trust the string
91
- // because the ms reflects wall time at record, not the pinned
92
- // fixture time. Real rows differ by <1s (second truncation).
93
- const rawInjMs = inj.injected_at_ms ?? null;
94
- const stringMs = inj.injected_at
95
- ? Date.parse(`${inj.injected_at.replace(" ", "T")}Z`)
96
- : null;
97
- const injectedMs = rawInjMs !== null &&
98
- stringMs !== null &&
99
- !Number.isNaN(stringMs) &&
100
- Math.abs(rawInjMs - stringMs) > 2000
101
- ? stringMs
102
- : toMs(inj.injected_at, rawInjMs);
103
- // Fetch candidate failing calls for this fingerprint and filter by ms
104
- const failRows = (hasToolMs
105
- ? this.store.prepare(`SELECT id, ts, ts_ms FROM tool_calls
106
- WHERE session_id = ?
107
- AND success = 0
108
- AND COALESCE(error_fingerprint, fingerprint) = ?`)
109
- : this.store.prepare(`SELECT id, ts FROM tool_calls
110
- WHERE session_id = ?
111
- AND success = 0
112
- AND COALESCE(error_fingerprint, fingerprint) = ?`)).all(sessionId, inj.fingerprint);
113
- let n = 0;
114
- for (const r of failRows) {
115
- if (originCallId !== null && r.id === originCallId)
116
- continue;
117
- const tsMs = toMs(r.ts, r.ts_ms ?? null);
118
- if (injectedMs !== null && tsMs !== null) {
119
- if (tsMs < injectedMs)
120
- continue;
121
- }
122
- else {
123
- // fallback to string comparison when either side missing (legacy)
124
- if (r.ts < inj.injected_at)
125
- continue;
126
- }
127
- n++;
128
- }
129
- // v0.5.0 (K5-005 / plan §5.1, D5-01) — three-way settlement:
130
- // recurrences >= 1 → ineffective (existing side effects unchanged)
131
- // else fixes >= 1 → effective (a linked fix was OBSERVED)
132
- // else → inconclusive (new majority bucket; excluded
133
- // from the precision denominator)
134
- if (n >= 1) {
135
- if (inj.outcome === "unmeasured") {
136
- this.store
137
- .prepare(`UPDATE kevin_injections SET outcome = 'ineffective'
138
- WHERE id = ?`)
139
- .run(inj.id);
140
- this.metrics?.incr("injections_ineffective", 1);
141
- }
142
- this.store
143
- .prepare(`UPDATE memories
144
- SET recurrence_count = MAX(recurrence_count, ?),
145
- last_injected_at = CASE
146
- WHEN last_injected_at IS NULL
147
- OR ? > last_injected_at
148
- THEN ? ELSE last_injected_at END
149
- WHERE fingerprint = ? AND id = ?`)
150
- .run(n, inj.injected_at, inj.injected_at, inj.fingerprint, inj.memory_id);
151
- // v0.4.0 (K4-025 / plan §5.1 rule 4, D4-06) — recurrence
152
- // expels: a fingerprint at `recurrence_count >= 3` is
153
- // demoted to `status='stale'` and never injected again
154
- // (only a new causal pattern — from a linked fix —
155
- // re-admits the lesson, not the stale error row).
156
- this.store
157
- .prepare(`UPDATE memories SET status = 'stale'
158
- WHERE id = ? AND recurrence_count >= 3`)
159
- .run(inj.memory_id);
160
- }
161
- else if (inj.outcome === "unmeasured") {
162
- // Mirror of the recurrence predicate with the success flag
163
- // inverted and the fingerprint matched on
164
- // `fix_for_fingerprint` (populated by CausalChain.onSuccess,
165
- // indexed by idx_tool_calls_fix_fp since migration 004). The
166
- // `ts >= injected_at` bound and `session_id = ?` filter are
167
- // kept; there is no `origin_call_id` exemption for fixes —
168
- // a fix is not the creating call.
169
- // v1.1.0 — ms-aware: fetch and filter via toMs.
170
- const fixCandidates = (hasToolMs
171
- ? this.store.prepare(`SELECT ts, ts_ms FROM tool_calls
172
- WHERE session_id = ?
173
- AND success = 1
174
- AND fix_for_fingerprint = ?`)
175
- : this.store.prepare(`SELECT ts FROM tool_calls
176
- WHERE session_id = ?
177
- AND success = 1
178
- AND fix_for_fingerprint = ?`)).all(sessionId, inj.fingerprint);
179
- let hasFix = false;
180
- for (const fr of fixCandidates) {
181
- const tsMs = toMs(fr.ts, fr.ts_ms ?? null);
182
- if (injectedMs !== null && tsMs !== null) {
183
- if (tsMs >= injectedMs) {
184
- hasFix = true;
185
- break;
186
- }
187
- }
188
- else if (fr.ts >= inj.injected_at) {
189
- hasFix = true;
190
- break;
191
- }
192
- }
193
- if (hasFix) {
194
- this.store
195
- .prepare(`UPDATE kevin_injections SET outcome = 'effective'
196
- WHERE id = ?`)
197
- .run(inj.id);
198
- this.metrics?.incr("injections_effective", 1);
199
- }
200
- else {
201
- this.store
202
- .prepare(`UPDATE kevin_injections SET outcome = 'inconclusive'
203
- WHERE id = ?`)
204
- .run(inj.id);
205
- this.metrics?.incr("injections_inconclusive", 1);
206
- }
207
- }
208
- }
209
- }
210
- /**
211
- * Per-fingerprint failing tool-call counts for the session. Feeds
212
- * QualityGate.canInject and the HITL suggestion block.
213
- */
214
- recurrencesFor(sessionId) {
215
- const rows = this.store
216
- .prepare(`SELECT COALESCE(error_fingerprint, fingerprint) AS fp
217
- FROM tool_calls
218
- WHERE session_id = ? AND success = 0
219
- AND (error_fingerprint IS NOT NULL OR fingerprint IS NOT NULL)`)
220
- .all(sessionId);
221
- const out = new Map();
222
- for (const r of rows) {
223
- if (!r.fp)
224
- continue;
225
- out.set(r.fp, (out.get(r.fp) ?? 0) + 1);
226
- }
227
- return out;
228
- }
229
- /**
230
- * v0.4.0 (K4-017) — recurrence counts for the QualityGate at
231
- * injection time: only failing calls that happened AFTER the
232
- * fingerprint was already injected this session count. The failure
233
- * that *created* a lesson is not a recurrence — it precedes any
234
- * injection (plan §5.1 rule 4, same `ts >= injected_at` semantics
235
- * `settle` uses).
236
- */
237
- postInjectionRecurrencesFor(sessionId) {
238
- const rows = this.store
239
- .prepare(`SELECT COALESCE(t.error_fingerprint, t.fingerprint) AS fp
240
- FROM tool_calls t
241
- WHERE t.session_id = ? AND t.success = 0
242
- AND (t.error_fingerprint IS NOT NULL OR t.fingerprint IS NOT NULL)
243
- AND t.ts >= COALESCE(
244
- (SELECT MAX(injected_at) FROM kevin_injections
245
- WHERE session_id = t.session_id
246
- AND fingerprint = COALESCE(t.error_fingerprint, t.fingerprint)),
247
- '9999-12-31')`)
248
- .all(sessionId);
249
- const out = new Map();
250
- for (const r of rows) {
251
- if (!r.fp)
252
- continue;
253
- out.set(r.fp, (out.get(r.fp) ?? 0) + 1);
254
- }
255
- return out;
256
- }
257
- /** Number of rows for the session not yet settled (drives tests and settle). */
258
- unsettledForSession(sessionId) {
259
- const row = this.store
260
- .prepare(`SELECT COUNT(*) AS n FROM kevin_injections
261
- WHERE session_id = ? AND outcome = 'unmeasured'`)
262
- .get(sessionId);
263
- return row.n;
264
- }
265
- /** Latest ledger rows for a session, newest first (used by tests/tools). */
266
- rowsForSession(sessionId) {
267
- return this.store
268
- .prepare(`SELECT id, memory_id, fingerprint, session_id, hook, tokens,
269
- injected_at, outcome
270
- FROM kevin_injections
271
- WHERE session_id = ?
272
- ORDER BY injected_at ASC, id ASC`)
273
- .all(sessionId);
274
- }
275
- /**
276
- * v0.5.0 (K5-005 / plan §8.4) — one grouped rollup over the whole
277
- * ledger, zero-filled for every outcome. Consumed by `kevin_audit`.
278
- */
279
- outcomeCounts() {
280
- const rows = this.store
281
- .prepare("SELECT outcome, COUNT(*) AS n FROM kevin_injections GROUP BY outcome")
282
- .all();
283
- const out = {
284
- unmeasured: 0,
285
- effective: 0,
286
- ineffective: 0,
287
- inconclusive: 0,
288
- };
289
- for (const r of rows) {
290
- out[r.outcome] = r.n;
291
- }
292
- return out;
293
- }
294
- }
295
- // v1.1.0 — readOriginCallId deduplicated: imported from MemoryService (K11-003/K11-013)
@@ -1,44 +0,0 @@
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>;
@@ -1,45 +0,0 @@
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
- }
@@ -1,83 +0,0 @@
1
- import type { ArtifactWriter, WriteOutcome } from "./ArtifactWriter.js";
2
- import type { Store } from "./Store.js";
3
- /**
4
- * K6-017 — v0.6.0 pull — topic bundles for the pull channels (plan §5.6).
5
- *
6
- * Writes `~/.opencode-kevin/refs/<topic>.md` (one file per topic) and
7
- * `~/.opencode-kevin/skills/project-knowledge.md` (one file). Both go
8
- * through the `ArtifactWriter` — marker-scoped, atomic, hash-audited.
9
- * There is no direct raw file-write anywhere in this module (D6-01).
10
- *
11
- * Topic derivation is deterministic and semantic (D6-14): a topic is
12
- * `<type>-<dominant token>`, where the dominant token is the
13
- * highest-frequency non-stop-word token of the fingerprint-normalized
14
- * content across the memories in the group, ties broken lexicographically.
15
- * Topics are NEVER derived from a fingerprint prefix, and hash-like tokens
16
- * (hex-only, length >= 8) are excluded from the race entirely.
17
- *
18
- * Output ordering within each bundle is by memory id, matching §5.4.
19
- * Regeneration with unchanged inputs is a `noop` at the ArtifactWriter
20
- * level, so the "run twice, compare bytes" criterion holds for every
21
- * bundle.
22
- */
23
- export interface MaterializedBundle {
24
- readonly topic: string;
25
- readonly path: string;
26
- readonly outcome: WriteOutcome;
27
- }
28
- export interface BundleTarget {
29
- readonly topic: string;
30
- readonly path: string;
31
- }
32
- export declare const SKILL_TOPIC = "project-knowledge";
33
- /**
34
- * v0.9.0 (K9-016 / plan §5.4, D9-10) — the native registration surfaces.
35
- * Registration replaces emission; they are never both active.
36
- */
37
- export type NativeSurface = "skill" | "reference";
38
- export declare class Materializer {
39
- private readonly store;
40
- /** `root` is injectable for tests; production defaults to ~/.opencode-kevin. */
41
- private readonly root;
42
- /**
43
- * v0.9.0 (K9-016 / plan §5.4, D9-10) — the mutual-exclusion guard.
44
- * A surface `attachNative()` registered is skipped by `materialize()`
45
- * for that surface only; the other surface keeps emitting.
46
- */
47
- private readonly nativeRegistered;
48
- constructor(store: Store, options?: {
49
- root?: string;
50
- });
51
- /**
52
- * v0.9.0 (K9-016 / plan §5.4, D9-10) — record whether the surface was
53
- * registered natively. Called at attach time (index.ts wiring, K9-018)
54
- * with each `NativeRegistration.registered` flag.
55
- */
56
- markNativeRegistered(surface: NativeSurface, registered: boolean): void;
57
- /** v0.9.0 (K9-016 / plan §5.4, D9-10) — is the surface registered natively? */
58
- hasNativeRegistration(surface: NativeSurface): boolean;
59
- /** The curated, active memories — the knowledge the pull channels publish. */
60
- private curatedRows;
61
- /** The rendered skill body over all curated memories; "" when empty. */
62
- skillBody(): string;
63
- /** Group rows by type, deriving each type's topic from its own content. */
64
- private groupByTopic;
65
- /**
66
- * The pull-channel targets: the skill file plus one ref file per topic.
67
- * The order is deterministic (skill first, refs by topic).
68
- */
69
- bundleTargets(): BundleTarget[];
70
- /**
71
- * Regenerate every bundle through the ArtifactWriter (write). This is
72
- * a call site of `write()` — the single write FUNCTION with two
73
- * constrained targets: `kevin_approve` reaches `agents_md_path`, this
74
- * module reaches `~/.opencode-kevin` only (D6-07; enforced by K6-020).
75
- *
76
- * v0.9.0 (K9-016 / plan §5.4, D9-10) — the guard: a surface registered
77
- * natively by `attachNative()` is skipped here for that surface only.
78
- * Both active would put the curated skill in front of the model twice
79
- * from two sources that can disagree; neither active would silently
80
- * remove the feature on hosts that gained the subpath.
81
- */
82
- materialize(writer: ArtifactWriter): MaterializedBundle[];
83
- }