@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/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 ${OKF_VERSION}`,
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 > OKF_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
- "name": "@jmtrin/kevin-core",
3
- "version": "1.5.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": [
15
- "dist"
16
- ],
17
- "scripts": {
18
- "build": "tsc -p tsconfig.json && node ./scripts/copy-migrations.mjs",
19
- "typecheck": "tsc --noEmit"
20
- },
21
- "engines": {
22
- "node": ">=22.5.0"
23
- },
24
- "publishConfig": {
25
- "access": "public"
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
  }