@jmtrin/kevin-core 1.5.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/Retrospective.js +4 -0
- package/dist/contract.d.ts +58 -1
- package/dist/contract.js +206 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +9 -2
- package/dist/metrics.d.ts +1 -1
- package/dist/metrics.js +18 -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/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/package.json +24 -26
package/dist/okf.js
CHANGED
|
@@ -20,6 +20,9 @@ import { computeConfidence } from "./confidence.js";
|
|
|
20
20
|
import { fnv1a64 } from "./fingerprint.js";
|
|
21
21
|
/** OKF v2 format version marker, written on the first header line. */
|
|
22
22
|
export const OKF_VERSION = 2;
|
|
23
|
+
/** v2.0.0 (K16-007) — OKF v3 marker */
|
|
24
|
+
export const OKF_V3 = 3;
|
|
25
|
+
export const OKF_VERSIONS = [2, 3];
|
|
23
26
|
/** A single canonicalized entry line may not exceed this many bytes. */
|
|
24
27
|
export const MAX_LINE_BYTES = 4096;
|
|
25
28
|
/** A serialized corpus may not exceed this many entries. */
|
|
@@ -58,13 +61,13 @@ export function canonicalize(e) {
|
|
|
58
61
|
* byte check) and corpora over MAX_ENTRIES are refused, not
|
|
59
62
|
* truncated (plan §5.3, physical rules).
|
|
60
63
|
*/
|
|
61
|
-
export function serialize(entries, repoId, version) {
|
|
64
|
+
export function serialize(entries, repoId, version, okfVersion = OKF_VERSION) {
|
|
62
65
|
if (entries.length > MAX_ENTRIES) {
|
|
63
66
|
throw new Error(`okf: corpus of ${entries.length} entries exceeds MAX_ENTRIES (${MAX_ENTRIES})`);
|
|
64
67
|
}
|
|
65
68
|
const sorted = [...entries].sort((a, b) => a.entry_id < b.entry_id ? -1 : a.entry_id > b.entry_id ? 1 : 0);
|
|
66
69
|
const lines = [
|
|
67
|
-
`#okf ${
|
|
70
|
+
`#okf ${okfVersion}`,
|
|
68
71
|
`#repo ${repoId}`,
|
|
69
72
|
`#generated-by opencode-kevin/${version}`,
|
|
70
73
|
];
|
|
@@ -160,7 +163,7 @@ export function parse(text) {
|
|
|
160
163
|
}
|
|
161
164
|
const declared = Number(lines[0].slice(5));
|
|
162
165
|
version = Number.isInteger(declared) && declared >= 0 ? declared : 0;
|
|
163
|
-
if (version >
|
|
166
|
+
if (version > OKF_V3) {
|
|
164
167
|
// Guessing at a future format's semantics is how corpora get
|
|
165
168
|
// corrupted — refuse the whole file, never a best-effort parse.
|
|
166
169
|
return {
|
|
@@ -171,6 +174,11 @@ export function parse(text) {
|
|
|
171
174
|
folded: 0,
|
|
172
175
|
};
|
|
173
176
|
}
|
|
177
|
+
if (version !== OKF_VERSION && version !== OKF_V3) {
|
|
178
|
+
// Unknown version below ahead — treat as not_okf for safety
|
|
179
|
+
// But keep version_ahead semantics for future-proofing
|
|
180
|
+
// For K16-007, only 2 and 3 are valid
|
|
181
|
+
}
|
|
174
182
|
if (lines[1]?.startsWith("#repo ")) {
|
|
175
183
|
repoId = lines[1].slice(6) || null;
|
|
176
184
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MemorySource, SourceEntry } from "./MemorySource.js";
|
|
2
|
+
export declare class ClaudeMemorySource implements MemorySource {
|
|
3
|
+
private enabledFlag;
|
|
4
|
+
private root;
|
|
5
|
+
name: string;
|
|
6
|
+
precedence: number;
|
|
7
|
+
constructor(enabledFlag: () => boolean, root?: string);
|
|
8
|
+
enabled(): boolean;
|
|
9
|
+
fetch(): Promise<SourceEntry[]>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// K16-014 — ClaudeMemorySource (read-only mirror of ~/.claude/memory)
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
export class ClaudeMemorySource {
|
|
6
|
+
enabledFlag;
|
|
7
|
+
root;
|
|
8
|
+
name = "claude-memory";
|
|
9
|
+
precedence = 20;
|
|
10
|
+
constructor(enabledFlag, root = join(homedir(), ".claude")) {
|
|
11
|
+
this.enabledFlag = enabledFlag;
|
|
12
|
+
this.root = root;
|
|
13
|
+
}
|
|
14
|
+
enabled() { return this.enabledFlag(); }
|
|
15
|
+
async fetch() {
|
|
16
|
+
if (!this.enabled())
|
|
17
|
+
return [];
|
|
18
|
+
const memPath = join(this.root, "memory");
|
|
19
|
+
// Could be directory of markdown files or single file — best-effort
|
|
20
|
+
if (!existsSync(memPath))
|
|
21
|
+
return [];
|
|
22
|
+
try {
|
|
23
|
+
const txt = readFileSync(memPath, "utf8");
|
|
24
|
+
// naive: split lines non-empty as statements
|
|
25
|
+
return txt.split("\n").map(s => s.trim()).filter(Boolean).slice(0, 100).map(statement => ({
|
|
26
|
+
statement,
|
|
27
|
+
type: "rule",
|
|
28
|
+
scope: null,
|
|
29
|
+
source: this.name,
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MemorySource, SourceEntry } from "./MemorySource.js";
|
|
2
|
+
export declare class CodexMemoriesSource implements MemorySource {
|
|
3
|
+
private enabledFlag;
|
|
4
|
+
private root;
|
|
5
|
+
name: string;
|
|
6
|
+
precedence: number;
|
|
7
|
+
constructor(enabledFlag: () => boolean, root?: string);
|
|
8
|
+
enabled(): boolean;
|
|
9
|
+
fetch(): Promise<SourceEntry[]>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// K16-015 — CodexMemoriesSource (mirror of ~/.codex/memories)
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
export class CodexMemoriesSource {
|
|
6
|
+
enabledFlag;
|
|
7
|
+
root;
|
|
8
|
+
name = "codex-memories";
|
|
9
|
+
precedence = 30;
|
|
10
|
+
constructor(enabledFlag, root = join(homedir(), ".codex", "memories")) {
|
|
11
|
+
this.enabledFlag = enabledFlag;
|
|
12
|
+
this.root = root;
|
|
13
|
+
}
|
|
14
|
+
enabled() { return this.enabledFlag(); }
|
|
15
|
+
async fetch() {
|
|
16
|
+
if (!this.enabled())
|
|
17
|
+
return [];
|
|
18
|
+
if (!existsSync(this.root))
|
|
19
|
+
return [];
|
|
20
|
+
const out = [];
|
|
21
|
+
try {
|
|
22
|
+
for (const f of readdirSync(this.root)) {
|
|
23
|
+
if (!f.endsWith(".md") && !f.endsWith(".txt"))
|
|
24
|
+
continue;
|
|
25
|
+
try {
|
|
26
|
+
const txt = readFileSync(join(this.root, f), "utf8");
|
|
27
|
+
for (const line of txt.split("\n").map(s => s.trim()).filter(Boolean).slice(0, 50)) {
|
|
28
|
+
out.push({ statement: line, type: "rule", scope: null, source: this.name });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Store } from "../Store.js";
|
|
2
|
+
import type { MemorySource, SourceSyncResult } from "./MemorySource.js";
|
|
3
|
+
export interface SyncDeps {
|
|
4
|
+
store: Store;
|
|
5
|
+
sources: MemorySource[];
|
|
6
|
+
metrics?: {
|
|
7
|
+
incr(key: string, by?: number): void;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function idleSync(deps: SyncDeps): Promise<SourceSyncResult[]>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { fingerprint } from "../fingerprint.js";
|
|
2
|
+
function dedupKey(e) {
|
|
3
|
+
// lower precedence wins attribution: fingerprint over normalized statement + scope
|
|
4
|
+
return fingerprint(`${e.type}\0${e.statement}\0${e.scope ?? ""}`);
|
|
5
|
+
}
|
|
6
|
+
export async function idleSync(deps) {
|
|
7
|
+
const results = [];
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
// Pre-populate dedup with existing memories fingerprints (lowest precedence already wins)
|
|
10
|
+
try {
|
|
11
|
+
const rows = deps.store.prepare("SELECT fingerprint FROM memories").all();
|
|
12
|
+
for (const r of rows)
|
|
13
|
+
if (r.fingerprint)
|
|
14
|
+
seen.add(r.fingerprint);
|
|
15
|
+
}
|
|
16
|
+
catch { }
|
|
17
|
+
for (const src of deps.sources.sort((a, b) => a.precedence - b.precedence)) {
|
|
18
|
+
if (!src.enabled()) {
|
|
19
|
+
results.push({ source: src.name, fetched: 0, dedupSkipped: 0, inserted: 0 });
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const entries = await src.fetch().catch(() => []);
|
|
23
|
+
let dedupSkipped = 0;
|
|
24
|
+
let inserted = 0;
|
|
25
|
+
for (const e of entries) {
|
|
26
|
+
const fp = dedupKey(e);
|
|
27
|
+
if (seen.has(fp)) {
|
|
28
|
+
dedupSkipped++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
seen.add(fp);
|
|
32
|
+
// Insert as memory with source provenance (simplified)
|
|
33
|
+
try {
|
|
34
|
+
deps.store.prepare(`INSERT OR IGNORE INTO memories (id, project_id, type, content, scope, fingerprint, confidence, origin, source, created_at)
|
|
35
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`).run(`src-${fp.slice(0, 12)}-${src.name}`, "default", e.type, e.statement, e.scope, fp, 0.5, src.name, src.name);
|
|
36
|
+
inserted++;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
dedupSkipped++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (dedupSkipped > 0)
|
|
43
|
+
deps.metrics?.incr("source_dedup_skips_total", dedupSkipped);
|
|
44
|
+
if (inserted > 0)
|
|
45
|
+
deps.metrics?.incr("source_syncs_total", 1);
|
|
46
|
+
results.push({ source: src.name, fetched: entries.length, dedupSkipped, inserted });
|
|
47
|
+
}
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface MemorySource {
|
|
2
|
+
name: string;
|
|
3
|
+
precedence: number;
|
|
4
|
+
enabled(): boolean;
|
|
5
|
+
fetch(): Promise<SourceEntry[]>;
|
|
6
|
+
}
|
|
7
|
+
export interface SourceEntry {
|
|
8
|
+
statement: string;
|
|
9
|
+
type: "decision" | "rule" | "pattern" | "solution";
|
|
10
|
+
scope: string | null;
|
|
11
|
+
source: string;
|
|
12
|
+
updatedAt?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SourceSyncResult {
|
|
15
|
+
source: string;
|
|
16
|
+
fetched: number;
|
|
17
|
+
dedupSkipped: number;
|
|
18
|
+
inserted: number;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MemorySource, SourceEntry } from "./MemorySource.js";
|
|
2
|
+
export declare class OpencodeNativeSource implements MemorySource {
|
|
3
|
+
private enabledFlag;
|
|
4
|
+
private projectRoot;
|
|
5
|
+
name: string;
|
|
6
|
+
precedence: number;
|
|
7
|
+
constructor(enabledFlag: () => boolean, projectRoot?: string);
|
|
8
|
+
enabled(): boolean;
|
|
9
|
+
fetch(): Promise<SourceEntry[]>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// K16-016 — OpencodeNativeSource (native opencode memories, e.g. .opencode/memory)
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export class OpencodeNativeSource {
|
|
5
|
+
enabledFlag;
|
|
6
|
+
projectRoot;
|
|
7
|
+
name = "opencode-native";
|
|
8
|
+
precedence = 40;
|
|
9
|
+
constructor(enabledFlag, projectRoot = process.cwd()) {
|
|
10
|
+
this.enabledFlag = enabledFlag;
|
|
11
|
+
this.projectRoot = projectRoot;
|
|
12
|
+
}
|
|
13
|
+
enabled() { return this.enabledFlag(); }
|
|
14
|
+
async fetch() {
|
|
15
|
+
if (!this.enabled())
|
|
16
|
+
return [];
|
|
17
|
+
const p = join(this.projectRoot, ".opencode", "memory");
|
|
18
|
+
if (!existsSync(p))
|
|
19
|
+
return [];
|
|
20
|
+
try {
|
|
21
|
+
const txt = readFileSync(p, "utf8");
|
|
22
|
+
return txt.split("\n").map(s => s.trim()).filter(Boolean).slice(0, 100).map(statement => ({
|
|
23
|
+
statement,
|
|
24
|
+
type: "rule",
|
|
25
|
+
scope: null,
|
|
26
|
+
source: this.name,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MemorySource, SourceEntry } from "./MemorySource.js";
|
|
2
|
+
export declare class OpencodePluginSource implements MemorySource {
|
|
3
|
+
private enabledFlag;
|
|
4
|
+
name: string;
|
|
5
|
+
precedence: number;
|
|
6
|
+
constructor(enabledFlag: () => boolean);
|
|
7
|
+
enabled(): boolean;
|
|
8
|
+
fetch(): Promise<SourceEntry[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class OpencodePluginSource {
|
|
2
|
+
enabledFlag;
|
|
3
|
+
name = "opencode-plugin";
|
|
4
|
+
precedence = 10;
|
|
5
|
+
constructor(enabledFlag) {
|
|
6
|
+
this.enabledFlag = enabledFlag;
|
|
7
|
+
}
|
|
8
|
+
enabled() { return this.enabledFlag(); }
|
|
9
|
+
async fetch() {
|
|
10
|
+
// Plugin source is the local DB itself — no fetch, handled elsewhere
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
},
|
|
27
|
-
"license": "MIT"
|
|
2
|
+
"name": "@jmtrin/kevin-core",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Kevin core — host-agnostic deterministic brain (Bedrock)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json && node ./scripts/copy-migrations.mjs",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.5.0"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT"
|
|
28
26
|
}
|