@jmtrin/opencode-kevin 1.2.0 → 1.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 (140) hide show
  1. package/README.md +84 -22
  2. package/dist/plugin/index.d.ts +5 -4
  3. package/dist/plugin/index.js +127 -113
  4. package/dist/plugin/native.d.ts +2 -2
  5. package/dist/plugin/native.js +2 -2
  6. package/package.json +56 -68
  7. package/dist/migrations/001_initial.sql +0 -92
  8. package/dist/migrations/002_indexes.sql +0 -14
  9. package/dist/migrations/003_v02_signal.sql +0 -58
  10. package/dist/migrations/004_v03_knowledge.sql +0 -138
  11. package/dist/migrations/005_v04_signal.sql +0 -57
  12. package/dist/migrations/006_v05_glassbox.sql +0 -118
  13. package/dist/migrations/007_v06_pull.sql +0 -145
  14. package/dist/migrations/008_v07_truth.sql +0 -124
  15. package/dist/migrations/009_v08_team.sql +0 -100
  16. package/dist/migrations/010_v09_native.sql +0 -78
  17. package/dist/migrations/011_v10_proven.sql +0 -69
  18. package/dist/migrations/012_v11_drift.sql +0 -24
  19. package/dist/plugin/Archiver.d.ts +0 -42
  20. package/dist/plugin/Archiver.js +0 -83
  21. package/dist/plugin/ArtifactWriter.d.ts +0 -93
  22. package/dist/plugin/ArtifactWriter.js +0 -291
  23. package/dist/plugin/CausalChain.d.ts +0 -22
  24. package/dist/plugin/CausalChain.js +0 -197
  25. package/dist/plugin/ChatBridge.d.ts +0 -41
  26. package/dist/plugin/ChatBridge.js +0 -103
  27. package/dist/plugin/ConflictDetector.d.ts +0 -35
  28. package/dist/plugin/ConflictDetector.js +0 -260
  29. package/dist/plugin/ContextInjector.d.ts +0 -222
  30. package/dist/plugin/ContextInjector.js +0 -533
  31. package/dist/plugin/ConventionMiner.d.ts +0 -35
  32. package/dist/plugin/ConventionMiner.js +0 -242
  33. package/dist/plugin/Curator.d.ts +0 -116
  34. package/dist/plugin/Curator.js +0 -324
  35. package/dist/plugin/DashboardHtml.d.ts +0 -5
  36. package/dist/plugin/DashboardHtml.js +0 -180
  37. package/dist/plugin/Feedback.d.ts +0 -67
  38. package/dist/plugin/Feedback.js +0 -120
  39. package/dist/plugin/HookLiveness.d.ts +0 -88
  40. package/dist/plugin/HookLiveness.js +0 -322
  41. package/dist/plugin/InjectionLedger.d.ts +0 -98
  42. package/dist/plugin/InjectionLedger.js +0 -295
  43. package/dist/plugin/LessonFixer.d.ts +0 -44
  44. package/dist/plugin/LessonFixer.js +0 -45
  45. package/dist/plugin/Materializer.d.ts +0 -83
  46. package/dist/plugin/Materializer.js +0 -181
  47. package/dist/plugin/MemoryService.d.ts +0 -360
  48. package/dist/plugin/MemoryService.js +0 -1242
  49. package/dist/plugin/Migrate.d.ts +0 -17
  50. package/dist/plugin/Migrate.js +0 -271
  51. package/dist/plugin/PatternMiner.d.ts +0 -49
  52. package/dist/plugin/PatternMiner.js +0 -132
  53. package/dist/plugin/QualityGate.d.ts +0 -124
  54. package/dist/plugin/QualityGate.js +0 -119
  55. package/dist/plugin/Reflector.d.ts +0 -91
  56. package/dist/plugin/Reflector.js +0 -307
  57. package/dist/plugin/RepoIdentity.d.ts +0 -124
  58. package/dist/plugin/RepoIdentity.js +0 -300
  59. package/dist/plugin/RepoTruth.d.ts +0 -80
  60. package/dist/plugin/RepoTruth.js +0 -599
  61. package/dist/plugin/Retrospective.d.ts +0 -17
  62. package/dist/plugin/Retrospective.js +0 -216
  63. package/dist/plugin/SharedLayer.d.ts +0 -159
  64. package/dist/plugin/SharedLayer.js +0 -462
  65. package/dist/plugin/Store.d.ts +0 -29
  66. package/dist/plugin/Store.js +0 -50
  67. package/dist/plugin/ToolCallObserver.d.ts +0 -32
  68. package/dist/plugin/ToolCallObserver.js +0 -201
  69. package/dist/plugin/TuiActions.d.ts +0 -43
  70. package/dist/plugin/TuiActions.js +0 -181
  71. package/dist/plugin/TuiSnapshots.d.ts +0 -24
  72. package/dist/plugin/TuiSnapshots.js +0 -158
  73. package/dist/plugin/columns.d.ts +0 -11
  74. package/dist/plugin/columns.js +0 -54
  75. package/dist/plugin/confidence.d.ts +0 -8
  76. package/dist/plugin/confidence.js +0 -34
  77. package/dist/plugin/contract.d.ts +0 -54
  78. package/dist/plugin/contract.js +0 -386
  79. package/dist/plugin/diff.d.ts +0 -8
  80. package/dist/plugin/diff.js +0 -182
  81. package/dist/plugin/escape.d.ts +0 -39
  82. package/dist/plugin/escape.js +0 -60
  83. package/dist/plugin/fingerprint.d.ts +0 -27
  84. package/dist/plugin/fingerprint.js +0 -73
  85. package/dist/plugin/inferability.d.ts +0 -32
  86. package/dist/plugin/inferability.js +0 -88
  87. package/dist/plugin/kevin_approve.d.ts +0 -34
  88. package/dist/plugin/kevin_approve.js +0 -52
  89. package/dist/plugin/kevin_audit.d.ts +0 -229
  90. package/dist/plugin/kevin_audit.js +0 -631
  91. package/dist/plugin/kevin_bench.d.ts +0 -10
  92. package/dist/plugin/kevin_bench.js +0 -82
  93. package/dist/plugin/kevin_conflicts.d.ts +0 -9
  94. package/dist/plugin/kevin_conflicts.js +0 -50
  95. package/dist/plugin/kevin_contract.d.ts +0 -8
  96. package/dist/plugin/kevin_contract.js +0 -59
  97. package/dist/plugin/kevin_doctor.d.ts +0 -62
  98. package/dist/plugin/kevin_doctor.js +0 -196
  99. package/dist/plugin/kevin_facts.d.ts +0 -42
  100. package/dist/plugin/kevin_facts.js +0 -36
  101. package/dist/plugin/kevin_forget.d.ts +0 -33
  102. package/dist/plugin/kevin_forget.js +0 -260
  103. package/dist/plugin/kevin_native.d.ts +0 -29
  104. package/dist/plugin/kevin_native.js +0 -79
  105. package/dist/plugin/kevin_propose.d.ts +0 -23
  106. package/dist/plugin/kevin_propose.js +0 -14
  107. package/dist/plugin/kevin_publish.d.ts +0 -38
  108. package/dist/plugin/kevin_publish.js +0 -18
  109. package/dist/plugin/kevin_why.d.ts +0 -23
  110. package/dist/plugin/kevin_why.js +0 -111
  111. package/dist/plugin/memory-format.d.ts +0 -20
  112. package/dist/plugin/memory-format.js +0 -59
  113. package/dist/plugin/metrics.d.ts +0 -97
  114. package/dist/plugin/metrics.js +0 -288
  115. package/dist/plugin/okf-export.d.ts +0 -3
  116. package/dist/plugin/okf-export.js +0 -134
  117. package/dist/plugin/okf-import.d.ts +0 -76
  118. package/dist/plugin/okf-import.js +0 -271
  119. package/dist/plugin/okf.d.ts +0 -107
  120. package/dist/plugin/okf.js +0 -303
  121. package/dist/plugin/perf.d.ts +0 -35
  122. package/dist/plugin/perf.js +0 -174
  123. package/dist/plugin/query-tokenizer.d.ts +0 -13
  124. package/dist/plugin/query-tokenizer.js +0 -133
  125. package/dist/plugin/redact.d.ts +0 -2
  126. package/dist/plugin/redact.js +0 -18
  127. package/dist/plugin/replay-types.d.ts +0 -69
  128. package/dist/plugin/replay-types.js +0 -156
  129. package/dist/plugin/replay.d.ts +0 -36
  130. package/dist/plugin/replay.js +0 -202
  131. package/dist/plugin/sqlite-adapter.d.ts +0 -12
  132. package/dist/plugin/sqlite-adapter.js +0 -81
  133. package/dist/plugin/time-ms.d.ts +0 -1
  134. package/dist/plugin/time-ms.js +0 -16
  135. package/dist/plugin/tui-types.d.ts +0 -59
  136. package/dist/plugin/tui-types.js +0 -4
  137. package/dist/plugin/tui.d.ts +0 -18
  138. package/dist/plugin/tui.js +0 -198
  139. package/dist/plugin/uuid.d.ts +0 -1
  140. package/dist/plugin/uuid.js +0 -50
