@jmtrin/opencode-kevin 0.6.0 → 0.7.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 +582 -580
- package/dist/migrations/001_initial.sql +91 -91
- package/dist/migrations/003_v02_signal.sql +57 -57
- package/dist/migrations/004_v03_knowledge.sql +138 -138
- package/dist/migrations/005_v04_signal.sql +57 -57
- package/dist/migrations/006_v05_glassbox.sql +118 -118
- package/dist/migrations/007_v06_pull.sql +144 -144
- package/dist/migrations/008_v07_truth.sql +124 -0
- package/dist/plugin/ConflictDetector.d.ts +35 -0
- package/dist/plugin/ConflictDetector.js +283 -0
- package/dist/plugin/ConflictDetector.js.map +1 -0
- package/dist/plugin/ConventionMiner.d.ts +35 -0
- package/dist/plugin/ConventionMiner.js +243 -0
- package/dist/plugin/ConventionMiner.js.map +1 -0
- package/dist/plugin/Curator.js +45 -14
- package/dist/plugin/Curator.js.map +1 -1
- package/dist/plugin/MemoryService.d.ts +18 -0
- package/dist/plugin/MemoryService.js +92 -2
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.js +31 -2
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/Reflector.js +13 -0
- package/dist/plugin/Reflector.js.map +1 -1
- package/dist/plugin/RepoTruth.d.ts +80 -0
- package/dist/plugin/RepoTruth.js +600 -0
- package/dist/plugin/RepoTruth.js.map +1 -0
- package/dist/plugin/Retrospective.js +8 -0
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/index.d.ts +7 -2
- package/dist/plugin/index.js +135 -2
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/kevin_audit.d.ts +41 -1
- package/dist/plugin/kevin_audit.js +98 -3
- package/dist/plugin/kevin_audit.js.map +1 -1
- package/dist/plugin/kevin_conflicts.d.ts +9 -0
- package/dist/plugin/kevin_conflicts.js +51 -0
- package/dist/plugin/kevin_conflicts.js.map +1 -0
- package/dist/plugin/kevin_facts.d.ts +42 -0
- package/dist/plugin/kevin_facts.js +37 -0
- package/dist/plugin/kevin_facts.js.map +1 -0
- package/dist/plugin/metrics.d.ts +3 -3
- package/dist/plugin/metrics.js +12 -2
- package/dist/plugin/metrics.js.map +1 -1
- package/dist/plugin/replay-types.d.ts +2 -2
- package/migrations/001_initial.sql +91 -91
- package/migrations/003_v02_signal.sql +57 -57
- package/migrations/004_v03_knowledge.sql +138 -138
- package/migrations/005_v04_signal.sql +57 -57
- package/migrations/006_v05_glassbox.sql +118 -118
- package/migrations/007_v06_pull.sql +144 -144
- package/migrations/008_v07_truth.sql +124 -0
- package/package.json +3 -2
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { fingerprint } from "./fingerprint.js";
|
|
2
|
+
const DEFAULT_MIN_SUPPORT = 5;
|
|
3
|
+
export class ConventionMiner {
|
|
4
|
+
store;
|
|
5
|
+
memoryService;
|
|
6
|
+
projectId;
|
|
7
|
+
metrics;
|
|
8
|
+
constructor(store, memoryService, projectId, metrics) {
|
|
9
|
+
this.store = store;
|
|
10
|
+
this.memoryService = memoryService;
|
|
11
|
+
this.projectId = projectId;
|
|
12
|
+
this.metrics = metrics ?? null;
|
|
13
|
+
// `memoryService` is retained for emission (K7-012); mining itself is
|
|
14
|
+
// store-only.
|
|
15
|
+
void this.memoryService;
|
|
16
|
+
}
|
|
17
|
+
/** v0.7.0 (K7-010) — the `sequence` miner over successful tool_calls. */
|
|
18
|
+
mineSequence(minSupport = DEFAULT_MIN_SUPPORT) {
|
|
19
|
+
const rows = this.fetchToolCalls();
|
|
20
|
+
if (rows.length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
// Group successful calls by session in execution order (ts ASC, then id
|
|
23
|
+
// as a deterministic tie-break).
|
|
24
|
+
const bySession = new Map();
|
|
25
|
+
const sorted = [...rows]
|
|
26
|
+
.filter((r) => r.success === 1)
|
|
27
|
+
.sort((a, b) => a.session_id === b.session_id
|
|
28
|
+
? compareTs(a.ts, b.ts) || a.id.localeCompare(b.id)
|
|
29
|
+
: a.session_id.localeCompare(b.session_id));
|
|
30
|
+
for (const r of sorted) {
|
|
31
|
+
const list = bySession.get(r.session_id) ?? [];
|
|
32
|
+
list.push(r);
|
|
33
|
+
bySession.set(r.session_id, list);
|
|
34
|
+
}
|
|
35
|
+
const map = new Map();
|
|
36
|
+
const record = (key, statement, sessionId) => {
|
|
37
|
+
let cand = map.get(key);
|
|
38
|
+
if (!cand) {
|
|
39
|
+
cand = { key, statement, sessions: new Set() };
|
|
40
|
+
map.set(key, cand);
|
|
41
|
+
}
|
|
42
|
+
cand.sessions.add(sessionId);
|
|
43
|
+
};
|
|
44
|
+
for (const [sessionId, list] of bySession) {
|
|
45
|
+
if (list.length < 2)
|
|
46
|
+
continue;
|
|
47
|
+
for (let i = 0; i < list.length - 1; i++) {
|
|
48
|
+
const a = list[i];
|
|
49
|
+
const b = list[i + 1];
|
|
50
|
+
const aTok = tokenOf(a);
|
|
51
|
+
const bTok = tokenOf(b);
|
|
52
|
+
const key2 = `seq::${aTok}::${bTok}`;
|
|
53
|
+
const stmt2 = statementForSequence(aTok, bTok);
|
|
54
|
+
record(key2, stmt2, sessionId);
|
|
55
|
+
if (i + 2 < list.length) {
|
|
56
|
+
const c = list[i + 2];
|
|
57
|
+
const cTok = tokenOf(c);
|
|
58
|
+
const key3 = `seq::${aTok}::${bTok}::${cTok}`;
|
|
59
|
+
const stmt3 = statementForSequence3(aTok, bTok, cTok);
|
|
60
|
+
record(key3, stmt3, sessionId);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return this.materialize(map, minSupport, "sequence");
|
|
65
|
+
}
|
|
66
|
+
/** v0.7.0 (K7-011) — the `co_edit` miner over same-session file writes. */
|
|
67
|
+
mineCoEdit(minSupport = DEFAULT_MIN_SUPPORT) {
|
|
68
|
+
const rows = this.fetchToolCalls();
|
|
69
|
+
if (rows.length === 0)
|
|
70
|
+
return [];
|
|
71
|
+
// Successful file writes (broad: any success call mentioning a path) per
|
|
72
|
+
// session. Pairs span two different directory prefixes.
|
|
73
|
+
const bySession = new Map();
|
|
74
|
+
const sorted = [...rows]
|
|
75
|
+
.filter((r) => r.success === 1 && pathOf(r) !== null)
|
|
76
|
+
.sort((a, b) => a.session_id === b.session_id
|
|
77
|
+
? compareTs(a.ts, b.ts) || a.id.localeCompare(b.id)
|
|
78
|
+
: a.session_id.localeCompare(b.session_id));
|
|
79
|
+
for (const r of sorted) {
|
|
80
|
+
const list = bySession.get(r.session_id) ?? [];
|
|
81
|
+
list.push(pathOf(r));
|
|
82
|
+
bySession.set(r.session_id, list);
|
|
83
|
+
}
|
|
84
|
+
const map = new Map();
|
|
85
|
+
const record = (key, statement, sessionId) => {
|
|
86
|
+
let cand = map.get(key);
|
|
87
|
+
if (!cand) {
|
|
88
|
+
cand = { key, statement, sessions: new Set() };
|
|
89
|
+
map.set(key, cand);
|
|
90
|
+
}
|
|
91
|
+
cand.sessions.add(sessionId);
|
|
92
|
+
};
|
|
93
|
+
for (const [sessionId, paths] of bySession) {
|
|
94
|
+
const unique = [...new Set(paths)];
|
|
95
|
+
if (unique.length < 2)
|
|
96
|
+
continue;
|
|
97
|
+
// Bounded enumeration: a large session must not explode into an
|
|
98
|
+
// O(n²) pair set. Cap distinct prefixes per session at 40.
|
|
99
|
+
const limit = Math.min(unique.length, 40);
|
|
100
|
+
for (let i = 0; i < limit; i++) {
|
|
101
|
+
const pA = unique[i];
|
|
102
|
+
for (let j = i + 1; j < limit; j++) {
|
|
103
|
+
const pB = unique[j];
|
|
104
|
+
const prefixA = dirOf(pA);
|
|
105
|
+
const prefixB = dirOf(pB);
|
|
106
|
+
if (prefixA === prefixB)
|
|
107
|
+
continue;
|
|
108
|
+
const [lo, hi] = prefixA < prefixB ? [prefixA, prefixB] : [prefixB, prefixA];
|
|
109
|
+
const key = `coedit::${lo}::${hi}`;
|
|
110
|
+
const stmt = `every new file under ${lo}/ is accompanied by a change under ${hi}/`;
|
|
111
|
+
record(key, stmt, sessionId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return this.materialize(map, minSupport, "co_edit");
|
|
116
|
+
}
|
|
117
|
+
/** v0.7.0 (K7-012) — mine both kinds in one call. */
|
|
118
|
+
mine(minSupport = DEFAULT_MIN_SUPPORT) {
|
|
119
|
+
return [
|
|
120
|
+
...this.mineSequence(minSupport),
|
|
121
|
+
...this.mineCoEdit(minSupport),
|
|
122
|
+
].sort((a, b) => a.statement.localeCompare(b.statement));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* v0.7.0 (K7-012 / plan §5.4, D7-11) — emit mined(conventions) as `rule`
|
|
126
|
+
* memories. Returns the number of memories created or refreshed and
|
|
127
|
+
* increments `conventions_mined`. Runs on session.idle only, behind
|
|
128
|
+
* convention_mining_enabled. The fingerprint derives from the statement,
|
|
129
|
+
* so a re-mine of an unchanged convention collides and supersedes.
|
|
130
|
+
*/
|
|
131
|
+
emit(conventions) {
|
|
132
|
+
let emitted = 0;
|
|
133
|
+
for (const c of conventions) {
|
|
134
|
+
const derivedFingerprint = fingerprint(c.statement, this.projectId);
|
|
135
|
+
// save() derives the fingerprint-based supersede/collision path for
|
|
136
|
+
// `rule` types automatically, so re-emitting an unchanged convention
|
|
137
|
+
// refreshes rather than duplicates.
|
|
138
|
+
this.memoryService.save({
|
|
139
|
+
type: "rule",
|
|
140
|
+
origin: "pattern",
|
|
141
|
+
scope: "project",
|
|
142
|
+
projectId: this.projectId,
|
|
143
|
+
fingerprint: derivedFingerprint,
|
|
144
|
+
content: c.statement,
|
|
145
|
+
relevanceScore: 0.5,
|
|
146
|
+
sourceTool: "ConventionMiner",
|
|
147
|
+
});
|
|
148
|
+
this.metrics?.incr("conventions_mined", 1);
|
|
149
|
+
emitted += 1;
|
|
150
|
+
}
|
|
151
|
+
return emitted;
|
|
152
|
+
}
|
|
153
|
+
materialize(map, minSupport, kind) {
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const cand of map.values()) {
|
|
156
|
+
if (cand.sessions.size < minSupport)
|
|
157
|
+
continue;
|
|
158
|
+
out.push({
|
|
159
|
+
fingerprint: fingerprint(cand.statement, this.projectId),
|
|
160
|
+
statement: cand.statement,
|
|
161
|
+
support: cand.sessions.size,
|
|
162
|
+
kind,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Deterministic order: lexicographic by statement (ties are resolved).
|
|
166
|
+
return out.sort((a, b) => a.statement.localeCompare(b.statement));
|
|
167
|
+
}
|
|
168
|
+
fetchToolCalls() {
|
|
169
|
+
const rows = this.store
|
|
170
|
+
.prepare(`SELECT id, session_id, ts, tool, success, args_summary
|
|
171
|
+
FROM tool_calls
|
|
172
|
+
WHERE project_id = ?
|
|
173
|
+
ORDER BY session_id, ts`)
|
|
174
|
+
.all(this.projectId);
|
|
175
|
+
return rows;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Helpers — deterministic token/path extraction. Every value is normalized so
|
|
180
|
+
// the same underlying fact never collides via a JavaScript type difference.
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
/** `<tool>:<first path segment>` token for sequence mining. */
|
|
183
|
+
function tokenOf(r) {
|
|
184
|
+
const seg = firstPathSegment(r.args_summary);
|
|
185
|
+
return `${r.tool}:${seg}`;
|
|
186
|
+
}
|
|
187
|
+
function compareTs(a, b) {
|
|
188
|
+
return a.localeCompare(b);
|
|
189
|
+
}
|
|
190
|
+
/** First path segment of the first path-like argument, else "*". */
|
|
191
|
+
function firstPathSegment(argsSummary) {
|
|
192
|
+
const paths = pathSegments(argsSummary);
|
|
193
|
+
const first = paths[0];
|
|
194
|
+
if (!first)
|
|
195
|
+
return "*";
|
|
196
|
+
return first.split("/").filter(Boolean)[0] ?? "*";
|
|
197
|
+
}
|
|
198
|
+
/** All path segments found in args_summary, in appearance order. */
|
|
199
|
+
function pathSegments(argsSummary) {
|
|
200
|
+
const text = argsSummary ?? "";
|
|
201
|
+
const found = [];
|
|
202
|
+
// Try JSON object string values first.
|
|
203
|
+
try {
|
|
204
|
+
const parsed = JSON.parse(text);
|
|
205
|
+
if (typeof parsed === "object" &&
|
|
206
|
+
parsed !== null &&
|
|
207
|
+
!Array.isArray(parsed)) {
|
|
208
|
+
for (const v of Object.values(parsed)) {
|
|
209
|
+
if (typeof v === "string" && /\//.test(v) && !v.includes(" ")) {
|
|
210
|
+
found.push(v.replace(/\\/g, "/").toLowerCase().trim());
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
/* not JSON */
|
|
217
|
+
}
|
|
218
|
+
if (found.length === 0) {
|
|
219
|
+
const m = text.match(/[^\s"':,=]*\/[^\s"':,=]*/g);
|
|
220
|
+
for (const tok of m ?? []) {
|
|
221
|
+
found.push(tok.replace(/\\/g, "/").toLowerCase().trim());
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return found;
|
|
225
|
+
}
|
|
226
|
+
/** Directory of a path (drop the filename). `src/routes/user.ts` → `src/routes`. */
|
|
227
|
+
function dirOf(p) {
|
|
228
|
+
const segs = p.split("/").filter(Boolean);
|
|
229
|
+
segs.pop();
|
|
230
|
+
return segs.join("/") || "*";
|
|
231
|
+
}
|
|
232
|
+
/** A deterministic path for a call if it is a file write/read on disk. */
|
|
233
|
+
function pathOf(r) {
|
|
234
|
+
const segs = pathSegments(r.args_summary);
|
|
235
|
+
return segs[0] ?? null;
|
|
236
|
+
}
|
|
237
|
+
function statementForSequence(a, b) {
|
|
238
|
+
return `every ${a} is immediately followed by ${b}`;
|
|
239
|
+
}
|
|
240
|
+
function statementForSequence3(a, b, c) {
|
|
241
|
+
return `every ${a} is followed by ${b} and then ${c}`;
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=ConventionMiner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConventionMiner.js","sourceRoot":"","sources":["../../plugin/ConventionMiner.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin/Curator.js
CHANGED
|
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { MARKER_BEGIN, MARKER_END, } from "./ArtifactWriter.js";
|
|
4
4
|
import { computeConfidence } from "./confidence.js";
|
|
5
|
+
import { normalize } from "./fingerprint.js";
|
|
5
6
|
import { uuidv7 } from "./uuid.js";
|
|
6
7
|
const MAX_CANDIDATE_LINES = 20;
|
|
7
8
|
const MAX_CANDIDATE_CHARS = 4000;
|
|
@@ -127,20 +128,27 @@ export class Curator {
|
|
|
127
128
|
const candidates = this.candidates();
|
|
128
129
|
if (candidates.length === 0)
|
|
129
130
|
return [];
|
|
130
|
-
const
|
|
131
|
-
const currentBlock = extractBlock(
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
131
|
+
const readPlan = writer.plan(targetPath, this.renderBlock(candidates));
|
|
132
|
+
const currentBlock = extractBlock(readPlan.before);
|
|
133
|
+
// v0.7.0 (K7-013 / plan §4, D6-09) — de-duplicate against the WHOLE
|
|
134
|
+
// file, not just the region between Kevin's markers. A convention the
|
|
135
|
+
// user already wrote in their own words in their own section must not
|
|
136
|
+
// be proposed back to them. The comparison is over the same normalized
|
|
137
|
+
// tokens the fingerprint uses — no new similarity metric (D7-11).
|
|
138
|
+
const entireFile = wholeFileLines(readPlan.before);
|
|
139
|
+
const freshCandidates = candidates.filter((candidate) => !entireFile.has(normalizeBullet(candidate.line)));
|
|
140
|
+
if (freshCandidates.length === 0 && currentBlock === "")
|
|
141
|
+
return [];
|
|
142
|
+
const newBlock = this.renderBlock(freshCandidates);
|
|
143
|
+
const newLines = newBlock.split("\n").filter((l) => l !== "");
|
|
144
|
+
const mergedBlock = newLines.length === 0
|
|
145
|
+
? currentBlock
|
|
146
|
+
: currentBlock === ""
|
|
147
|
+
? `${newLines.join("\n")}\n`
|
|
143
148
|
: `${currentBlock}${currentBlock.endsWith("\n") ? "" : "\n"}${newLines.join("\n")}\n`;
|
|
149
|
+
// v0.7.0 (K7-013) — nothing new to propose: every candidate is already
|
|
150
|
+
// in the file (inside or outside the markers). A vacuous proposal would
|
|
151
|
+
// be noise; return none.
|
|
144
152
|
const plan = writer.plan(targetPath, mergedBlock);
|
|
145
153
|
const prior = this.store
|
|
146
154
|
.prepare(`SELECT id FROM curation_proposals
|
|
@@ -150,7 +158,7 @@ export class Curator {
|
|
|
150
158
|
for (const row of prior) {
|
|
151
159
|
this.transition(row.id, "supersede");
|
|
152
160
|
}
|
|
153
|
-
const memoryIds =
|
|
161
|
+
const memoryIds = freshCandidates.map((c) => c.memoryId);
|
|
154
162
|
const id = uuidv7();
|
|
155
163
|
this.store
|
|
156
164
|
.prepare(`INSERT INTO curation_proposals
|
|
@@ -249,4 +257,27 @@ function extractBlock(before) {
|
|
|
249
257
|
.replace(/^\r?\n/, "")
|
|
250
258
|
.replace(/\r?\n$/, "");
|
|
251
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* v0.7.0 (K7-013 / plan §4) — the normalized set of bullet statements across
|
|
262
|
+
* the WHOLE file, including any the user wrote outside Kevin's markers. Only
|
|
263
|
+
* markdown bullets (`- ...`) are considered: prose and the marker lines are
|
|
264
|
+
* never a candidate to propose back.
|
|
265
|
+
*/
|
|
266
|
+
function wholeFileLines(before) {
|
|
267
|
+
const set = new Set();
|
|
268
|
+
for (const line of before.split(/\r?\n/)) {
|
|
269
|
+
if (line.trim().startsWith("-")) {
|
|
270
|
+
set.add(normalizeBullet(line));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return set;
|
|
274
|
+
}
|
|
275
|
+
/** Normalize a rendered bullet for de-duplication — strip the marker prefix. */
|
|
276
|
+
function normalizeBullet(line) {
|
|
277
|
+
const statement = line
|
|
278
|
+
.replace(/^\s*-\s+/, "")
|
|
279
|
+
.trim()
|
|
280
|
+
.replace(/\s+\((?:verified|feedback)[^)]*\)\s*$/i, "");
|
|
281
|
+
return normalize(statement);
|
|
282
|
+
}
|
|
252
283
|
//# sourceMappingURL=Curator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Curator.js","sourceRoot":"","sources":["../../plugin/Curator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAEN,YAAY,EACZ,UAAU,GACV,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"Curator.js","sourceRoot":"","sources":["../../plugin/Curator.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,6 +54,13 @@ export interface Memory {
|
|
|
54
54
|
/** v0.5.0 (K5-010 / plan §5.3, D5-02) — human judgement counters,
|
|
55
55
|
* folded into confidence by computeConfidence. */
|
|
56
56
|
feedbackNegative?: number;
|
|
57
|
+
/** v0.7.0 (K7-008 / plan §5.3, D7-03) — de-ranking penalty in
|
|
58
|
+
* [0, 0.5], clamped by applyTruthPenalty. Multiplies rankScore as
|
|
59
|
+
* `(1 - truthPenalty)`. Defaults to 0. */
|
|
60
|
+
truthPenalty?: number;
|
|
61
|
+
/** v0.7.0 (K7-008 / plan §5.3) — when the memory was first contradicted
|
|
62
|
+
* (ISO/sqlite time). Null when never contradicted. */
|
|
63
|
+
contradictedAt?: string | null;
|
|
57
64
|
}
|
|
58
65
|
export interface SaveInput {
|
|
59
66
|
type: MemoryType;
|
|
@@ -150,6 +157,7 @@ export declare class MemoryService {
|
|
|
150
157
|
private _hasRecurrenceColumn;
|
|
151
158
|
private hasIgnoredColumn;
|
|
152
159
|
private hasCuratedColumn;
|
|
160
|
+
private hasTruthColumns;
|
|
153
161
|
save(input: SaveInput): string;
|
|
154
162
|
/**
|
|
155
163
|
* v0.6.0 (K6-011 / plan §5.4) — mark memories as curated. Batch in a
|
|
@@ -167,6 +175,16 @@ export declare class MemoryService {
|
|
|
167
175
|
*/
|
|
168
176
|
getByFingerprint(fingerprint: string, type?: MemoryType): Memory | null;
|
|
169
177
|
update(id: string, fields: Partial<Memory>): void;
|
|
178
|
+
/**
|
|
179
|
+
* v0.7.0 (K7-008 / plan §5.3, D7-03) — apply a bounded truth penalty.
|
|
180
|
+
* Clamps `penalty` to [0, 0.5], writes `truth_penalty` and `contradicted_at`,
|
|
181
|
+
* and increments `memories_contradicted` ONLY when the value moves from 0
|
|
182
|
+
* to non-zero (a second penalty on the same memory does not re-count).
|
|
183
|
+
* It NEVER writes `status` — contradiction de-ranks; it never deletes
|
|
184
|
+
* (Principle 24). `reason` is the human-readable explanation surfaced by
|
|
185
|
+
* `kevin_facts`; the caller persists it in the `memory_conflicts` row.
|
|
186
|
+
*/
|
|
187
|
+
applyTruthPenalty(memoryId: string, penalty: number, reason: string): void;
|
|
170
188
|
delete(id: string): void;
|
|
171
189
|
/** v0.1.x behavior — returns full `Memory` rows. */
|
|
172
190
|
query(input: QueryInput & {
|
|
@@ -112,6 +112,25 @@ function hasCuratedColumn(store) {
|
|
|
112
112
|
return false;
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* v0.7.0 (K7-008 / migration 008) — cached per-store probe for the 008-only
|
|
117
|
+
* `truth_penalty` column (pre-008 DBs must not reference it). Same
|
|
118
|
+
* positive-only caching discipline as `curated` (K6-001a).
|
|
119
|
+
*/
|
|
120
|
+
const truthColumnsCache = new WeakMap();
|
|
121
|
+
function hasTruthColumns(store) {
|
|
122
|
+
const cached = truthColumnsCache.get(store);
|
|
123
|
+
if (cached === true)
|
|
124
|
+
return true;
|
|
125
|
+
try {
|
|
126
|
+
store.prepare("SELECT truth_penalty FROM memories LIMIT 1").get();
|
|
127
|
+
truthColumnsCache.set(store, true);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
115
134
|
/**
|
|
116
135
|
* v0.5.0 (K5-009 / plan §5.3) — the 006-only columns are appended when the
|
|
117
136
|
* migration has run (same probe as the `ignored = 0` retrieval filter).
|
|
@@ -122,9 +141,12 @@ function rowSelect(store) {
|
|
|
122
141
|
? `${MEMORY_ROW_SELECT}, ignored, superseded_by,
|
|
123
142
|
feedback_positive, feedback_negative`
|
|
124
143
|
: MEMORY_ROW_SELECT;
|
|
125
|
-
|
|
144
|
+
const withCurated = hasCuratedColumn(store)
|
|
126
145
|
? `${base}, curated, curated_at, inferable`
|
|
127
146
|
: base;
|
|
147
|
+
return hasTruthColumns(store)
|
|
148
|
+
? `${withCurated}, truth_penalty, contradicted_at`
|
|
149
|
+
: withCurated;
|
|
128
150
|
}
|
|
129
151
|
function mapRow(row, score) {
|
|
130
152
|
const mem = {
|
|
@@ -171,6 +193,11 @@ function mapRow(row, score) {
|
|
|
171
193
|
: row.inferable === 0
|
|
172
194
|
? "non_inferable"
|
|
173
195
|
: null,
|
|
196
|
+
// v0.7.0 (K7-008 / plan §5.3, D7-03) — de-ranking state. `truth_penalty`
|
|
197
|
+
// defaults to 0 (a pre-008 row without the column reads 0), and
|
|
198
|
+
// `contradicted_at` is null until the first contradiction.
|
|
199
|
+
truthPenalty: row.truth_penalty ?? 0,
|
|
200
|
+
contradictedAt: row.contradicted_at ?? null,
|
|
174
201
|
};
|
|
175
202
|
if (score !== undefined) {
|
|
176
203
|
if (!mem.metadata)
|
|
@@ -248,6 +275,12 @@ export class MemoryService {
|
|
|
248
275
|
hasCuratedColumn() {
|
|
249
276
|
return hasCuratedColumn(this.store);
|
|
250
277
|
}
|
|
278
|
+
// v0.7.0 (K7-008 / plan §5.3) — cached column probe for pre-008 DBs
|
|
279
|
+
// (which lack `truth_penalty`); the retrieval SELECTs must not reference
|
|
280
|
+
// the column there, or rankScore cannot see the de-ranking factor.
|
|
281
|
+
hasTruthColumns() {
|
|
282
|
+
return hasTruthColumns(this.store);
|
|
283
|
+
}
|
|
251
284
|
save(input) {
|
|
252
285
|
const scope = input.scope ?? "project";
|
|
253
286
|
const relevanceScore = input.relevanceScore ?? 0.5;
|
|
@@ -493,6 +526,48 @@ export class MemoryService {
|
|
|
493
526
|
.prepare(`UPDATE memories SET ${cols.join(", ")} WHERE id = ?`)
|
|
494
527
|
.run(...vals);
|
|
495
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* v0.7.0 (K7-008 / plan §5.3, D7-03) — apply a bounded truth penalty.
|
|
531
|
+
* Clamps `penalty` to [0, 0.5], writes `truth_penalty` and `contradicted_at`,
|
|
532
|
+
* and increments `memories_contradicted` ONLY when the value moves from 0
|
|
533
|
+
* to non-zero (a second penalty on the same memory does not re-count).
|
|
534
|
+
* It NEVER writes `status` — contradiction de-ranks; it never deletes
|
|
535
|
+
* (Principle 24). `reason` is the human-readable explanation surfaced by
|
|
536
|
+
* `kevin_facts`; the caller persists it in the `memory_conflicts` row.
|
|
537
|
+
*/
|
|
538
|
+
applyTruthPenalty(memoryId, penalty, reason) {
|
|
539
|
+
const clamped = Math.max(0, Math.min(0.5, penalty));
|
|
540
|
+
const row = this.store
|
|
541
|
+
.prepare("SELECT truth_penalty FROM memories WHERE id = ?")
|
|
542
|
+
.get(memoryId);
|
|
543
|
+
if (!row)
|
|
544
|
+
return;
|
|
545
|
+
const current = Number(row.truth_penalty ?? 0);
|
|
546
|
+
const hadPenalty = current > 0;
|
|
547
|
+
const nowHasPenalty = clamped > 0;
|
|
548
|
+
// `reason` is accepted for interface parity with plan §5.3; the caller
|
|
549
|
+
// records it in the conflict row, not here.
|
|
550
|
+
void reason;
|
|
551
|
+
const stamp = nowHasPenalty ? new Date().toISOString() : null;
|
|
552
|
+
this.store
|
|
553
|
+
.prepare(`UPDATE memories
|
|
554
|
+
SET truth_penalty = ?,
|
|
555
|
+
contradicted_at = CASE
|
|
556
|
+
WHEN ? IS NULL THEN NULL
|
|
557
|
+
ELSE COALESCE(contradicted_at, ?)
|
|
558
|
+
END
|
|
559
|
+
WHERE id = ?`)
|
|
560
|
+
.run(clamped, stamp, stamp, memoryId);
|
|
561
|
+
if (nowHasPenalty && !hadPenalty) {
|
|
562
|
+
this.metrics?.incr("memories_contradicted", 1);
|
|
563
|
+
}
|
|
564
|
+
// v0.7.0 (K7-008 / D7-03) — recovery: a penalty lifted back to 0
|
|
565
|
+
// (the repo no longer contradicts the memory) mirrors the counter,
|
|
566
|
+
// which the 008 post-apply hook re-derives as COUNT(truth_penalty > 0).
|
|
567
|
+
if (!nowHasPenalty && hadPenalty) {
|
|
568
|
+
this.metrics?.incr("memories_contradicted", -1);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
496
571
|
delete(id) {
|
|
497
572
|
this.store.prepare("DELETE FROM memories WHERE id = ?").run(id);
|
|
498
573
|
}
|
|
@@ -589,6 +664,10 @@ export class MemoryService {
|
|
|
589
664
|
if (this.hasCuratedColumn()) {
|
|
590
665
|
sql += ", curated, curated_at, inferable";
|
|
591
666
|
}
|
|
667
|
+
// v0.7.0 (K7-008) — 008-only truth columns, appended when present.
|
|
668
|
+
if (this.hasTruthColumns()) {
|
|
669
|
+
sql += ", truth_penalty, contradicted_at";
|
|
670
|
+
}
|
|
592
671
|
sql += `
|
|
593
672
|
FROM memories
|
|
594
673
|
WHERE (expires_at IS NULL OR expires_at > datetime('now'))`;
|
|
@@ -628,6 +707,11 @@ export class MemoryService {
|
|
|
628
707
|
if (this.hasCuratedColumn()) {
|
|
629
708
|
sql += ", m.curated, m.curated_at, m.inferable";
|
|
630
709
|
}
|
|
710
|
+
// v0.7.0 (K7-008) — 008-only truth columns, appended when present so
|
|
711
|
+
// rankScore can apply the de-ranking factor in retrieval.
|
|
712
|
+
if (this.hasTruthColumns()) {
|
|
713
|
+
sql += ", m.truth_penalty, m.contradicted_at";
|
|
714
|
+
}
|
|
631
715
|
sql += `,
|
|
632
716
|
bm25(memories_fts) AS score
|
|
633
717
|
FROM memories_fts
|
|
@@ -1023,7 +1107,13 @@ function rankScore(mem, nowMs, deterministic) {
|
|
|
1023
1107
|
// the recency factor at 1.0 so ordering depends only on content
|
|
1024
1108
|
// relevance and origin boost, never on the wall clock.
|
|
1025
1109
|
const recencyDecay = deterministic ? 1 : RECENCY_DECAY_PER_DAY ** ageDays;
|
|
1026
|
-
|
|
1110
|
+
// v0.7.0 (K7-008 / plan §5.3, D7-04) — trailing multiplicative factor,
|
|
1111
|
+
// applied AFTER the existing chain. At the default (truthPenalty = 0) the
|
|
1112
|
+
// expression reduces to the v0.6.0 one exactly. rankScore returns a
|
|
1113
|
+
// NEGATIVE score for BM25 rows (more negative = better), so scaling by a
|
|
1114
|
+
// factor in (0.5, 1] moves a row toward zero — i.e. toward worse — which
|
|
1115
|
+
// is the intended de-ranking direction.
|
|
1116
|
+
return base * originBoost(mem) * recencyDecay * (1 - (mem.truthPenalty ?? 0));
|
|
1027
1117
|
}
|
|
1028
1118
|
function rankCompare(a, b, nowMs, deterministic) {
|
|
1029
1119
|
const ra = rankScore(a, nowMs, deterministic);
|