@jmtrin/kevin-core 1.4.0 → 2.0.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.
- package/README.md +711 -667
- package/dist/ArtifactWriter.js +1 -1
- package/dist/InjectionLedger.js +112 -110
- package/dist/Materializer.d.ts +5 -0
- package/dist/Materializer.js +11 -0
- package/dist/MemoryService.js +93 -91
- package/dist/RepoIdentity.js +1 -1
- package/dist/Retrospective.js +8 -0
- package/dist/Store.d.ts +1 -0
- package/dist/Store.js +14 -2
- package/dist/contract.d.ts +58 -1
- package/dist/contract.js +215 -3
- package/dist/import-host.d.ts +41 -0
- package/dist/import-host.js +286 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +16 -1
- package/dist/kevin_audit.d.ts +16 -0
- package/dist/kevin_audit.js +37 -0
- package/dist/metrics.d.ts +1 -1
- package/dist/metrics.js +22 -0
- package/dist/mif.d.ts +32 -0
- package/dist/mif.js +112 -0
- package/dist/migrations/014_v2_commonwealth.sql +50 -0
- package/dist/okf-shards.d.ts +10 -0
- package/dist/okf-shards.js +103 -0
- package/dist/okf.d.ts +5 -1
- package/dist/okf.js +11 -3
- package/dist/skills-emit.d.ts +38 -0
- package/dist/skills-emit.js +421 -0
- package/dist/skills-validate.d.ts +7 -0
- package/dist/skills-validate.js +236 -0
- package/dist/sources/ClaudeMemorySource.d.ts +10 -0
- package/dist/sources/ClaudeMemorySource.js +36 -0
- package/dist/sources/CodexMemoriesSource.d.ts +10 -0
- package/dist/sources/CodexMemoriesSource.js +37 -0
- package/dist/sources/IdleSync.d.ts +10 -0
- package/dist/sources/IdleSync.js +49 -0
- package/dist/sources/MemorySource.d.ts +19 -0
- package/dist/sources/MemorySource.js +1 -0
- package/dist/sources/OpencodeNativeSource.d.ts +10 -0
- package/dist/sources/OpencodeNativeSource.js +33 -0
- package/dist/sources/OpencodePluginSource.d.ts +9 -0
- package/dist/sources/OpencodePluginSource.js +13 -0
- package/dist/sqlite-adapter.js +1 -1
- package/package.json +24 -26
package/dist/ArtifactWriter.js
CHANGED
|
@@ -243,7 +243,7 @@ export class ArtifactWriter {
|
|
|
243
243
|
// Rule 7 — write to `<path>.kevin.tmp` in the same directory (same
|
|
244
244
|
// filesystem, so rename is atomic), fsync, close, then rename over the
|
|
245
245
|
// target. Never write the target path directly, never truncate-then-write.
|
|
246
|
-
const tmpPath = `${plan.path}.kevin.tmp`;
|
|
246
|
+
const tmpPath = `${plan.path}.kevin.tmp.${uuidv7()}`;
|
|
247
247
|
let fd;
|
|
248
248
|
try {
|
|
249
249
|
fd = openSync(tmpPath, "w");
|
package/dist/InjectionLedger.js
CHANGED
|
@@ -83,146 +83,148 @@ export class InjectionLedger {
|
|
|
83
83
|
FROM kevin_injections
|
|
84
84
|
WHERE session_id = ?
|
|
85
85
|
ORDER BY injected_at ASC, id ASC`)).all(sessionId);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
86
|
+
this.store.transaction(() => {
|
|
87
|
+
for (const inj of injections) {
|
|
88
|
+
// Same identity dimension CausalChain uses: the failing call's
|
|
89
|
+
// `error_fingerprint` (stamped by Reflector) or the legacy
|
|
90
|
+
// `fingerprint` hash.
|
|
91
|
+
// v1.1.0 — time comparison uses toMs helper (prefers _ms).
|
|
92
|
+
//
|
|
93
|
+
// BUG-003 — the exemption is now bounded to the lesson's OWN
|
|
94
|
+
// creating call (memories.metadata.origin_call_id, stamped by
|
|
95
|
+
// Reflector). The old code excluded the session's FIRST failing
|
|
96
|
+
// call of the fingerprint, which is only the creating call when
|
|
97
|
+
// the lesson was born in THIS session; a lesson created in an
|
|
98
|
+
// earlier session had its first in-session failure (a genuine
|
|
99
|
+
// post-injection recurrence) wrongly exempted, inflating
|
|
100
|
+
// precision. Memories without a tracked creating call (agent-
|
|
101
|
+
// saved, test fixtures) get no exemption: only the `ts >=
|
|
102
|
+
// injected_at` bound applies.
|
|
103
|
+
const metaRow = this.store
|
|
104
|
+
.prepare("SELECT metadata FROM memories WHERE id = ?")
|
|
105
|
+
.get(inj.memory_id);
|
|
106
|
+
const originCallId = readOriginCallId(metaRow?.metadata ?? null);
|
|
107
|
+
// v1.1.0 — heuristic: when legacy string and _ms diverge by >2s
|
|
108
|
+
// (manual UPDATE of injected_at in tests), trust the string
|
|
109
|
+
// because the ms reflects wall time at record, not the pinned
|
|
110
|
+
// fixture time. Real rows differ by <1s (second truncation).
|
|
111
|
+
const rawInjMs = inj.injected_at_ms ?? null;
|
|
112
|
+
const stringMs = inj.injected_at
|
|
113
|
+
? Date.parse(`${inj.injected_at.replace(" ", "T")}Z`)
|
|
114
|
+
: null;
|
|
115
|
+
const injectedMs = rawInjMs !== null &&
|
|
116
|
+
stringMs !== null &&
|
|
117
|
+
!Number.isNaN(stringMs) &&
|
|
118
|
+
Math.abs(rawInjMs - stringMs) > 2000
|
|
119
|
+
? stringMs
|
|
120
|
+
: toMs(inj.injected_at, rawInjMs);
|
|
121
|
+
// Fetch candidate failing calls for this fingerprint and filter by ms
|
|
122
|
+
const failRows = (hasToolMs
|
|
123
|
+
? this.store.prepare(`SELECT id, ts, ts_ms FROM tool_calls
|
|
123
124
|
WHERE session_id = ?
|
|
124
125
|
AND success = 0
|
|
125
126
|
AND COALESCE(error_fingerprint, fingerprint) = ?`)
|
|
126
|
-
|
|
127
|
+
: this.store.prepare(`SELECT id, ts FROM tool_calls
|
|
127
128
|
WHERE session_id = ?
|
|
128
129
|
AND success = 0
|
|
129
130
|
AND COALESCE(error_fingerprint, fingerprint) = ?`)).all(sessionId, inj.fingerprint);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
continue;
|
|
134
|
-
const tsMs = toMs(r.ts, r.ts_ms ?? null);
|
|
135
|
-
if (injectedMs !== null && tsMs !== null) {
|
|
136
|
-
if (tsMs < injectedMs)
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
// fallback to string comparison when either side missing (legacy)
|
|
141
|
-
if (r.ts < inj.injected_at)
|
|
131
|
+
let n = 0;
|
|
132
|
+
for (const r of failRows) {
|
|
133
|
+
if (originCallId !== null && r.id === originCallId)
|
|
142
134
|
continue;
|
|
135
|
+
const tsMs = toMs(r.ts, r.ts_ms ?? null);
|
|
136
|
+
if (injectedMs !== null && tsMs !== null) {
|
|
137
|
+
if (tsMs < injectedMs)
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// fallback to string comparison when either side missing (legacy)
|
|
142
|
+
if (r.ts < inj.injected_at)
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
n++;
|
|
143
146
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.store
|
|
154
|
-
.prepare(`UPDATE kevin_injections SET outcome = 'ineffective'
|
|
147
|
+
// v0.5.0 (K5-005 / plan §5.1, D5-01) — three-way settlement:
|
|
148
|
+
// recurrences >= 1 → ineffective (existing side effects unchanged)
|
|
149
|
+
// else fixes >= 1 → effective (a linked fix was OBSERVED)
|
|
150
|
+
// else → inconclusive (new majority bucket; excluded
|
|
151
|
+
// from the precision denominator)
|
|
152
|
+
if (n >= 1) {
|
|
153
|
+
if (inj.outcome === "unmeasured") {
|
|
154
|
+
this.store
|
|
155
|
+
.prepare(`UPDATE kevin_injections SET outcome = 'ineffective'
|
|
155
156
|
WHERE id = ?`)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
.run(inj.id);
|
|
158
|
+
this.metrics?.incr("injections_ineffective", 1);
|
|
159
|
+
}
|
|
160
|
+
this.store
|
|
161
|
+
.prepare(`UPDATE memories
|
|
161
162
|
SET recurrence_count = MAX(recurrence_count, ?),
|
|
162
163
|
last_injected_at = CASE
|
|
163
164
|
WHEN last_injected_at IS NULL
|
|
164
165
|
OR ? > last_injected_at
|
|
165
166
|
THEN ? ELSE last_injected_at END
|
|
166
167
|
WHERE fingerprint = ? AND id = ?`)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
168
|
+
.run(n, inj.injected_at, inj.injected_at, inj.fingerprint, inj.memory_id);
|
|
169
|
+
// v0.4.0 (K4-025 / plan §5.1 rule 4, D4-06) — recurrence
|
|
170
|
+
// expels: a fingerprint at `recurrence_count >= 3` is
|
|
171
|
+
// demoted to `status='stale'` and never injected again
|
|
172
|
+
// (only a new causal pattern — from a linked fix —
|
|
173
|
+
// re-admits the lesson, not the stale error row).
|
|
174
|
+
this.store
|
|
175
|
+
.prepare(`UPDATE memories SET status = 'stale'
|
|
175
176
|
WHERE id = ? AND recurrence_count >= 3`)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
.run(inj.memory_id);
|
|
178
|
+
}
|
|
179
|
+
else if (inj.outcome === "unmeasured") {
|
|
180
|
+
// Mirror of the recurrence predicate with the success flag
|
|
181
|
+
// inverted and the fingerprint matched on
|
|
182
|
+
// `fix_for_fingerprint` (populated by CausalChain.onSuccess,
|
|
183
|
+
// indexed by idx_tool_calls_fix_fp since migration 004). The
|
|
184
|
+
// `ts >= injected_at` bound and `session_id = ?` filter are
|
|
185
|
+
// kept; there is no `origin_call_id` exemption for fixes —
|
|
186
|
+
// a fix is not the creating call.
|
|
187
|
+
// v1.1.0 — ms-aware: fetch and filter via toMs.
|
|
188
|
+
const fixCandidates = (hasToolMs
|
|
189
|
+
? this.store.prepare(`SELECT ts, ts_ms FROM tool_calls
|
|
189
190
|
WHERE session_id = ?
|
|
190
191
|
AND success = 1
|
|
191
192
|
AND fix_for_fingerprint = ?`)
|
|
192
|
-
|
|
193
|
+
: this.store.prepare(`SELECT ts FROM tool_calls
|
|
193
194
|
WHERE session_id = ?
|
|
194
195
|
AND success = 1
|
|
195
196
|
AND fix_for_fingerprint = ?`)).all(sessionId, inj.fingerprint);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
let hasFix = false;
|
|
198
|
+
for (const fr of fixCandidates) {
|
|
199
|
+
const tsMs = toMs(fr.ts, fr.ts_ms ?? null);
|
|
200
|
+
if (injectedMs !== null && tsMs !== null) {
|
|
201
|
+
if (tsMs >= injectedMs) {
|
|
202
|
+
hasFix = true;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (fr.ts >= inj.injected_at) {
|
|
201
207
|
hasFix = true;
|
|
202
208
|
break;
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (hasFix) {
|
|
211
|
-
this.store
|
|
212
|
-
.prepare(`UPDATE kevin_injections SET outcome = 'effective'
|
|
211
|
+
if (hasFix) {
|
|
212
|
+
this.store
|
|
213
|
+
.prepare(`UPDATE kevin_injections SET outcome = 'effective'
|
|
213
214
|
WHERE id = ?`)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
215
|
+
.run(inj.id);
|
|
216
|
+
this.metrics?.incr("injections_effective", 1);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
this.store
|
|
220
|
+
.prepare(`UPDATE kevin_injections SET outcome = 'inconclusive'
|
|
220
221
|
WHERE id = ?`)
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
.run(inj.id);
|
|
223
|
+
this.metrics?.incr("injections_inconclusive", 1);
|
|
224
|
+
}
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
|
-
}
|
|
227
|
+
});
|
|
226
228
|
}
|
|
227
229
|
/**
|
|
228
230
|
* Per-fingerprint failing tool-call counts for the session. Feeds
|
package/dist/Materializer.d.ts
CHANGED
|
@@ -59,6 +59,11 @@ export declare class Materializer {
|
|
|
59
59
|
hasNativeRegistration(surface: NativeSurface): boolean;
|
|
60
60
|
/** The curated, active memories — the knowledge the pull channels publish. */
|
|
61
61
|
private curatedRows;
|
|
62
|
+
/** K15-003 — topic bundles for skill emit (deterministic) */
|
|
63
|
+
topicBundles(): Array<{
|
|
64
|
+
topic: string;
|
|
65
|
+
content: string;
|
|
66
|
+
}>;
|
|
62
67
|
/** The rendered skill body over all curated memories; "" when empty. */
|
|
63
68
|
skillBody(): string;
|
|
64
69
|
/** Group rows by type, deriving each type's topic from its own content. */
|
package/dist/Materializer.js
CHANGED
|
@@ -86,6 +86,17 @@ export class Materializer {
|
|
|
86
86
|
WHERE status = 'active' AND curated = 1`)
|
|
87
87
|
.all();
|
|
88
88
|
}
|
|
89
|
+
/** K15-003 — topic bundles for skill emit (deterministic) */
|
|
90
|
+
topicBundles() {
|
|
91
|
+
const rows = this.curatedRows();
|
|
92
|
+
const bundles = [];
|
|
93
|
+
for (const g of this.groupByTopic(rows)) {
|
|
94
|
+
const body = renderRows(g.rows);
|
|
95
|
+
if (body !== "")
|
|
96
|
+
bundles.push({ topic: g.topic, content: body });
|
|
97
|
+
}
|
|
98
|
+
return bundles;
|
|
99
|
+
}
|
|
89
100
|
/** The rendered skill body over all curated memories; "" when empty. */
|
|
90
101
|
skillBody() {
|
|
91
102
|
return renderRows(this.curatedRows());
|
package/dist/MemoryService.js
CHANGED
|
@@ -301,99 +301,101 @@ export class MemoryService {
|
|
|
301
301
|
// supersession a navigable audit trail. Guarded: pre-006 DBs lack
|
|
302
302
|
// the column (same migration as `ignored`).
|
|
303
303
|
const supersedableTypes = ["decision", "rule"];
|
|
304
|
-
if (fp !== null && supersedableTypes.includes(input.type)) {
|
|
305
|
-
const withSupersededBy = this.hasIgnoredColumn();
|
|
306
|
-
const setClause = withSupersededBy
|
|
307
|
-
? "SET status = 'superseded', superseded_by = ?, updated_at = datetime('now')"
|
|
308
|
-
: "SET status = 'superseded', updated_at = datetime('now')";
|
|
309
|
-
// v0.8.0 (K8-007 / plan §5.7) — once the 009 column exists and an
|
|
310
|
-
// identity is resolved, supersession is scoped on repo_id (NULL
|
|
311
|
-
// rows are global); project_id stays as the pre-009 scope.
|
|
312
|
-
const scopedOnRepoId = this.hasRepoIdColumn() && this.repoId !== null;
|
|
313
|
-
const whereScope = scopedOnRepoId
|
|
314
|
-
? "AND (repo_id IS ? OR repo_id IS NULL)"
|
|
315
|
-
: "AND (project_id IS ? OR (project_id IS NULL AND ? IS NULL))";
|
|
316
|
-
const scopeParams = scopedOnRepoId
|
|
317
|
-
? [this.repoId]
|
|
318
|
-
: [projectId, projectId];
|
|
319
|
-
this.store
|
|
320
|
-
.prepare(`UPDATE memories
|
|
321
|
-
${setClause}
|
|
322
|
-
WHERE fingerprint = ?
|
|
323
|
-
AND type = ?
|
|
324
|
-
AND status = 'active'
|
|
325
|
-
${whereScope}`)
|
|
326
|
-
.run(...(withSupersededBy
|
|
327
|
-
? [id, fp, input.type, ...scopeParams]
|
|
328
|
-
: [fp, input.type, ...scopeParams]));
|
|
329
|
-
const after = this.store.prepare("SELECT changes() AS n").get();
|
|
330
|
-
if (after.n > 0) {
|
|
331
|
-
this.metrics?.incr("memories_superseded", 1);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
304
|
try {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
305
|
+
this.store.transaction(() => {
|
|
306
|
+
if (fp !== null && supersedableTypes.includes(input.type)) {
|
|
307
|
+
const withSupersededBy = this.hasIgnoredColumn();
|
|
308
|
+
const setClause = withSupersededBy
|
|
309
|
+
? "SET status = 'superseded', superseded_by = ?, updated_at = datetime('now')"
|
|
310
|
+
: "SET status = 'superseded', updated_at = datetime('now')";
|
|
311
|
+
// v0.8.0 (K8-007 / plan §5.7) — once the 009 column exists and an
|
|
312
|
+
// identity is resolved, supersession is scoped on repo_id (NULL
|
|
313
|
+
// rows are global); project_id stays as the pre-009 scope.
|
|
314
|
+
const scopedOnRepoId = this.hasRepoIdColumn() && this.repoId !== null;
|
|
315
|
+
const whereScope = scopedOnRepoId
|
|
316
|
+
? "AND (repo_id IS ? OR repo_id IS NULL)"
|
|
317
|
+
: "AND (project_id IS ? OR (project_id IS NULL AND ? IS NULL))";
|
|
318
|
+
const scopeParams = scopedOnRepoId
|
|
319
|
+
? [this.repoId]
|
|
320
|
+
: [projectId, projectId];
|
|
321
|
+
this.store
|
|
322
|
+
.prepare(`UPDATE memories
|
|
323
|
+
${setClause}
|
|
324
|
+
WHERE fingerprint = ?
|
|
325
|
+
AND type = ?
|
|
326
|
+
AND status = 'active'
|
|
327
|
+
${whereScope}`)
|
|
328
|
+
.run(...(withSupersededBy
|
|
329
|
+
? [id, fp, input.type, ...scopeParams]
|
|
330
|
+
: [fp, input.type, ...scopeParams]));
|
|
331
|
+
const after = this.store.prepare("SELECT changes() AS n").get();
|
|
332
|
+
if (after.n > 0) {
|
|
333
|
+
this.metrics?.incr("memories_superseded", 1);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// v0.4.0 (BUG-008) — recurrence_count is only persisted when the
|
|
337
|
+
// column exists (migration 005); pre-005 DBs get the legacy shape.
|
|
338
|
+
// v0.6.0 (K6-011 / plan §5.3) — the inferable verdict is persisted
|
|
339
|
+
// on insert when the column exists (migration 007). The column
|
|
340
|
+
// list is assembled so every migration level gets exactly its own
|
|
341
|
+
// shape: 005+ gains recurrence_count, 007+ gains inferable.
|
|
342
|
+
const withRecurrence = this.hasRecurrenceColumn();
|
|
343
|
+
const withCurated = this.hasCuratedColumn();
|
|
344
|
+
const columns = [
|
|
345
|
+
"id",
|
|
346
|
+
"type",
|
|
347
|
+
"content",
|
|
348
|
+
"scope",
|
|
349
|
+
"relevance_score",
|
|
350
|
+
"source_tool",
|
|
351
|
+
"source_session",
|
|
352
|
+
"metadata",
|
|
353
|
+
"expires_at",
|
|
354
|
+
"project_id",
|
|
355
|
+
"fingerprint",
|
|
356
|
+
"origin",
|
|
357
|
+
"evidence_count",
|
|
358
|
+
"last_verified_at",
|
|
359
|
+
"status",
|
|
360
|
+
];
|
|
361
|
+
const params = [
|
|
362
|
+
id,
|
|
363
|
+
input.type,
|
|
364
|
+
input.content,
|
|
365
|
+
scope,
|
|
366
|
+
relevanceScore,
|
|
367
|
+
input.sourceTool ?? null,
|
|
368
|
+
input.sourceSession ?? null,
|
|
369
|
+
metadata,
|
|
370
|
+
expiresAt,
|
|
371
|
+
projectId,
|
|
372
|
+
fp,
|
|
373
|
+
origin,
|
|
374
|
+
input.evidenceCount ?? 0,
|
|
375
|
+
input.lastVerifiedAt ?? null,
|
|
376
|
+
status,
|
|
377
|
+
];
|
|
378
|
+
if (withRecurrence) {
|
|
379
|
+
columns.push("recurrence_count");
|
|
380
|
+
params.push(input.recurrenceCount ?? 0);
|
|
381
|
+
}
|
|
382
|
+
if (withCurated) {
|
|
383
|
+
columns.push("inferable");
|
|
384
|
+
params.push(persistInferable(input.type, input.content, input.metadata));
|
|
385
|
+
}
|
|
386
|
+
// v0.8.0 (K8-007 / plan §5.7) — repo_id is persisted on every new
|
|
387
|
+
// memory (009 column). A NULL projectId stays NULL-scoped — the
|
|
388
|
+
// global rows PatternMiner's nullPid convention relies on — and a
|
|
389
|
+
// NULL repo_id row matches every scope. project_id remains written
|
|
390
|
+
// above: it is provenance now, not scope (D8-02).
|
|
391
|
+
if (this.hasRepoIdColumn()) {
|
|
392
|
+
columns.push("repo_id");
|
|
393
|
+
params.push(projectId !== null ? this.repoId : null);
|
|
394
|
+
}
|
|
395
|
+
const insert = `INSERT INTO memories (${columns.join(", ")})
|
|
395
396
|
VALUES (${params.map(() => "?").join(", ")})`;
|
|
396
|
-
|
|
397
|
+
this.store.prepare(insert).run(...params);
|
|
398
|
+
});
|
|
397
399
|
return id;
|
|
398
400
|
}
|
|
399
401
|
catch (err) {
|
package/dist/RepoIdentity.js
CHANGED
|
@@ -63,7 +63,7 @@ export function parseGitConfigRemote(text, name = "origin") {
|
|
|
63
63
|
continue;
|
|
64
64
|
const eq = line.indexOf("=");
|
|
65
65
|
if (eq === -1)
|
|
66
|
-
|
|
66
|
+
continue;
|
|
67
67
|
const key = line.slice(0, eq).trim();
|
|
68
68
|
const value = line.slice(eq + 1).trim();
|
|
69
69
|
if (key !== "url")
|
package/dist/Retrospective.js
CHANGED
|
@@ -87,6 +87,14 @@ export const METRIC_KEY_LABELS = {
|
|
|
87
87
|
mcp_writes_accepted: "Escrituras MCP aceptadas",
|
|
88
88
|
mcp_writes_refused: "Escrituras MCP rechazadas",
|
|
89
89
|
mcp_errors_total: "Errores MCP totales",
|
|
90
|
+
// v1.5.0 (K15-001 / plan §4) — Diaspora metrics; labels required by BUG-014.
|
|
91
|
+
skills_emitted_total: "Skills emitidos (total)",
|
|
92
|
+
mif_exports_total: "Exportaciones MIF totales",
|
|
93
|
+
mif_imports_total: "Importaciones MIF totales",
|
|
94
|
+
// v2.0.0 (K16-012 / plan §4.4) — Commonwealth metrics
|
|
95
|
+
source_syncs_total: "Sincronizaciones de fuentes (total)",
|
|
96
|
+
source_dedup_skips_total: "Skips por dedup de fuentes (total)",
|
|
97
|
+
okf_v3_files_written: "Archivos OKF v3 escritos",
|
|
90
98
|
};
|
|
91
99
|
function originLabel(origin) {
|
|
92
100
|
if (origin === "reflector")
|
package/dist/Store.d.ts
CHANGED
package/dist/Store.js
CHANGED
|
@@ -17,6 +17,7 @@ import { createDatabase } from "./sqlite-adapter.js";
|
|
|
17
17
|
export class Store {
|
|
18
18
|
db;
|
|
19
19
|
closed = false;
|
|
20
|
+
txDepth = 0;
|
|
20
21
|
constructor(options) {
|
|
21
22
|
this.db = createDatabase(options.path);
|
|
22
23
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -31,8 +32,19 @@ export class Store {
|
|
|
31
32
|
transaction(fn) {
|
|
32
33
|
if (this.closed)
|
|
33
34
|
throw new Error("Store is closed");
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (this.txDepth > 0)
|
|
36
|
+
return fn();
|
|
37
|
+
this.txDepth++;
|
|
38
|
+
try {
|
|
39
|
+
const tx = this.db.transaction(fn);
|
|
40
|
+
const result = tx();
|
|
41
|
+
this.txDepth--;
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
this.txDepth--;
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
36
48
|
}
|
|
37
49
|
exec(sql) {
|
|
38
50
|
if (this.closed)
|
package/dist/contract.d.ts
CHANGED
|
@@ -8,7 +8,54 @@ import { type KevinEnv } from "./env.js";
|
|
|
8
8
|
* core file allowed to call process cwd.
|
|
9
9
|
*/
|
|
10
10
|
export declare function resolveScanRoots(env?: KevinEnv): string[];
|
|
11
|
-
export declare const CONTRACT_VERSION =
|
|
11
|
+
export declare const CONTRACT_VERSION = 2;
|
|
12
|
+
export declare const CONTRACT_CORE_EXPORTS: readonly {
|
|
13
|
+
name: string;
|
|
14
|
+
kind: string;
|
|
15
|
+
}[];
|
|
16
|
+
export declare const CONTRACT_MCP_TOOLS: readonly string[];
|
|
17
|
+
export declare const CONTRACT_MCP_REFUSALS: readonly string[];
|
|
18
|
+
export declare const CONTRACT_SKILLS_LAYOUT: {
|
|
19
|
+
readonly canonical_dir: ".agents/skills";
|
|
20
|
+
readonly skill_dir: "kevin-knowledge";
|
|
21
|
+
readonly files: readonly ["SKILL.md", "references/*.md"];
|
|
22
|
+
readonly frontmatter_fields: readonly ["name", "description", "metadata.generator", "metadata.repo_id"];
|
|
23
|
+
readonly mirror_policy: "copy (not symlink) to .claude/skills and .cursor/skills when enabled";
|
|
24
|
+
};
|
|
25
|
+
export declare const CONTRACT_MIF_PROFILE: {
|
|
26
|
+
readonly envelope_version: 1;
|
|
27
|
+
readonly format: "mif";
|
|
28
|
+
readonly field_mapping: {
|
|
29
|
+
readonly entry_id: "id";
|
|
30
|
+
readonly statement: "content";
|
|
31
|
+
readonly type: "type";
|
|
32
|
+
readonly scope: "metadata.scope";
|
|
33
|
+
readonly fingerprint: "metadata.fingerprint";
|
|
34
|
+
readonly confidence: "metadata.confidence";
|
|
35
|
+
readonly created_at: "timestamp";
|
|
36
|
+
};
|
|
37
|
+
readonly redaction: "SECRET_PATTERNS (PII)";
|
|
38
|
+
readonly dedup: "content-hash (fingerprint)";
|
|
39
|
+
readonly vendor_extensions_preserved: true;
|
|
40
|
+
};
|
|
41
|
+
export declare const CONTRACT_MEMORY_SOURCES: {
|
|
42
|
+
readonly sources: readonly [{
|
|
43
|
+
readonly name: "opencode-plugin";
|
|
44
|
+
readonly precedence: 10;
|
|
45
|
+
}, {
|
|
46
|
+
readonly name: "claude-memory";
|
|
47
|
+
readonly precedence: 20;
|
|
48
|
+
}, {
|
|
49
|
+
readonly name: "codex-memories";
|
|
50
|
+
readonly precedence: 30;
|
|
51
|
+
}, {
|
|
52
|
+
readonly name: "opencode-native";
|
|
53
|
+
readonly precedence: 40;
|
|
54
|
+
}];
|
|
55
|
+
readonly dedup_rule: "lower precedence wins attribution; losers counted in source_dedup_skips_total";
|
|
56
|
+
readonly conflict_kind: "source_pair";
|
|
57
|
+
readonly precedence_order: readonly [10, 20, 30, 40];
|
|
58
|
+
};
|
|
12
59
|
export type Stability = "frozen" | "forward-only";
|
|
13
60
|
export interface ContractClause {
|
|
14
61
|
readonly id: string;
|
|
@@ -58,6 +105,16 @@ export declare const CONTRACT_CONFIG_ADDITIONS: readonly {
|
|
|
58
105
|
name: string;
|
|
59
106
|
since: string;
|
|
60
107
|
}[];
|
|
108
|
+
/**
|
|
109
|
+
* v2.0.0 (K16-004 / plan §5.1) — removed settings (retirements)
|
|
110
|
+
* Each entry carries `since` 2.0.0 and optional replacement pointer.
|
|
111
|
+
* Succession test treats these as LEGITIMATE removals via `removed` annotation.
|
|
112
|
+
*/
|
|
113
|
+
export declare const CONTRACT_REMOVED_CONFIG_KEYS: readonly {
|
|
114
|
+
name: string;
|
|
115
|
+
since: string;
|
|
116
|
+
replacement?: string;
|
|
117
|
+
}[];
|
|
61
118
|
/**
|
|
62
119
|
* v1.0.0 (K10-027 / plan §5.7) — the C-09 boundary addition. Stored is
|
|
63
120
|
* not trusted: anything reaching an artifact or a prompt is escaped at
|