@@ -1,462 +0,0 @@
1
- // ============================================================
2
- // Kevin 0.8.0 — SharedLayer (F3, K8-016 … K8-020)
3
- // ============================================================
4
- // The two-directional bridge between the OKF file and the local
5
- // store. Import may retire a memory — but only on an explicit
6
- // committed tombstone. Export may write a file — but only through
7
- // the one write path that has existed since v0.6.0 (D8-08).
8
- //
9
- // Every statement filters on `repo_id` (D8-02): there is no
10
- // unscoped read or write of `shared_entries` or `okf_imports`.
11
- // ============================================================
12
- import { readFileSync } from "node:fs";
13
- import { computeConfidence } from "./confidence.js";
14
- import { fnv1a64 } from "./fingerprint.js";
15
- import { MAX_ENTRIES, MAX_LINE_BYTES, OKF_VERSION, canonicalize, computeEntryId, deriveConfidence, merge, parse, serialize, } from "./okf.js";
16
- import { uuidv7 } from "./uuid.js";
17
- /**
18
- * Project a local memory into its OKF candidate (K8-020): the
19
- * identifying triple (type, statement, scope) determines the
20
- * entry_id, so a re-export of the same memory is byte-identical.
21
- * created_at is normalized from SQLite's "YYYY-MM-DD HH:MM:SS" to
22
- * the file's ISO-8601 form, keeping join()'s min from flipping
23
- * formats across exports.
24
- */
25
- function memoryToEntry(row) {
26
- return {
27
- entry_id: computeEntryId(row.type, row.content, row.scope),
28
- type: row.type,
29
- statement: row.content,
30
- scope: row.scope,
31
- evidence: row.evidence_count ?? 0,
32
- recurrence: row.recurrence_count ?? 0,
33
- origin: row.origin ?? "pattern",
34
- author_hash: null,
35
- op: "assert",
36
- created_at: `${(row.created_at ?? "").replace(" ", "T")}Z`,
37
- supersedes: null,
38
- };
39
- }
40
- export class SharedLayer {
41
- store;
42
- repoId;
43
- projectId;
44
- version;
45
- writer;
46
- constructor(deps) {
47
- this.store = deps.store;
48
- this.repoId = deps.repoId;
49
- this.projectId = deps.projectId;
50
- this.version = deps.version;
51
- this.writer = deps.writer;
52
- }
53
- /**
54
- * The single whole-file (mode `whole`) construction site in
55
- * SharedLayer (K8-019 / K8-020, D8-08, asserted by
56
- * tests/unit/single_write_path.test.ts): the OKF file is written
57
- * through the ArtifactWriter's write funnel like every other file
58
- * Kevin owns — there is no second write path.
59
- */
60
- wholeRequest(path, content, refusal) {
61
- return {
62
- path,
63
- mode: "whole",
64
- content,
65
- ...(refusal !== undefined ? { refusal } : {}),
66
- };
67
- }
68
- /**
69
- * Pure export planner (K8-020 / plan §5.5): reads the current file
70
- * and merges the given local memories' projections into the corpus,
71
- * producing a {@link WritePlan} — but writes nothing. Refusals are
72
- * audited like any other write: `after === before`, both hashes
73
- * recorded (D6-04). The refusal ladder, in the order the code
74
- * evaluates it — file-side refusals first (not_okf, version_ahead,
75
- * repo_mismatch), then candidate-side refusals in the loop order
76
- * (line_too_long, below_floor, not_curated, unknown_entry), then
77
- * too_many_entries and finally parse_damaged.
78
- */
79
- planExport(memoryIds, path) {
80
- const { exists, parsed } = this.readWithFlag(path);
81
- if (exists && parsed.version === 0) {
82
- return this.refused(path, "not_okf");
83
- }
84
- if (exists && parsed.version > OKF_VERSION) {
85
- return this.refused(path, "version_ahead");
86
- }
87
- if (parsed.repoId !== null && parsed.repoId !== this.repoId) {
88
- return this.refused(path, "repo_mismatch");
89
- }
90
- const candidates = this.candidateEntries(memoryIds);
91
- if (candidates.refusal !== null) {
92
- return this.refused(path, candidates.refusal);
93
- }
94
- const merged = merge(parsed.entries, candidates.entries);
95
- if (merged.length > MAX_ENTRIES) {
96
- return this.refused(path, "too_many_entries");
97
- }
98
- if (exists && parsed.rejected.length > 0) {
99
- return this.refused(path, "parse_damaged");
100
- }
101
- const content = serialize(merged, this.repoId, this.version);
102
- const existing = new Set(parsed.entries.map((e) => e.entry_id));
103
- const entriesAdded = merged.filter((e) => !existing.has(e.entry_id)).length;
104
- const request = this.wholeRequest(path, content);
105
- return { path, request, write: this.writer.plan(request), entriesAdded };
106
- }
107
- /**
108
- * Tombstone-append planner (K8-020 / D8-09): emits one `tombstone`
109
- * line per entry id, never deletes a line. The identifying triple is
110
- * reconstructed from the local projection (a memory with
111
- * `shared_entry_id`) so the line still passes parse's
112
- * tamper-evident entry_id check; an id with no local projection is
113
- * refused rather than emitting an invalid line.
114
- */
115
- planTombstone(entryIds, path) {
116
- const { exists, parsed } = this.readWithFlag(path);
117
- if (exists && parsed.version === 0) {
118
- return this.refused(path, "not_okf");
119
- }
120
- if (exists && parsed.version > OKF_VERSION) {
121
- return this.refused(path, "version_ahead");
122
- }
123
- if (parsed.repoId !== null && parsed.repoId !== this.repoId) {
124
- return this.refused(path, "repo_mismatch");
125
- }
126
- if (exists && parsed.rejected.length > 0) {
127
- return this.refused(path, "parse_damaged");
128
- }
129
- if (entryIds.length === 0) {
130
- const content = serialize(parsed.entries, this.repoId, this.version);
131
- const request = this.wholeRequest(path, content);
132
- return {
133
- path,
134
- request,
135
- write: this.writer.plan(request),
136
- entriesAdded: 0,
137
- };
138
- }
139
- const unique = [...new Set(entryIds)];
140
- const placeholders = unique.map(() => "?").join(", ");
141
- const rows = this.store
142
- .prepare(`SELECT type, content, scope, evidence_count, recurrence_count,
143
- origin, created_at
144
- FROM memories
145
- WHERE shared_entry_id IN (${placeholders}) AND repo_id = ?`)
146
- .all(...unique, this.repoId);
147
- if (rows.length !== entryIds.length) {
148
- return this.refused(path, "unknown_entry");
149
- }
150
- const tombstones = rows.map((row) => ({
151
- ...memoryToEntry(row),
152
- op: "tombstone",
153
- }));
154
- const merged = merge(parsed.entries, tombstones);
155
- if (merged.length > MAX_ENTRIES) {
156
- return this.refused(path, "too_many_entries");
157
- }
158
- const content = serialize(merged, this.repoId, this.version);
159
- const existing = new Set(parsed.entries.map((e) => e.entry_id));
160
- const entriesAdded = merged.filter((e) => !existing.has(e.entry_id)).length;
161
- const request = this.wholeRequest(path, content);
162
- return { path, request, write: this.writer.plan(request), entriesAdded };
163
- }
164
- /**
165
- * v0.8.0 (BUG-003) — heal a stale `#repo` header after a rekey.
166
- * Rekey changes the scope the file is written under; a stale first
167
- * line would make every later planExport/planTombstone refuse with
168
- * repo_mismatch forever. Only the header line is replaced; the entry
169
- * bytes, line endings and trailing newline are preserved. The write
170
- * goes through the single funnel (D8-08). Returns true when the
171
- * header was rewritten. Never throws: a missing or unreadable file
172
- * is nothing to heal.
173
- */
174
- healHeader(path, newRepoId) {
175
- let text;
176
- try {
177
- text = readFileSync(path, "utf8");
178
- }
179
- catch {
180
- return false;
181
- }
182
- const eol = text.includes("\r\n") ? "\r\n" : "\n";
183
- const lines = text.split(/\r?\n/);
184
- const header = `#repo ${newRepoId}`;
185
- const repoLine = lines.findIndex((l) => l.startsWith("#repo "));
186
- if (repoLine === -1 || lines[repoLine] === header)
187
- return false;
188
- lines[repoLine] = header;
189
- this.writer.write(this.wholeRequest(path, lines.join(eol)));
190
- return true;
191
- }
192
- /**
193
- * Apply a previously planned export: the write goes through the
194
- * single write funnel (re-planning against the file's current state),
195
- * and the outcome is recorded on the returned plan (K8-020).
196
- */
197
- applyExport(plan) {
198
- return { ...plan, applied: this.writer.write(plan.request) };
199
- }
200
- /** Refusal plans are audited with both hashes (after === before). */
201
- refused(path, reason) {
202
- const request = this.wholeRequest(path, "", reason);
203
- return {
204
- path,
205
- request,
206
- write: this.writer.plan(request),
207
- entriesAdded: 0,
208
- };
209
- }
210
- /**
211
- * Convert the requested local memories into OKF candidates and run
212
- * the candidate-side refusals — line_too_long, below_floor,
213
- * not_curated — in plan §5.5 order.
214
- */
215
- candidateEntries(memoryIds) {
216
- if (memoryIds.length === 0) {
217
- return { entries: [], refusal: null };
218
- }
219
- const placeholders = memoryIds.map(() => "?").join(", ");
220
- const rows = this.store
221
- .prepare(`SELECT type, content, scope, evidence_count, recurrence_count,
222
- origin, created_at, curated
223
- FROM memories WHERE id IN (${placeholders}) AND repo_id = ?`)
224
- .all(...memoryIds, this.repoId);
225
- // v0.8.0 (BUG-006) — every requested id must have a local
226
- // projection, exactly like planTombstone: a typo'd or foreign id
227
- // is refused instead of silently sharing the matching subset.
228
- if (rows.length !== memoryIds.length) {
229
- return { entries: [], refusal: "unknown_entry" };
230
- }
231
- const requiresApproval = this.setting("share_requires_approval", "1") === "1";
232
- const floor = Number.parseFloat(this.setting("shared_confidence_floor", "0.7"));
233
- const clampedFloor = Number.isNaN(floor)
234
- ? 0.7
235
- : Math.min(1, Math.max(0, floor));
236
- const entries = [];
237
- for (const row of rows) {
238
- const entry = memoryToEntry(row);
239
- if (Buffer.byteLength(canonicalize(entry), "utf8") > MAX_LINE_BYTES) {
240
- return { entries: [], refusal: "line_too_long" };
241
- }
242
- if (computeConfidence(entry.evidence, entry.recurrence) < clampedFloor) {
243
- return { entries: [], refusal: "below_floor" };
244
- }
245
- if (requiresApproval && row.curated !== 1) {
246
- return { entries: [], refusal: "not_curated" };
247
- }
248
- entries.push(entry);
249
- }
250
- return { entries, refusal: null };
251
- }
252
- setting(key, fallback) {
253
- const row = this.store
254
- .prepare("SELECT value FROM kevin_settings WHERE key = ?")
255
- .get(key);
256
- return row?.value ?? fallback;
257
- }
258
- readWithFlag(path) {
259
- let text;
260
- try {
261
- text = readFileSync(path, "utf8");
262
- }
263
- catch {
264
- return {
265
- exists: false,
266
- parsed: {
267
- version: 0,
268
- repoId: null,
269
- entries: [],
270
- rejected: [],
271
- folded: 0,
272
- },
273
- };
274
- }
275
- return { exists: true, parsed: parse(text) };
276
- }
277
- /**
278
- * Read and parse the shared file. Total: a missing or unreadable
279
- * file yields an empty ParseResult, never a throw (D8-14).
280
- */
281
- read(path) {
282
- let text;
283
- try {
284
- text = readFileSync(path, "utf8");
285
- }
286
- catch {
287
- return {
288
- version: 0,
289
- repoId: null,
290
- entries: [],
291
- rejected: [],
292
- folded: 0,
293
- };
294
- }
295
- return parse(text);
296
- }
297
- /**
298
- * Idempotent and cheap to call (K8-016 / plan §5.5): hash the file
299
- * first and return `{ skipped: true }` when the hash matches the
300
- * most recent `okf_imports` row for this repo_id — the session.idle
301
- * path costs one readFileSync plus one hash on an unchanged
302
- * repository. A missing file is not an error: it returns
303
- * `fileHash: null`, `parsed: 0`, and still writes an audit row.
304
- */
305
- import(path) {
306
- let fileHash = null;
307
- let text;
308
- try {
309
- text = readFileSync(path, "utf8");
310
- fileHash = fnv1a64(text);
311
- }
312
- catch {
313
- fileHash = null;
314
- this.writeImportRow(path, null, 0, 0, 0, 0);
315
- return {
316
- path,
317
- fileHash: null,
318
- parsed: 0,
319
- folded: 0,
320
- rejected: 0,
321
- imported: 0,
322
- tombstoned: 0,
323
- skipped: false,
324
- };
325
- }
326
- const last = this.store
327
- .prepare(`SELECT file_hash FROM okf_imports
328
- WHERE repo_id = ? ORDER BY imported_at DESC, rowid DESC LIMIT 1`)
329
- .get(this.repoId);
330
- if (last !== undefined && last.file_hash === fileHash) {
331
- this.writeImportRow(path, fileHash, 0, 0, 0, 1);
332
- return {
333
- path,
334
- fileHash,
335
- parsed: 0,
336
- folded: 0,
337
- rejected: 0,
338
- imported: 0,
339
- tombstoned: 0,
340
- skipped: true,
341
- };
342
- }
343
- const result = parse(text);
344
- const imported = this.upsertEntries(result.entries);
345
- const tombstoned = this.projectEntries(result.entries);
346
- if (result.folded > 0) {
347
- // A persistently non-zero value is signal, not error: the team
348
- // is editing the same entries concurrently and the lattice is
349
- // absorbing it (K8-016).
350
- this.store
351
- .prepare(`INSERT INTO kevin_metrics (key, value, updated_at)
352
- VALUES ('okf_merge_folds', ?, datetime('now'))
353
- ON CONFLICT(key) DO UPDATE SET
354
- value = value + excluded.value,
355
- updated_at = datetime('now')`)
356
- .run(result.folded);
357
- }
358
- this.writeImportRow(path, fileHash, result.entries.length, result.folded, result.rejected.length, 0);
359
- return {
360
- path,
361
- fileHash,
362
- parsed: result.entries.length,
363
- folded: result.folded,
364
- rejected: result.rejected.length,
365
- imported,
366
- tombstoned,
367
- skipped: false,
368
- };
369
- }
370
- writeImportRow(path, fileHash, parsed, folded, rejected, skipped) {
371
- this.store
372
- .prepare(`INSERT INTO okf_imports
373
- (id, repo_id, path, file_hash, entries_parsed, entries_folded,
374
- entries_rejected, skipped)
375
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
376
- .run(uuidv7(), this.repoId, path, fileHash, parsed, folded, rejected, skipped);
377
- }
378
- /**
379
- * Project every imported entry into memories (K8-017 / plan §5.5,
380
- * D8-10): a shared memory IS a memory, so getRelevant(), rankScore(),
381
- * the five gates, truth_penalty, ConflictDetector and every audit
382
- * rollup keep working unchanged.
383
- *
384
- * - assert entries: inserted once, never updated — re-importing an
385
- * unchanged file must not create duplicates, bump evidence_count or
386
- * move updated_at. `fingerprint` is deliberately left NULL: it is a
387
- * different identity dimension (plan §3.3) and conflating it with
388
- * `shared_entry_id` would make the v0.4.0 supersede path fire on
389
- * unrelated entries (K8-017 acceptance).
390
- * - tombstone entries: the ONE place in this codebase where the shared
391
- * layer may write memories.status. This is NOT a v0.7.0 Principle 24
392
- * violation: that principle forbids *contradiction* — a fuzzy
393
- * inference — from writing status, and a tombstone is the opposite:
394
- * an explicit, committed, human-reviewed decision that arrived
395
- * through a pull request (D8-09, plan §5.5).
396
- *
397
- * Returns the number of memories archived by tombstones.
398
- */
399
- projectEntries(entries) {
400
- const findMemory = this.store.prepare(`SELECT id FROM memories
401
- WHERE shared_entry_id = ? AND repo_id = ? LIMIT 1`);
402
- const insertMemory = this.store.prepare(`INSERT INTO memories
403
- (id, type, content, scope, relevance_score, project_id, origin,
404
- evidence_count, recurrence_count, created_at, updated_at, status,
405
- curated, inferable, layer, repo_id, shared_entry_id)
406
- VALUES (?, ?, ?, ?, ?, ?, 'imported', ?, ?, datetime('now'),
407
- datetime('now'), 'active', 1, 1, 'shared', ?, ?)`);
408
- const tombstone = this.store.prepare(`UPDATE memories SET status = 'archived'
409
- WHERE shared_entry_id = ? AND repo_id = ? AND layer = 'shared'
410
- AND status = 'active'`);
411
- const lastChanges = this.store.prepare("SELECT changes() AS c");
412
- let archived = 0;
413
- for (const e of entries) {
414
- if (e.op === "tombstone") {
415
- // Explicit committed decision — see the class comment above.
416
- tombstone.run(e.entry_id, this.repoId);
417
- archived += lastChanges.get().c;
418
- continue;
419
- }
420
- const existing = findMemory.get(e.entry_id, this.repoId);
421
- if (existing)
422
- continue;
423
- insertMemory.run(uuidv7(), e.type, e.statement,
424
- // memories.scope is CHECK-constrained to project/session; a
425
- // path-prefix scope from the file degrades to 'project'.
426
- e.scope === "project" || e.scope === "session" ? e.scope : "project", deriveConfidence(e), this.projectId, e.evidence, e.recurrence, this.repoId, e.entry_id);
427
- }
428
- return archived;
429
- }
430
- /**
431
- * Upsert every entry into shared_entries keyed on (repo_id,
432
- * entry_id) — the UNIQUE index from migration 009 makes this an
433
- * upsert rather than an append. `confidence` is the DERIVED value
434
- * (deriveConfidence), never a transported float (plan §5.3).
435
- * Returns the number of rows inserted or updated.
436
- */
437
- upsertEntries(entries) {
438
- const upsert = this.store.prepare(`INSERT INTO shared_entries
439
- (id, repo_id, entry_id, type, statement, scope, confidence,
440
- evidence, origin, author_hash, op, supersedes, created_at,
441
- imported_at)
442
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
443
- ON CONFLICT(repo_id, entry_id) DO UPDATE SET
444
- type = excluded.type,
445
- statement = excluded.statement,
446
- scope = excluded.scope,
447
- confidence = excluded.confidence,
448
- evidence = excluded.evidence,
449
- origin = excluded.origin,
450
- author_hash = excluded.author_hash,
451
- op = excluded.op,
452
- supersedes = excluded.supersedes,
453
- created_at = excluded.created_at,
454
- imported_at = datetime('now')`);
455
- let n = 0;
456
- for (const e of entries) {
457
- upsert.run(uuidv7(), this.repoId, e.entry_id, e.type, e.statement, e.scope, deriveConfidence(e), e.evidence, e.origin, e.author_hash, e.op, e.supersedes, e.created_at);
458
- n++;
459
- }
460
- return n;
461
- }
462
- }
@@ -1,29 +0,0 @@
1
- import { type SqliteAdapter } from "./sqlite-adapter.js";
2
- export interface StoreOptions {
3
- path: string;
4
- }
5
- /**
6
- * Thin generic wrapper around {@link SqliteAdapter}. The Store deliberately
7
- * does NOT pre-prepare statements: callers (MemoryService, ToolCallObserver,
8
- * Reflector, Retrospective, Metrics, …) prepare SQL on demand so that any
9
- * migration can add columns/tables without requiring coordinated edits here.
10
- *
11
- * v0.2.0 (migration 003_v02_signal.sql) introduces the `kevin_metrics` and
12
- * `kevin_settings` tables plus nullable columns on `memories` and
13
- * `tool_calls`. All v0.2.0 callers read/write those rows via the existing
14
- * {@link Store.prepare | prepare()} / {@link Store.exec | exec()} /
15
- * {@link Store.transaction | transaction()} surface — no new member is
16
- * required on Store. K2-005 confirms this contract by exercising the new
17
- * tables/columns through the generic helpers (see
18
- * `tests/unit/store-prepare-003.test.ts`).
19
- */
20
- export declare class Store {
21
- private db;
22
- private closed;
23
- constructor(options: StoreOptions);
24
- prepare(sql: string): ReturnType<SqliteAdapter["prepare"]>;
25
- transaction<T>(fn: () => T): T;
26
- exec(sql: string): void;
27
- close(): void;
28
- get raw(): SqliteAdapter;
29
- }
@@ -1,50 +0,0 @@
1
- import { createDatabase } from "./sqlite-adapter.js";
2
- /**
3
- * Thin generic wrapper around {@link SqliteAdapter}. The Store deliberately
4
- * does NOT pre-prepare statements: callers (MemoryService, ToolCallObserver,
5
- * Reflector, Retrospective, Metrics, …) prepare SQL on demand so that any
6
- * migration can add columns/tables without requiring coordinated edits here.
7
- *
8
- * v0.2.0 (migration 003_v02_signal.sql) introduces the `kevin_metrics` and
9
- * `kevin_settings` tables plus nullable columns on `memories` and
10
- * `tool_calls`. All v0.2.0 callers read/write those rows via the existing
11
- * {@link Store.prepare | prepare()} / {@link Store.exec | exec()} /
12
- * {@link Store.transaction | transaction()} surface — no new member is
13
- * required on Store. K2-005 confirms this contract by exercising the new
14
- * tables/columns through the generic helpers (see
15
- * `tests/unit/store-prepare-003.test.ts`).
16
- */
17
- export class Store {
18
- db;
19
- closed = false;
20
- constructor(options) {
21
- this.db = createDatabase(options.path);
22
- this.db.exec("PRAGMA journal_mode = WAL");
23
- this.db.exec("PRAGMA foreign_keys = ON");
24
- }
25
- prepare(sql) {
26
- if (this.closed)
27
- throw new Error("Store is closed");
28
- return this.db.prepare(sql);
29
- }
30
- transaction(fn) {
31
- if (this.closed)
32
- throw new Error("Store is closed");
33
- const tx = this.db.transaction(fn);
34
- return tx();
35
- }
36
- exec(sql) {
37
- if (this.closed)
38
- throw new Error("Store is closed");
39
- this.db.exec(sql);
40
- }
41
- close() {
42
- if (this.closed)
43
- return;
44
- this.closed = true;
45
- this.db.close();
46
- }
47
- get raw() {
48
- return this.db;
49
- }
50
- }
@@ -1,32 +0,0 @@
1
- import type { Store } from "./Store.js";
2
- import type { Metrics } from "./metrics.js";
3
- export interface ToolExecuteInput {
4
- tool: string;
5
- args?: Record<string, unknown>;
6
- agent?: string;
7
- sessionId?: string;
8
- callID?: string;
9
- projectId?: string | null;
10
- }
11
- export interface ToolExecuteOutput {
12
- success?: boolean;
13
- stdout?: string;
14
- stderr?: string;
15
- exitCode?: number;
16
- }
17
- export declare class ToolCallObserver {
18
- private store;
19
- private startTs;
20
- private readonly metrics;
21
- constructor(store: Store, metrics?: Metrics | null);
22
- onBefore(input: ToolExecuteInput, _output: ToolExecuteOutput): void;
23
- onAfter(input: ToolExecuteInput, output: ToolExecuteOutput): void;
24
- private isDedupEnabled;
25
- redactSecrets(text: string): string;
26
- summarizeArgs(args: Record<string, unknown>): string;
27
- inferErrorType(stderr: string, stdout: string, exitCode?: number): string;
28
- private key;
29
- private looksSecret;
30
- private redactArgs;
31
- private redactValue;
32
- }