@jmtrin/opencode-kevin 0.2.0 → 0.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.
- package/README.md +108 -35
- package/dist/migrations/004_v03_knowledge.sql +138 -0
- package/dist/migrations/005_v04_signal.sql +57 -0
- package/dist/plugin/CausalChain.d.ts +22 -0
- package/dist/plugin/CausalChain.js +179 -0
- package/dist/plugin/CausalChain.js.map +1 -0
- package/dist/plugin/ContextInjector.d.ts +89 -1
- package/dist/plugin/ContextInjector.js +276 -71
- package/dist/plugin/ContextInjector.js.map +1 -1
- package/dist/plugin/InjectionLedger.d.ts +85 -0
- package/dist/plugin/InjectionLedger.js +189 -0
- package/dist/plugin/InjectionLedger.js.map +1 -0
- package/dist/plugin/LessonFixer.d.ts +44 -0
- package/dist/plugin/LessonFixer.js +46 -0
- package/dist/plugin/LessonFixer.js.map +1 -0
- package/dist/plugin/MemoryService.d.ts +123 -2
- package/dist/plugin/MemoryService.js +439 -31
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.js +22 -0
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/QualityGate.d.ts +71 -0
- package/dist/plugin/QualityGate.js +78 -0
- package/dist/plugin/QualityGate.js.map +1 -0
- package/dist/plugin/Reflector.d.ts +33 -0
- package/dist/plugin/Reflector.js +126 -32
- package/dist/plugin/Reflector.js.map +1 -1
- package/dist/plugin/Retrospective.js +10 -1
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/ToolCallObserver.d.ts +1 -0
- package/dist/plugin/ToolCallObserver.js +16 -9
- package/dist/plugin/ToolCallObserver.js.map +1 -1
- package/dist/plugin/confidence.d.ts +6 -0
- package/dist/plugin/confidence.js +23 -0
- package/dist/plugin/confidence.js.map +1 -0
- package/dist/plugin/index.d.ts +5 -0
- package/dist/plugin/index.js +336 -44
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/kevin_why.d.ts +23 -0
- package/dist/plugin/kevin_why.js +108 -0
- package/dist/plugin/kevin_why.js.map +1 -0
- package/dist/plugin/memory-format.d.ts +12 -0
- package/dist/plugin/memory-format.js +45 -5
- package/dist/plugin/memory-format.js.map +1 -1
- package/dist/plugin/metrics.d.ts +7 -1
- package/dist/plugin/metrics.js +19 -0
- package/dist/plugin/metrics.js.map +1 -1
- package/dist/plugin/okf-export.d.ts +3 -0
- package/dist/plugin/okf-export.js +127 -0
- package/dist/plugin/okf-export.js.map +1 -0
- package/dist/plugin/okf-import.d.ts +76 -0
- package/dist/plugin/okf-import.js +272 -0
- package/dist/plugin/okf-import.js.map +1 -0
- package/dist/plugin/query-tokenizer.d.ts +13 -0
- package/dist/plugin/query-tokenizer.js +86 -0
- package/dist/plugin/query-tokenizer.js.map +1 -0
- package/migrations/004_v03_knowledge.sql +138 -0
- package/migrations/005_v04_signal.sql +57 -0
- package/package.json +1 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { fingerprint as computeFingerprint } from "./fingerprint.js";
|
|
2
|
+
import { uuidv7 } from "./uuid.js";
|
|
3
|
+
const PAIR_RE = /^([a-z_]+):\s*(.*)$/i;
|
|
4
|
+
const ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5
|
+
/**
|
|
6
|
+
* v0.3.0 fix — Clean state-machine parser for the frontmatter bundle
|
|
7
|
+
* format produced by `okf-export.ts::exportOkf`. Each entry is:
|
|
8
|
+
*
|
|
9
|
+
* ---
|
|
10
|
+
* id: <uuid>
|
|
11
|
+
* type: <decision|rule|pattern>
|
|
12
|
+
* confidence: 0.70
|
|
13
|
+
* evidence_count: 2
|
|
14
|
+
* last_verified_at: 2026-07-25 12:34:56
|
|
15
|
+
* fingerprint: <hex>
|
|
16
|
+
* created: 2026-07-25 12:00:00
|
|
17
|
+
* scope: project
|
|
18
|
+
* ---
|
|
19
|
+
*
|
|
20
|
+
* <content body, may span multiple lines, may include `---` lines
|
|
21
|
+
* within — the body terminator is the NEXT top-level `---` followed
|
|
22
|
+
* by an `id:` line, or EOF>
|
|
23
|
+
*
|
|
24
|
+
* The previous implementation only ever flagged `inFm = true` once and
|
|
25
|
+
* never reset `contentStarted` between entries, so 2..N entries were
|
|
26
|
+
* silently dropped (bug #1). This rewrite handles arbitrary numbers of
|
|
27
|
+
* consecutive frontmatter sections.
|
|
28
|
+
*/
|
|
29
|
+
export function parseMarkdownBundle(text) {
|
|
30
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
31
|
+
const entries = [];
|
|
32
|
+
let i = 0;
|
|
33
|
+
let fm = null;
|
|
34
|
+
let body = [];
|
|
35
|
+
// True when we have just seen a `---` opener and are collecting
|
|
36
|
+
// `key: value` pairs until the matching `---` closer.
|
|
37
|
+
const isFmKey = (s) => PAIR_RE.test(s) || s.trim() === "";
|
|
38
|
+
while (i < lines.length) {
|
|
39
|
+
const line = lines[i];
|
|
40
|
+
// Seek an opening `---`.
|
|
41
|
+
if (line.trim() !== "---") {
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Trivially empty bundle (just opener/closer with no keys) — skip.
|
|
46
|
+
if (lines[i + 1]?.trim() === "---") {
|
|
47
|
+
i += 2;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Begin frontmatter collection.
|
|
51
|
+
fm = {};
|
|
52
|
+
body = [];
|
|
53
|
+
i++;
|
|
54
|
+
let closed = false;
|
|
55
|
+
while (i < lines.length) {
|
|
56
|
+
const cur = lines[i];
|
|
57
|
+
if (cur.trim() === "---") {
|
|
58
|
+
closed = true;
|
|
59
|
+
i++;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
const m = cur.match(PAIR_RE);
|
|
63
|
+
if (m) {
|
|
64
|
+
fm[m[1].toLowerCase()] = m[2].trim();
|
|
65
|
+
}
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
if (!closed)
|
|
69
|
+
break;
|
|
70
|
+
if (!fm.id)
|
|
71
|
+
continue;
|
|
72
|
+
// Collect body until the next top-level `---` opener. The opener
|
|
73
|
+
// is recognizable as `---` followed by an `id:` pair in the next
|
|
74
|
+
// frontmatter block; we look ahead conservatively: a `---` line
|
|
75
|
+
// immediately followed by a `key:` line is an opener, anything
|
|
76
|
+
// else is body content.
|
|
77
|
+
while (i < lines.length) {
|
|
78
|
+
const cur = lines[i];
|
|
79
|
+
if (cur.trim() === "---") {
|
|
80
|
+
// peek the NEXT non-empty line — if it looks like a PAIR,
|
|
81
|
+
// this `---` is an opener for the next entry, so stop body
|
|
82
|
+
// here without consuming.
|
|
83
|
+
let j = i + 1;
|
|
84
|
+
while (j < lines.length && lines[j].trim() === "")
|
|
85
|
+
j++;
|
|
86
|
+
if (j < lines.length && isFmKey(lines[j]) && PAIR_RE.test(lines[j])) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
// Otherwise treat as body content (e.g. thematic break).
|
|
90
|
+
body.push(cur);
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
body.push(cur);
|
|
95
|
+
i++;
|
|
96
|
+
}
|
|
97
|
+
const content = body.join("\n").trim();
|
|
98
|
+
if (!content)
|
|
99
|
+
continue;
|
|
100
|
+
const type = (fm.type ?? "context").toLowerCase();
|
|
101
|
+
const id = ID_RE.test(fm.id) ? fm.id : uuidv7();
|
|
102
|
+
const fp = fm.fingerprint ?? null;
|
|
103
|
+
const evidenceCount = Number.parseInt(fm.evidence_count ?? "0", 10) || 0;
|
|
104
|
+
const recurrenceCount = Number.parseInt(fm.recurrence_count ?? "0", 10) || 0;
|
|
105
|
+
const lastVerified = fm.last_verified_at ?? null;
|
|
106
|
+
entries.push({
|
|
107
|
+
id,
|
|
108
|
+
type,
|
|
109
|
+
content,
|
|
110
|
+
fingerprint: fp,
|
|
111
|
+
evidence_count: evidenceCount,
|
|
112
|
+
recurrence_count: recurrenceCount,
|
|
113
|
+
last_verified_at: lastVerified,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* v0.3.0 fix — Fallback parser for the markdown-style `##` heading
|
|
120
|
+
* format produced by `okf-export.ts::exportMarkdown`. The previous
|
|
121
|
+
* version extracted `fingerprint: null` for every entry (regex was
|
|
122
|
+
* pinned to 16 hex chars while actual fingerprints are variable
|
|
123
|
+
* length) and contaminated `content` with the heading line and
|
|
124
|
+
* metadata bullets. This version cleanly separates metadata bullets
|
|
125
|
+
* (looking for `**ID:**`, `**Fingerprint:**`, `**Evidence count:**`,
|
|
126
|
+
* `**Last verified:**`, `**Scope:**` prefixes) from the content
|
|
127
|
+
* body, which starts after the first blank line following the bullet
|
|
128
|
+
* block and runs until the trailing `---` separator (or EOF).
|
|
129
|
+
*/
|
|
130
|
+
export function parseMarkdownHeadings(text) {
|
|
131
|
+
const normalized = text.replace(/\r\n/g, "\n");
|
|
132
|
+
// Split on `## ` headings; first chunk is the document preamble.
|
|
133
|
+
const chunks = normalized.split(/^##\s+/m).slice(1);
|
|
134
|
+
const entries = [];
|
|
135
|
+
for (const chunk of chunks) {
|
|
136
|
+
const lines = chunk.split("\n");
|
|
137
|
+
const typeMatch = lines[0]?.match(/^([A-Za-z]+):\s*/);
|
|
138
|
+
const type = typeMatch ? typeMatch[1].toLowerCase() : "context";
|
|
139
|
+
const fm = {};
|
|
140
|
+
let bodyStart = -1;
|
|
141
|
+
// True once at least one `- **Key:**` bullet was consumed; the
|
|
142
|
+
// body starts at the FIRST blank line AFTER the bullet block.
|
|
143
|
+
// Blank lines before any bullet (e.g. right after the `##` heading)
|
|
144
|
+
// must be skipped, not treated as body delimiters.
|
|
145
|
+
let sawBullet = false;
|
|
146
|
+
for (let k = 1; k < lines.length; k++) {
|
|
147
|
+
const ln = lines[k];
|
|
148
|
+
const bulletId = ln.match(/^- \*\*ID:\*\*\s*`([^`]+)`/);
|
|
149
|
+
const bulletFp = ln.match(/^- \*\*Fingerprint:\*\*\s*`([a-f0-9]+)`/i);
|
|
150
|
+
const bulletEv = ln.match(/^- \*\*Evidence count:\*\*\s*(\d+)/);
|
|
151
|
+
const bulletRec = ln.match(/^- \*\*Recurrence count:\*\*\s*(\d+)/);
|
|
152
|
+
const bulletLv = ln.match(/^- \*\*Last verified:\*\*\s*(.+)$/);
|
|
153
|
+
if (bulletId) {
|
|
154
|
+
fm.id = bulletId[1];
|
|
155
|
+
sawBullet = true;
|
|
156
|
+
}
|
|
157
|
+
else if (bulletFp) {
|
|
158
|
+
fm.fingerprint = bulletFp[1];
|
|
159
|
+
sawBullet = true;
|
|
160
|
+
}
|
|
161
|
+
else if (bulletEv) {
|
|
162
|
+
fm.evidence_count = bulletEv[1];
|
|
163
|
+
sawBullet = true;
|
|
164
|
+
}
|
|
165
|
+
else if (bulletRec) {
|
|
166
|
+
fm.recurrence_count = bulletRec[1];
|
|
167
|
+
sawBullet = true;
|
|
168
|
+
}
|
|
169
|
+
else if (bulletLv) {
|
|
170
|
+
fm.last_verified_at = bulletLv[1].trim();
|
|
171
|
+
sawBullet = true;
|
|
172
|
+
}
|
|
173
|
+
else if (ln.trim() === "") {
|
|
174
|
+
// First blank line after bullet block — body starts here.
|
|
175
|
+
if (sawBullet) {
|
|
176
|
+
bodyStart = k + 1;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
// Leading blank line (right after the heading) — skip.
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Other bullets (e.g. `- **Confidence:**`, `- **Scope:**`)
|
|
183
|
+
// are not captured — skip them; the bullet block ends at
|
|
184
|
+
// the next blank line.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (bodyStart < 0)
|
|
188
|
+
bodyStart = 1;
|
|
189
|
+
const bodyLines = [];
|
|
190
|
+
for (let k = bodyStart; k < lines.length; k++) {
|
|
191
|
+
const ln = lines[k];
|
|
192
|
+
if (ln.trim() === "---")
|
|
193
|
+
break;
|
|
194
|
+
bodyLines.push(ln);
|
|
195
|
+
}
|
|
196
|
+
const content = bodyLines.join("\n").trim();
|
|
197
|
+
if (!content)
|
|
198
|
+
continue;
|
|
199
|
+
const id = fm.id && ID_RE.test(fm.id) ? fm.id : uuidv7();
|
|
200
|
+
entries.push({
|
|
201
|
+
id,
|
|
202
|
+
type,
|
|
203
|
+
content,
|
|
204
|
+
fingerprint: fm.fingerprint ?? null,
|
|
205
|
+
evidence_count: Number.parseInt(fm.evidence_count ?? "0", 10) || 0,
|
|
206
|
+
recurrence_count: Number.parseInt(fm.recurrence_count ?? "0", 10) || 0,
|
|
207
|
+
last_verified_at: fm.last_verified_at ?? null,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return entries;
|
|
211
|
+
}
|
|
212
|
+
const IMPORT_ALLOWED_TYPES = new Set([
|
|
213
|
+
"decision",
|
|
214
|
+
"rule",
|
|
215
|
+
"pattern",
|
|
216
|
+
"context",
|
|
217
|
+
]);
|
|
218
|
+
/**
|
|
219
|
+
* v0.3.0 fix — Ingest a bundle (frontmatter OR markdown) into the
|
|
220
|
+
* local SQLite store as `context` memories with `origin='imported'`.
|
|
221
|
+
*
|
|
222
|
+
* Fixes over the v0.3.0 baseline:
|
|
223
|
+
* * Multi-entry bundles now produce N imports instead of N=1 — the
|
|
224
|
+
* parser bug is closed.
|
|
225
|
+
* * `ParsedEntry.evidence_count` and `last_verified_at` are
|
|
226
|
+
* threaded through so causal confidence survives a round-trip.
|
|
227
|
+
* * `ImportResult.superseded` is populated using
|
|
228
|
+
* `countSupersedeCandidates`, which mirrors the supersede logic
|
|
229
|
+
* in `MemoryService.save()`. Previously it was hard-coded to 0.
|
|
230
|
+
* * Generated ids use `uuidv7()` (the project's id generator)
|
|
231
|
+
* instead of `crypto.randomUUID()` for consistency.
|
|
232
|
+
*/
|
|
233
|
+
export function importOkf(bundle, memoryService) {
|
|
234
|
+
let entries = parseMarkdownBundle(bundle);
|
|
235
|
+
if (entries.length === 0) {
|
|
236
|
+
entries = parseMarkdownHeadings(bundle);
|
|
237
|
+
}
|
|
238
|
+
let imported = 0;
|
|
239
|
+
let superseded = 0;
|
|
240
|
+
for (const entry of entries) {
|
|
241
|
+
if (!IMPORT_ALLOWED_TYPES.has(entry.type))
|
|
242
|
+
continue;
|
|
243
|
+
const fp = entry.fingerprint ?? computeFingerprint(entry.content, undefined);
|
|
244
|
+
// Count rows that save() will mark as superseded (decision/rule
|
|
245
|
+
// with the same fingerprint). The supersede update itself runs
|
|
246
|
+
// inside MemoryService.save() in a single transaction; we count
|
|
247
|
+
// here to surface the value to the caller.
|
|
248
|
+
superseded += memoryService.countSupersedeCandidates(entry.type, fp, null);
|
|
249
|
+
memoryService.save({
|
|
250
|
+
type: entry.type,
|
|
251
|
+
// BUG-008 — preserve the bundle id so a round-trip (export →
|
|
252
|
+
// import) keeps memory identity and `getById` stays stable.
|
|
253
|
+
id: entry.id,
|
|
254
|
+
// BUG-009 — the content is the bundle body verbatim. The old
|
|
255
|
+
// code appended `[imported evidence_count=N, ...]`, which the
|
|
256
|
+
// ContextInjector later injected verbatim into model prompts;
|
|
257
|
+
// the values travel via the typed fields below instead.
|
|
258
|
+
content: entry.content,
|
|
259
|
+
scope: "project",
|
|
260
|
+
origin: "imported",
|
|
261
|
+
fingerprint: fp,
|
|
262
|
+
evidenceCount: entry.evidence_count > 0 ? entry.evidence_count : undefined,
|
|
263
|
+
// BUG-008 — restore the recurrence demotion so the re-imported
|
|
264
|
+
// copy keeps the v0.4.0 two-sided confidence of the source.
|
|
265
|
+
recurrenceCount: entry.recurrence_count > 0 ? entry.recurrence_count : undefined,
|
|
266
|
+
lastVerifiedAt: entry.last_verified_at ?? undefined,
|
|
267
|
+
});
|
|
268
|
+
imported++;
|
|
269
|
+
}
|
|
270
|
+
return { imported, superseded };
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=okf-import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"okf-import.js","sourceRoot":"","sources":["../../plugin/okf-import.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* K4-013 — shared FTS5 query tokenizer.
|
|
3
|
+
*
|
|
4
|
+
* Injection recall ORs the quoted tokens (`"t1" OR "t2"`) while
|
|
5
|
+
* `kevin_why` ANDs them (`"t1" AND "t2"`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const STOP_WORDS: Set<string>;
|
|
8
|
+
/** Lowercase, split on whitespace, drop stopwords. Returns raw tokens. */
|
|
9
|
+
export declare function tokenizeQuery(query: string): string[];
|
|
10
|
+
/** Quote a token for an FTS5 MATCH clause, escaping embedded quotes. */
|
|
11
|
+
export declare function quoteToken(token: string): string;
|
|
12
|
+
/** Join quoted tokens into a MATCH clause with the given separator. */
|
|
13
|
+
export declare function toMatchClause(tokens: string[], separator: " OR " | " AND "): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* K4-013 — shared FTS5 query tokenizer.
|
|
3
|
+
*
|
|
4
|
+
* Injection recall ORs the quoted tokens (`"t1" OR "t2"`) while
|
|
5
|
+
* `kevin_why` ANDs them (`"t1" AND "t2"`).
|
|
6
|
+
*/
|
|
7
|
+
export const STOP_WORDS = new Set([
|
|
8
|
+
"a",
|
|
9
|
+
"an",
|
|
10
|
+
"and",
|
|
11
|
+
"are",
|
|
12
|
+
"at",
|
|
13
|
+
"be",
|
|
14
|
+
"been",
|
|
15
|
+
"but",
|
|
16
|
+
"by",
|
|
17
|
+
"did",
|
|
18
|
+
"do",
|
|
19
|
+
"does",
|
|
20
|
+
"el",
|
|
21
|
+
"eso",
|
|
22
|
+
"for",
|
|
23
|
+
"how",
|
|
24
|
+
"i",
|
|
25
|
+
"if",
|
|
26
|
+
"in",
|
|
27
|
+
"is",
|
|
28
|
+
"it",
|
|
29
|
+
"la",
|
|
30
|
+
"las",
|
|
31
|
+
"los",
|
|
32
|
+
"mi",
|
|
33
|
+
"my",
|
|
34
|
+
"o",
|
|
35
|
+
"of",
|
|
36
|
+
"on",
|
|
37
|
+
"or",
|
|
38
|
+
"para",
|
|
39
|
+
"por",
|
|
40
|
+
"que",
|
|
41
|
+
"she",
|
|
42
|
+
"su",
|
|
43
|
+
"that",
|
|
44
|
+
"the",
|
|
45
|
+
"this",
|
|
46
|
+
"to",
|
|
47
|
+
"tu",
|
|
48
|
+
"un",
|
|
49
|
+
"una",
|
|
50
|
+
"we",
|
|
51
|
+
"were",
|
|
52
|
+
"what",
|
|
53
|
+
"when",
|
|
54
|
+
"where",
|
|
55
|
+
"which",
|
|
56
|
+
"who",
|
|
57
|
+
"why",
|
|
58
|
+
"with",
|
|
59
|
+
"y",
|
|
60
|
+
"you",
|
|
61
|
+
"como",
|
|
62
|
+
"con",
|
|
63
|
+
"de",
|
|
64
|
+
"en",
|
|
65
|
+
"he",
|
|
66
|
+
"they",
|
|
67
|
+
"was",
|
|
68
|
+
"sin",
|
|
69
|
+
]);
|
|
70
|
+
/** Lowercase, split on whitespace, drop stopwords. Returns raw tokens. */
|
|
71
|
+
export function tokenizeQuery(query) {
|
|
72
|
+
return query
|
|
73
|
+
.trim()
|
|
74
|
+
.toLowerCase()
|
|
75
|
+
.split(/\s+/)
|
|
76
|
+
.filter((t) => t.length > 0 && !STOP_WORDS.has(t));
|
|
77
|
+
}
|
|
78
|
+
/** Quote a token for an FTS5 MATCH clause, escaping embedded quotes. */
|
|
79
|
+
export function quoteToken(token) {
|
|
80
|
+
return `"${token.replace(/"/g, '""')}"`;
|
|
81
|
+
}
|
|
82
|
+
/** Join quoted tokens into a MATCH clause with the given separator. */
|
|
83
|
+
export function toMatchClause(tokens, separator) {
|
|
84
|
+
return tokens.map(quoteToken).join(separator);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=query-tokenizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-tokenizer.js","sourceRoot":"","sources":["../../plugin/query-tokenizer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAS;IACzC,GAAG;IACH,IAAI;IACJ,KAAK;IACL,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,KAAK;IACL,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,MAAM;IACN,IAAI;IACJ,KAAK;IACL,KAAK;IACL,KAAK;IACL,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;IACL,IAAI;IACJ,MAAM;IACN,KAAK;IACL,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,KAAK;IACL,KAAK;IACL,MAAM;IACN,GAAG;IACH,KAAK;IACL,MAAM;IACN,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,KAAK;IACL,KAAK;CACL,CAAC,CAAC;AAEH,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAAC,KAAa;IAC1C,OAAO,KAAK;SACV,IAAI,EAAE;SACN,WAAW,EAAE;SACb,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;AACzC,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAC5B,MAAgB,EAChB,SAA2B;IAE3B,OAAO,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- Kevin 0.3.0 — Migration 004: Knowledge + Causality (additive)
|
|
3
|
+
-- ============================================================
|
|
4
|
+
-- Backward-compatible, additive only. All new columns are
|
|
5
|
+
-- nullable or carry a NOT NULL DEFAULT so legacy rows keep
|
|
6
|
+
-- working without a destructive rebuild.
|
|
7
|
+
-- ============================================================
|
|
8
|
+
|
|
9
|
+
-- 1. memories: evidence + lifecycle columns.
|
|
10
|
+
-- evidence_count — how many times this fingerprint was confirmed as fixed.
|
|
11
|
+
-- last_verified_at — timestamp of the most recent causal confirmation.
|
|
12
|
+
-- status — lifecycle state; default 'active'. Superseded rows are hidden.
|
|
13
|
+
ALTER TABLE memories ADD COLUMN evidence_count INTEGER NOT NULL DEFAULT 0;
|
|
14
|
+
ALTER TABLE memories ADD COLUMN last_verified_at TEXT;
|
|
15
|
+
ALTER TABLE memories ADD COLUMN status TEXT NOT NULL DEFAULT 'active'
|
|
16
|
+
CHECK (status IN ('active', 'superseded', 'stale', 'archived'));
|
|
17
|
+
|
|
18
|
+
-- 2. tool_calls: causal link + feedback-loop link columns.
|
|
19
|
+
-- fix_for_fingerprint — set when this successful call resolved a prior failure
|
|
20
|
+
-- with the given fingerprint. NULL for tool calls that were not fixes.
|
|
21
|
+
-- error_fingerprint — set by Reflector (via onLinkError callback) when a call
|
|
22
|
+
-- FAILS, to the stderr-based fingerprint the matching error memory uses.
|
|
23
|
+
-- This fixes the v0.2.0/v0.3.0 feedback-loop fingerprint mismatch bug:
|
|
24
|
+
-- tool_calls.fingerprint is hashed from "tool|args|success" by ToolCallObserver,
|
|
25
|
+
-- while memories.fingerprint is hashed from stderr text by Reflector — they
|
|
26
|
+
-- never agreed, so boost/penalize queries silently mismatched. The new column
|
|
27
|
+
-- stores the SAME identity dimension the error memory uses.
|
|
28
|
+
ALTER TABLE tool_calls ADD COLUMN fix_for_fingerprint TEXT;
|
|
29
|
+
ALTER TABLE tool_calls ADD COLUMN error_fingerprint TEXT;
|
|
30
|
+
|
|
31
|
+
-- 3. Index: causal linkage by fingerprint. Used by CausalChain.onSuccess
|
|
32
|
+
-- and kevin_why to materialize traces.
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_fix_fp
|
|
34
|
+
ON tool_calls(fix_for_fingerprint)
|
|
35
|
+
WHERE fix_for_fingerprint IS NOT NULL;
|
|
36
|
+
|
|
37
|
+
-- 3b. Index: feedback-loop linkage by error_fingerprint. Used by
|
|
38
|
+
-- boostPositiveReflectors / penalizeRecurringReflectors to count
|
|
39
|
+
-- recurrences by the same identity dimension the error memory uses.
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_error_fp
|
|
41
|
+
ON tool_calls(error_fingerprint)
|
|
42
|
+
WHERE error_fingerprint IS NOT NULL;
|
|
43
|
+
|
|
44
|
+
-- 4. Index: memories by fingerprint for promotion + supersede queries.
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_memories_fp
|
|
46
|
+
ON memories(fingerprint)
|
|
47
|
+
WHERE fingerprint IS NOT NULL;
|
|
48
|
+
|
|
49
|
+
-- 5. kevin_metrics: seed new v0.3 counters.
|
|
50
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
|
|
51
|
+
('patterns_causal', 0),
|
|
52
|
+
('causal_links', 0),
|
|
53
|
+
('memories_superseded', 0);
|
|
54
|
+
|
|
55
|
+
-- 6. kevin_settings: seed new opt-in flags.
|
|
56
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
|
|
57
|
+
('llm_reflection_enabled', '0'),
|
|
58
|
+
('cross_project_enabled', '0');
|
|
59
|
+
|
|
60
|
+
-- 7. Seed version 004.
|
|
61
|
+
INSERT OR IGNORE INTO schema_version (version) VALUES ('004');
|
|
62
|
+
|
|
63
|
+
-- ============================================================
|
|
64
|
+
-- 8. Rebuild memories table with expanded CHECK constraints.
|
|
65
|
+
-- v0.3.0 introduces new types (rule, solution) and origins
|
|
66
|
+
-- (causal, imported). SQLite cannot ALTER a CHECK constraint,
|
|
67
|
+
-- so we rebuild via a temporary table. FTS5 external-content
|
|
68
|
+
-- table references the content table by name, so we drop+recreate
|
|
69
|
+
-- the FTS5 triggers after the rebuild.
|
|
70
|
+
-- ============================================================
|
|
71
|
+
|
|
72
|
+
-- Step 1: Create new table with correct constraints
|
|
73
|
+
CREATE TABLE memories_v04 (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
type TEXT NOT NULL CHECK(type IN ('error','pattern','decision','context','rule','solution')),
|
|
76
|
+
content TEXT NOT NULL,
|
|
77
|
+
scope TEXT NOT NULL DEFAULT 'project' CHECK(scope IN ('project','session')),
|
|
78
|
+
relevance_score REAL DEFAULT 0.5,
|
|
79
|
+
source_tool TEXT,
|
|
80
|
+
source_session TEXT,
|
|
81
|
+
metadata TEXT,
|
|
82
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
83
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
84
|
+
expires_at TEXT,
|
|
85
|
+
project_id TEXT,
|
|
86
|
+
fingerprint TEXT,
|
|
87
|
+
origin TEXT NOT NULL DEFAULT 'agent'
|
|
88
|
+
CHECK(origin IN ('reflector','agent','pattern','retrospective','causal','imported')),
|
|
89
|
+
evidence_count INTEGER NOT NULL DEFAULT 0,
|
|
90
|
+
last_verified_at TEXT,
|
|
91
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
92
|
+
CHECK(status IN ('active','superseded','stale','archived'))
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
-- Step 2: Copy data preserving rowids (FTS5 content sync relies on rowid)
|
|
96
|
+
INSERT INTO memories_v04
|
|
97
|
+
(rowid, id, type, content, scope, relevance_score, source_tool,
|
|
98
|
+
source_session, metadata, created_at, updated_at, expires_at,
|
|
99
|
+
project_id, fingerprint, origin, evidence_count, last_verified_at, status)
|
|
100
|
+
SELECT rowid, id, type, content, scope, relevance_score, source_tool,
|
|
101
|
+
source_session, metadata, created_at, updated_at, expires_at,
|
|
102
|
+
project_id, fingerprint, origin, evidence_count, last_verified_at, status
|
|
103
|
+
FROM memories;
|
|
104
|
+
|
|
105
|
+
-- Step 3: Drop old table and FTS triggers
|
|
106
|
+
DROP TRIGGER IF EXISTS memories_ai;
|
|
107
|
+
DROP TRIGGER IF EXISTS memories_ad;
|
|
108
|
+
DROP TRIGGER IF EXISTS memories_au;
|
|
109
|
+
DROP TABLE memories;
|
|
110
|
+
|
|
111
|
+
-- Step 4: Rename new table
|
|
112
|
+
ALTER TABLE memories_v04 RENAME TO memories;
|
|
113
|
+
|
|
114
|
+
-- Step 5: Recreate FTS triggers (memories_fts is content=external, survives DROP of content table)
|
|
115
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
116
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
117
|
+
END;
|
|
118
|
+
|
|
119
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
120
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
121
|
+
END;
|
|
122
|
+
|
|
123
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
124
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
125
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
126
|
+
END;
|
|
127
|
+
|
|
128
|
+
-- Step 6: Recreate indexes
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_memories_relevance ON memories(relevance_score DESC);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
|
|
133
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_memories_error_fp
|
|
134
|
+
ON memories(project_id, fingerprint)
|
|
135
|
+
WHERE type = 'error' AND fingerprint IS NOT NULL AND origin = 'reflector';
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_memories_fp
|
|
137
|
+
ON memories(fingerprint)
|
|
138
|
+
WHERE fingerprint IS NOT NULL;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- Kevin 0.4.0 — Migration 005: Signal over Noise (additive)
|
|
3
|
+
-- ============================================================
|
|
4
|
+
-- Backward-compatible, additive only. All new columns are
|
|
5
|
+
-- nullable or carry a NOT NULL DEFAULT so legacy rows keep
|
|
6
|
+
-- working without a destructive rebuild.
|
|
7
|
+
-- ============================================================
|
|
8
|
+
|
|
9
|
+
-- 1. memories: positive/negative evidence split (D4-03).
|
|
10
|
+
-- recurrence_count — how many times this fingerprint recurred AFTER
|
|
11
|
+
-- injection (negative evidence; lowers confidence).
|
|
12
|
+
-- fix_args — deterministic capture of the linked success call's
|
|
13
|
+
-- args_summary ("Fixed by:" raw material, D4-07).
|
|
14
|
+
-- last_injected_at — timestamp of the most recent injection of this memory.
|
|
15
|
+
ALTER TABLE memories ADD COLUMN recurrence_count INTEGER NOT NULL DEFAULT 0;
|
|
16
|
+
ALTER TABLE memories ADD COLUMN fix_args TEXT;
|
|
17
|
+
ALTER TABLE memories ADD COLUMN last_injected_at TEXT;
|
|
18
|
+
|
|
19
|
+
-- 2. kevin_injections: the injection ledger (D4-04). One row per injected
|
|
20
|
+
-- memory per prompt/compaction, settled at session.idle.
|
|
21
|
+
CREATE TABLE IF NOT EXISTS kevin_injections (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
memory_id TEXT NOT NULL,
|
|
24
|
+
fingerprint TEXT NOT NULL,
|
|
25
|
+
session_id TEXT NOT NULL,
|
|
26
|
+
hook TEXT NOT NULL CHECK (hook IN ('pre_prompt', 'compacting')),
|
|
27
|
+
tokens INTEGER NOT NULL,
|
|
28
|
+
injected_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
29
|
+
outcome TEXT CHECK (outcome IN ('unmeasured', 'effective', 'ineffective'))
|
|
30
|
+
NOT NULL DEFAULT 'unmeasured'
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
-- 2b. Indexes: settlement by session, recurrence lookups by fingerprint,
|
|
34
|
+
-- and outcome rollups for precision_rate.
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_injections_fp
|
|
36
|
+
ON kevin_injections(fingerprint);
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_injections_session
|
|
38
|
+
ON kevin_injections(session_id);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_injections_outcome
|
|
40
|
+
ON kevin_injections(outcome);
|
|
41
|
+
|
|
42
|
+
-- 3. kevin_metrics: seed new v0.4 counters.
|
|
43
|
+
-- patterns_promoted_new replaces patterns_causal (which was inflated by
|
|
44
|
+
-- idempotent refreshes); the latter stays for compat but is frozen.
|
|
45
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
|
|
46
|
+
('injections_total', 0),
|
|
47
|
+
('injections_effective', 0),
|
|
48
|
+
('injections_ineffective', 0),
|
|
49
|
+
('patterns_promoted_new', 0);
|
|
50
|
+
|
|
51
|
+
-- 4. kevin_settings: seed new v0.4 flags.
|
|
52
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
|
|
53
|
+
('quality_gate_enabled', '1'),
|
|
54
|
+
('lesson_snippet_injection','1');
|
|
55
|
+
|
|
56
|
+
-- 5. Seed version 005.
|
|
57
|
+
INSERT OR IGNORE INTO schema_version (version) VALUES ('005');
|