diff --git a/bun.lock b/bun.lock
index 26aa766..a7b94da 100644
--- a/bun.lock
+++ b/bun.lock
@@ -4,17 +4,35 @@
"workspaces": {
"": {
"name": "opencode-nuum",
+ "devDependencies": {
+ "@types/bun": "^1.2.0",
+ "esbuild": "^0.25.12",
+ "fast-check": "^4.5.3",
+ "typescript": "^5.8.0",
+ },
+ },
+ "packages/core": {
+ "name": "@loreai/core",
+ "version": "0.9.1",
"dependencies": {
"remark": "^15.0.1",
"uuidv7": "^1.1.0",
- "zod": "^3.25.0",
+ "zod": "^4.3.6",
+ },
+ "devDependencies": {
+ "@opencode-ai/sdk": "^1.1.39",
+ "@types/mdast": "^4.0.4",
+ },
+ },
+ "packages/opencode": {
+ "name": "opencode-lore",
+ "version": "0.9.1",
+ "dependencies": {
+ "@loreai/core": "workspace:*",
},
"devDependencies": {
"@opencode-ai/plugin": "^1.1.39",
"@opencode-ai/sdk": "^1.1.39",
- "@types/bun": "^1.2.0",
- "fast-check": "^4.5.3",
- "typescript": "^5.8.0",
},
"peerDependencies": {
"@opencode-ai/plugin": ">=1.1.0",
@@ -22,6 +40,60 @@
},
},
"packages": {
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+
+ "@loreai/core": ["@loreai/core@workspace:packages/core"],
+
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.2.6", "", { "dependencies": { "@opencode-ai/sdk": "1.2.6", "zod": "4.1.8" } }, "sha512-CJEp3k17yWsjyfivm3zQof8L42pdze3a7iTqMOyesHgJplSuLiBYAMndbBYMDuJkyAh0dHYjw8v10vVw7Kfl4Q=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.2.6", "", {}, "sha512-dWMF8Aku4h7fh8sw5tQ2FtbqRLbIFT8FcsukpxTird49ax7oUXP+gzqxM/VdxHjfksQvzLBjLZyMdDStc5g7xA=="],
@@ -52,6 +124,8 @@
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
+ "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-check": ["fast-check@4.5.3", "", { "dependencies": { "pure-rand": "^7.0.0" } }, "sha512-IE9csY7lnhxBnA8g/WI5eg/hygA6MGWJMSNfFRrBlXUciADEhS1EDB0SIsMSvzubzIlOBbVITSsypCsW717poA=="],
@@ -112,6 +186,8 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+ "opencode-lore": ["opencode-lore@workspace:packages/opencode"],
+
"pure-rand": ["pure-rand@7.0.1", "", {}, "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ=="],
"remark": ["remark@15.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="],
@@ -142,7 +218,7 @@
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
- "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
diff --git a/bunfig.toml b/bunfig.toml
index 8755352..1d216be 100644
--- a/bunfig.toml
+++ b/bunfig.toml
@@ -1,2 +1,2 @@
[test]
-preload = ["./test/setup.ts"]
+preload = ["./packages/core/test/setup.ts"]
diff --git a/package.json b/package.json
index 389f49d..56bd0f6 100644
--- a/package.json
+++ b/package.json
@@ -1,48 +1,26 @@
{
- "name": "opencode-lore",
- "version": "0.9.1",
+ "name": "lore-monorepo",
+ "private": true,
"type": "module",
"license": "MIT",
- "description": "Three-tier memory architecture for OpenCode — distillation, not summarization",
- "main": "src/index.ts",
- "exports": {
- ".": "./src/index.ts"
- },
+ "description": "Monorepo root for Lore — three-tier memory architecture",
+ "workspaces": [
+ "packages/*"
+ ],
"scripts": {
- "typecheck": "bun run tsc --noEmit",
- "test": "bun test"
- },
- "peerDependencies": {
- "@opencode-ai/plugin": ">=1.1.0"
- },
- "dependencies": {
- "remark": "^15.0.1",
- "uuidv7": "^1.1.0",
- "zod": "^4.3.6"
+ "typecheck": "bun --filter '*' typecheck",
+ "test": "bun test",
+ "build": "bun --filter '*' build"
},
"devDependencies": {
- "@opencode-ai/plugin": "^1.1.39",
- "@opencode-ai/sdk": "^1.1.39",
"@types/bun": "^1.2.0",
+ "esbuild": "^0.25.12",
"fast-check": "^4.5.3",
"typescript": "^5.8.0"
},
- "files": [
- "src/",
- "README.md",
- "LICENSE"
- ],
"repository": {
"type": "git",
"url": "git+https://github.com/BYK/opencode-lore.git"
},
- "keywords": [
- "opencode",
- "plugin",
- "memory",
- "agent",
- "distillation",
- "llm"
- ],
"author": "BYK"
}
diff --git a/packages/core/package.json b/packages/core/package.json
new file mode 100644
index 0000000..6e78e0b
--- /dev/null
+++ b/packages/core/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@loreai/core",
+ "version": "0.9.1",
+ "type": "module",
+ "license": "MIT",
+ "description": "Shared memory engine for Lore — three-tier storage, distillation, gradient context management",
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "exports": {
+ ".": {
+ "types": "./src/index.ts",
+ "bun": "./src/index.ts",
+ "default": "./dist/node/index.js"
+ }
+ },
+ "imports": {
+ "#db/driver": {
+ "bun": "./src/db/driver.bun.ts",
+ "default": "./src/db/driver.node.ts"
+ }
+ },
+ "scripts": {
+ "typecheck": "tsc --noEmit",
+ "build": "bun run script/build.ts"
+ },
+ "dependencies": {
+ "remark": "^15.0.1",
+ "uuidv7": "^1.1.0",
+ "zod": "^4.3.6"
+ },
+ "devDependencies": {
+ "@opencode-ai/sdk": "^1.1.39",
+ "@types/mdast": "^4.0.4"
+ },
+ "files": [
+ "src/",
+ "dist/",
+ "README.md",
+ "LICENSE"
+ ],
+ "engines": {
+ "node": ">=22.5"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/BYK/opencode-lore.git",
+ "directory": "packages/core"
+ },
+ "keywords": [
+ "lore",
+ "memory",
+ "llm",
+ "sqlite",
+ "fts5",
+ "distillation"
+ ],
+ "author": "BYK"
+}
diff --git a/src/agents-file.ts b/packages/core/src/agents-file.ts
similarity index 100%
rename from src/agents-file.ts
rename to packages/core/src/agents-file.ts
diff --git a/src/config.ts b/packages/core/src/config.ts
similarity index 100%
rename from src/config.ts
rename to packages/core/src/config.ts
diff --git a/src/curator.ts b/packages/core/src/curator.ts
similarity index 100%
rename from src/curator.ts
rename to packages/core/src/curator.ts
diff --git a/src/db.ts b/packages/core/src/db.ts
similarity index 100%
rename from src/db.ts
rename to packages/core/src/db.ts
diff --git a/src/distillation.ts b/packages/core/src/distillation.ts
similarity index 100%
rename from src/distillation.ts
rename to packages/core/src/distillation.ts
diff --git a/src/embedding.ts b/packages/core/src/embedding.ts
similarity index 100%
rename from src/embedding.ts
rename to packages/core/src/embedding.ts
diff --git a/src/gradient.ts b/packages/core/src/gradient.ts
similarity index 100%
rename from src/gradient.ts
rename to packages/core/src/gradient.ts
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
new file mode 100644
index 0000000..23f6122
--- /dev/null
+++ b/packages/core/src/index.ts
@@ -0,0 +1,80 @@
+// @loreai/core — shared memory engine for Lore.
+//
+// This barrel re-exports every core module so hosts (the OpenCode plugin, the
+// Pi extension, or any future adapter) can import from a single entry:
+//
+// import { ltm, temporal, gradient, ... } from "@loreai/core"
+//
+// Modules that are intentionally not re-exported:
+// - `db.ts` internals are exposed via specific functions (db(), ensureProject(), etc.)
+// - No Plugin/Hooks surface — those live in host-specific packages.
+
+export * as temporal from "./temporal";
+export * as ltm from "./ltm";
+export * as distillation from "./distillation";
+export * as curator from "./curator";
+export * as embedding from "./embedding";
+export * as latReader from "./lat-reader";
+export * as log from "./log";
+
+export { load, config, type LoreConfig } from "./config";
+export {
+ db,
+ ensureProject,
+ isFirstRun,
+ projectId,
+ projectName,
+ loadForceMinLayer,
+ saveForceMinLayer,
+ close,
+} from "./db";
+export {
+ transform,
+ setModelLimits,
+ needsUrgentDistillation,
+ calibrate,
+ setLtmTokens,
+ getLtmTokens,
+ getLtmBudget,
+ setForceMinLayer,
+ getLastTransformedCount,
+ getLastTransformEstimate,
+} from "./gradient";
+export {
+ formatKnowledge,
+ formatDistillations,
+ DISTILLATION_SYSTEM,
+ distillationUser,
+ RECURSIVE_SYSTEM,
+ recursiveUser,
+ CURATOR_SYSTEM,
+ curatorUser,
+ CONSOLIDATION_SYSTEM,
+ consolidationUser,
+ QUERY_EXPANSION_SYSTEM,
+} from "./prompt";
+export { shouldImport, importFromFile, exportToFile } from "./agents-file";
+export { workerSessionIDs, promptWorker, isWorkerSession } from "./worker";
+export {
+ ftsQuery,
+ ftsQueryOr,
+ EMPTY_QUERY,
+ reciprocalRankFusion,
+ expandQuery,
+ extractTopTerms,
+} from "./search";
+export {
+ serialize,
+ inline,
+ h,
+ p,
+ ul,
+ lip,
+ liph,
+ t,
+ root,
+ strong,
+ normalize,
+ sanitizeSurrogates,
+ unescapeMarkdown,
+} from "./markdown";
diff --git a/src/lat-reader.ts b/packages/core/src/lat-reader.ts
similarity index 100%
rename from src/lat-reader.ts
rename to packages/core/src/lat-reader.ts
diff --git a/src/log.ts b/packages/core/src/log.ts
similarity index 100%
rename from src/log.ts
rename to packages/core/src/log.ts
diff --git a/src/ltm.ts b/packages/core/src/ltm.ts
similarity index 100%
rename from src/ltm.ts
rename to packages/core/src/ltm.ts
diff --git a/src/markdown.ts b/packages/core/src/markdown.ts
similarity index 100%
rename from src/markdown.ts
rename to packages/core/src/markdown.ts
diff --git a/src/prompt.ts b/packages/core/src/prompt.ts
similarity index 100%
rename from src/prompt.ts
rename to packages/core/src/prompt.ts
diff --git a/src/search.ts b/packages/core/src/search.ts
similarity index 100%
rename from src/search.ts
rename to packages/core/src/search.ts
diff --git a/src/temporal.ts b/packages/core/src/temporal.ts
similarity index 100%
rename from src/temporal.ts
rename to packages/core/src/temporal.ts
diff --git a/src/worker.ts b/packages/core/src/worker.ts
similarity index 100%
rename from src/worker.ts
rename to packages/core/src/worker.ts
diff --git a/test/agents-file.test.ts b/packages/core/test/agents-file.test.ts
similarity index 100%
rename from test/agents-file.test.ts
rename to packages/core/test/agents-file.test.ts
diff --git a/test/config.test.ts b/packages/core/test/config.test.ts
similarity index 100%
rename from test/config.test.ts
rename to packages/core/test/config.test.ts
diff --git a/test/db.test.ts b/packages/core/test/db.test.ts
similarity index 100%
rename from test/db.test.ts
rename to packages/core/test/db.test.ts
diff --git a/test/embedding.test.ts b/packages/core/test/embedding.test.ts
similarity index 100%
rename from test/embedding.test.ts
rename to packages/core/test/embedding.test.ts
diff --git a/test/fixtures/lat.md/architecture.md b/packages/core/test/fixtures/lat.md/architecture.md
similarity index 100%
rename from test/fixtures/lat.md/architecture.md
rename to packages/core/test/fixtures/lat.md/architecture.md
diff --git a/test/fixtures/lat.md/auth.md b/packages/core/test/fixtures/lat.md/auth.md
similarity index 100%
rename from test/fixtures/lat.md/auth.md
rename to packages/core/test/fixtures/lat.md/auth.md
diff --git a/test/gradient.test.ts b/packages/core/test/gradient.test.ts
similarity index 100%
rename from test/gradient.test.ts
rename to packages/core/test/gradient.test.ts
diff --git a/test/integrity.test.ts b/packages/core/test/integrity.test.ts
similarity index 100%
rename from test/integrity.test.ts
rename to packages/core/test/integrity.test.ts
diff --git a/test/lat-reader.test.ts b/packages/core/test/lat-reader.test.ts
similarity index 100%
rename from test/lat-reader.test.ts
rename to packages/core/test/lat-reader.test.ts
diff --git a/test/ltm.test.ts b/packages/core/test/ltm.test.ts
similarity index 100%
rename from test/ltm.test.ts
rename to packages/core/test/ltm.test.ts
diff --git a/test/markdown.test.ts b/packages/core/test/markdown.test.ts
similarity index 79%
rename from test/markdown.test.ts
rename to packages/core/test/markdown.test.ts
index f5fca9f..14472bf 100644
--- a/test/markdown.test.ts
+++ b/packages/core/test/markdown.test.ts
@@ -3,7 +3,6 @@ import fc from "fast-check";
import { remark } from "remark";
import { normalize, unescapeMarkdown, sanitizeSurrogates, inline } from "../src/markdown";
import { formatDistillations, formatKnowledge } from "../src/prompt";
-import { isContextOverflow, buildRecoveryMessage } from "../src/index";
const proc = remark();
@@ -275,83 +274,6 @@ describe("unescapeMarkdown", () => {
});
});
-describe("isContextOverflow", () => {
- test("detects Anthropic 'prompt is too long' error", () => {
- expect(isContextOverflow({
- message: "prompt is too long: 214636 tokens > 200000 maximum",
- })).toBe(true);
- });
-
- test("detects wrapped APIError shape (error.data.message)", () => {
- expect(isContextOverflow({
- name: "APIError",
- data: { message: "prompt is too long: 214636 tokens > 200000 maximum" },
- })).toBe(true);
- });
-
- test("detects OpenAI 'context length exceeded'", () => {
- expect(isContextOverflow({
- message: "This model's maximum context length is 128000 tokens. However, your messages resulted in 150000 tokens. context length exceeded",
- })).toBe(true);
- });
-
- test("detects 'maximum context length'", () => {
- expect(isContextOverflow({
- message: "maximum context length exceeded",
- })).toBe(true);
- });
-
- test("detects 'too many tokens'", () => {
- expect(isContextOverflow({ message: "too many tokens" })).toBe(true);
- });
-
- test("detects 'ContextWindowExceededError'", () => {
- expect(isContextOverflow({
- message: "ContextWindowExceededError: request too large",
- })).toBe(true);
- });
-
- test("returns false for unrelated errors", () => {
- expect(isContextOverflow({ message: "rate limit exceeded" })).toBe(false);
- expect(isContextOverflow({ message: "internal server error" })).toBe(false);
- expect(isContextOverflow({ name: "TimeoutError" })).toBe(false);
- });
-
- test("returns false for null/undefined", () => {
- expect(isContextOverflow(null)).toBe(false);
- expect(isContextOverflow(undefined)).toBe(false);
- });
-});
-
-describe("buildRecoveryMessage", () => {
- test("includes distilled history when summaries exist", () => {
- const msg = buildRecoveryMessage([
- { observations: "Fixed the bug in auth.ts", generation: 0 },
- ]);
- expect(msg).toContain("");
- expect(msg).toContain("");
- expect(msg).toContain("context overflow error");
- expect(msg).toContain("Fixed the bug in auth.ts");
- });
-
- test("includes meta and recent sections from formatDistillations", () => {
- const msg = buildRecoveryMessage([
- { observations: "Earlier consolidated work", generation: 1 },
- { observations: "Recent detailed work", generation: 0 },
- ]);
- expect(msg).toContain("Earlier Work (summarized)");
- expect(msg).toContain("Recent Work (distilled)");
- expect(msg).toContain("Earlier consolidated work");
- expect(msg).toContain("Recent detailed work");
- });
-
- test("shows fallback message when no summaries available", () => {
- const msg = buildRecoveryMessage([]);
- expect(msg).toContain("");
- expect(msg).toContain("No distilled history available");
- });
-});
-
describe("sanitizeSurrogates", () => {
test("passes through normal text unchanged", () => {
expect(sanitizeSurrogates("hello world")).toBe("hello world");
diff --git a/test/refs.test.ts b/packages/core/test/refs.test.ts
similarity index 100%
rename from test/refs.test.ts
rename to packages/core/test/refs.test.ts
diff --git a/test/search.test.ts b/packages/core/test/search.test.ts
similarity index 100%
rename from test/search.test.ts
rename to packages/core/test/search.test.ts
diff --git a/test/setup.ts b/packages/core/test/setup.ts
similarity index 100%
rename from test/setup.ts
rename to packages/core/test/setup.ts
diff --git a/test/temporal.test.ts b/packages/core/test/temporal.test.ts
similarity index 100%
rename from test/temporal.test.ts
rename to packages/core/test/temporal.test.ts
diff --git a/test/worker.test.ts b/packages/core/test/worker.test.ts
similarity index 100%
rename from test/worker.test.ts
rename to packages/core/test/worker.test.ts
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
new file mode 100644
index 0000000..cc5979b
--- /dev/null
+++ b/packages/core/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "noEmit": true
+ },
+ "include": ["src", "test", "script"]
+}
diff --git a/eval/backfill.ts b/packages/opencode/eval/backfill.ts
similarity index 99%
rename from eval/backfill.ts
rename to packages/opencode/eval/backfill.ts
index 5d3d4d7..1f22647 100644
--- a/eval/backfill.ts
+++ b/packages/opencode/eval/backfill.ts
@@ -1,5 +1,5 @@
import { Database } from "bun:sqlite";
-import { DISTILLATION_SYSTEM, distillationUser } from "../src/prompt";
+import { DISTILLATION_SYSTEM, distillationUser } from "@loreai/core";
const BASE_URL = "http://localhost:4096";
const MODEL = { providerID: "anthropic", modelID: "claude-sonnet-4-6" };
diff --git a/eval/coding_eval.ts b/packages/opencode/eval/coding_eval.ts
similarity index 99%
rename from eval/coding_eval.ts
rename to packages/opencode/eval/coding_eval.ts
index 770b51e..6038b66 100644
--- a/eval/coding_eval.ts
+++ b/packages/opencode/eval/coding_eval.ts
@@ -1,6 +1,6 @@
import { parseArgs } from "util";
import { Database } from "bun:sqlite";
-import { DISTILLATION_SYSTEM, distillationUser } from "../src/prompt";
+import { DISTILLATION_SYSTEM, distillationUser } from "@loreai/core";
const BASE_URL = "http://localhost:4096";
const MODEL = { providerID: "anthropic", modelID: "claude-sonnet-4-6" };
diff --git a/eval/data/coding_memory_eval.json b/packages/opencode/eval/data/coding_memory_eval.json
similarity index 100%
rename from eval/data/coding_memory_eval.json
rename to packages/opencode/eval/data/coding_memory_eval.json
diff --git a/eval/data/coding_session_eval.json b/packages/opencode/eval/data/coding_session_eval.json
similarity index 100%
rename from eval/data/coding_session_eval.json
rename to packages/opencode/eval/data/coding_session_eval.json
diff --git a/eval/data/sessions/cli-nightly.json b/packages/opencode/eval/data/sessions/cli-nightly.json
similarity index 100%
rename from eval/data/sessions/cli-nightly.json
rename to packages/opencode/eval/data/sessions/cli-nightly.json
diff --git a/eval/data/sessions/cli-sentry-issue.json b/packages/opencode/eval/data/sessions/cli-sentry-issue.json
similarity index 100%
rename from eval/data/sessions/cli-sentry-issue.json
rename to packages/opencode/eval/data/sessions/cli-sentry-issue.json
diff --git a/eval/extract_session.ts b/packages/opencode/eval/extract_session.ts
similarity index 100%
rename from eval/extract_session.ts
rename to packages/opencode/eval/extract_session.ts
diff --git a/eval/results/.keep b/packages/opencode/eval/results/.keep
similarity index 100%
rename from eval/results/.keep
rename to packages/opencode/eval/results/.keep
diff --git a/eval/results/coding_eval_v3.jsonl b/packages/opencode/eval/results/coding_eval_v3.jsonl
similarity index 100%
rename from eval/results/coding_eval_v3.jsonl
rename to packages/opencode/eval/results/coding_eval_v3.jsonl
diff --git a/eval/results/session_eval_v1.jsonl b/packages/opencode/eval/results/session_eval_v1.jsonl
similarity index 100%
rename from eval/results/session_eval_v1.jsonl
rename to packages/opencode/eval/results/session_eval_v1.jsonl
diff --git a/eval/results/session_eval_v2.jsonl b/packages/opencode/eval/results/session_eval_v2.jsonl
similarity index 100%
rename from eval/results/session_eval_v2.jsonl
rename to packages/opencode/eval/results/session_eval_v2.jsonl
diff --git a/eval/results/session_eval_v3.jsonl b/packages/opencode/eval/results/session_eval_v3.jsonl
similarity index 100%
rename from eval/results/session_eval_v3.jsonl
rename to packages/opencode/eval/results/session_eval_v3.jsonl
diff --git a/eval/session_eval.ts b/packages/opencode/eval/session_eval.ts
similarity index 99%
rename from eval/session_eval.ts
rename to packages/opencode/eval/session_eval.ts
index 02a1c94..e343279 100644
--- a/eval/session_eval.ts
+++ b/packages/opencode/eval/session_eval.ts
@@ -10,7 +10,7 @@
*/
import { parseArgs } from "util";
import { Database } from "bun:sqlite";
-import { DISTILLATION_SYSTEM, distillationUser } from "../src/prompt";
+import { DISTILLATION_SYSTEM, distillationUser } from "@loreai/core";
// --- Config ---
const BASE_URL = "http://localhost:4096";
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
new file mode 100644
index 0000000..e18d9d1
--- /dev/null
+++ b/packages/opencode/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "opencode-lore",
+ "version": "0.9.1",
+ "type": "module",
+ "license": "MIT",
+ "description": "Three-tier memory architecture for OpenCode — distillation, not summarization",
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "exports": {
+ ".": {
+ "types": "./src/index.ts",
+ "bun": "./src/index.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "scripts": {
+ "typecheck": "tsc --noEmit",
+ "build": "bun run script/build.ts"
+ },
+ "peerDependencies": {
+ "@opencode-ai/plugin": ">=1.1.0"
+ },
+ "dependencies": {
+ "@loreai/core": "workspace:*"
+ },
+ "devDependencies": {
+ "@opencode-ai/plugin": "^1.1.39",
+ "@opencode-ai/sdk": "^1.1.39"
+ },
+ "files": [
+ "src/",
+ "dist/",
+ "README.md",
+ "LICENSE"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/BYK/opencode-lore.git",
+ "directory": "packages/opencode"
+ },
+ "keywords": [
+ "opencode",
+ "plugin",
+ "memory",
+ "agent",
+ "distillation",
+ "llm"
+ ],
+ "author": "BYK"
+}
diff --git a/scripts/distill-session.ts b/packages/opencode/scripts/distill-session.ts
similarity index 93%
rename from scripts/distill-session.ts
rename to packages/opencode/scripts/distill-session.ts
index 99ad8a2..9a3636e 100644
--- a/scripts/distill-session.ts
+++ b/packages/opencode/scripts/distill-session.ts
@@ -16,10 +16,7 @@
import { parseArgs } from "util";
import { createOpencodeClient } from "@opencode-ai/sdk";
-import { load, config } from "../src/config";
-import { ensureProject } from "../src/db";
-import * as temporal from "../src/temporal";
-import * as distillation from "../src/distillation";
+import { load, config, ensureProject, temporal, distillation } from "@loreai/core";
const { values, positionals } = parseArgs({
args: Bun.argv.slice(2),
diff --git a/scripts/list-sessions.ts b/packages/opencode/scripts/list-sessions.ts
similarity index 96%
rename from scripts/list-sessions.ts
rename to packages/opencode/scripts/list-sessions.ts
index d270ac1..af9a83c 100644
--- a/scripts/list-sessions.ts
+++ b/packages/opencode/scripts/list-sessions.ts
@@ -10,8 +10,7 @@
*/
import { parseArgs } from "util";
-import { load } from "../src/config";
-import { db } from "../src/db";
+import { load, db } from "@loreai/core";
const { values } = parseArgs({
args: Bun.argv.slice(2),
diff --git a/src/index.ts b/packages/opencode/src/index.ts
similarity index 98%
rename from src/index.ts
rename to packages/opencode/src/index.ts
index 1ad484f..efd221b 100644
--- a/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -1,12 +1,14 @@
import type { Plugin, Hooks } from "@opencode-ai/plugin";
import { join } from "path";
-import { load, config } from "./config";
-import { ensureProject, isFirstRun } from "./db";
-import * as temporal from "./temporal";
-import * as ltm from "./ltm";
-import * as distillation from "./distillation";
-import * as curator from "./curator";
import {
+ load,
+ config,
+ ensureProject,
+ isFirstRun,
+ temporal,
+ ltm,
+ distillation,
+ curator,
transform,
setModelLimits,
needsUrgentDistillation,
@@ -16,14 +18,17 @@ import {
setForceMinLayer,
getLastTransformedCount,
getLastTransformEstimate,
-} from "./gradient";
-import { formatKnowledge, formatDistillations } from "./prompt";
+ formatKnowledge,
+ formatDistillations,
+ shouldImport,
+ importFromFile,
+ exportToFile,
+ latReader,
+ embedding,
+ log,
+ isWorkerSession,
+} from "@loreai/core";
import { createRecallTool } from "./reflect";
-import { shouldImport, importFromFile, exportToFile } from "./agents-file";
-import * as latReader from "./lat-reader";
-import * as embedding from "./embedding";
-import * as log from "./log";
-import { isWorkerSession } from "./worker";
/**
* Detect whether an error from session.error is a context overflow ("prompt too long").
diff --git a/src/reflect.ts b/packages/opencode/src/reflect.ts
similarity index 97%
rename from src/reflect.ts
rename to packages/opencode/src/reflect.ts
index 20c87b3..c47a053 100644
--- a/src/reflect.ts
+++ b/packages/opencode/src/reflect.ts
@@ -1,14 +1,30 @@
import { tool } from "@opencode-ai/plugin/tool";
import type { createOpencodeClient } from "@opencode-ai/sdk";
-import * as temporal from "./temporal";
-import * as ltm from "./ltm";
-import * as latReader from "./lat-reader";
-import * as log from "./log";
-import * as embedding from "./embedding";
-import { db, ensureProject, projectName } from "./db";
-import { ftsQuery, ftsQueryOr, EMPTY_QUERY, reciprocalRankFusion, expandQuery } from "./search";
-import { serialize, inline, h, p, ul, lip, liph, t, root } from "./markdown";
-import type { LoreConfig } from "./config";
+import {
+ temporal,
+ ltm,
+ latReader,
+ log,
+ embedding,
+ db,
+ ensureProject,
+ projectName,
+ ftsQuery,
+ ftsQueryOr,
+ EMPTY_QUERY,
+ reciprocalRankFusion,
+ expandQuery,
+ serialize,
+ inline,
+ h,
+ p,
+ ul,
+ lip,
+ liph,
+ t,
+ root,
+ type LoreConfig,
+} from "@loreai/core";
type Client = ReturnType;
diff --git a/test/index.test.ts b/packages/opencode/test/index.test.ts
similarity index 99%
rename from test/index.test.ts
rename to packages/opencode/test/index.test.ts
index 4878783..ed97b7a 100644
--- a/test/index.test.ts
+++ b/packages/opencode/test/index.test.ts
@@ -1,8 +1,6 @@
import { describe, test, expect, beforeEach } from "bun:test";
import { isContextOverflow, buildRecoveryMessage, LorePlugin, isValidProjectPath } from "../src/index";
-import * as ltm from "../src/ltm";
-import { db } from "../src/db";
-import { getLtmTokens, setModelLimits, calibrate, setLtmTokens } from "../src/gradient";
+import { ltm, db, getLtmTokens, setModelLimits, calibrate, setLtmTokens } from "@loreai/core";
import type { Plugin } from "@opencode-ai/plugin";
import type { Message, Part } from "@opencode-ai/sdk";
diff --git a/packages/opencode/test/recovery.test.ts b/packages/opencode/test/recovery.test.ts
new file mode 100644
index 0000000..c5aa313
--- /dev/null
+++ b/packages/opencode/test/recovery.test.ts
@@ -0,0 +1,79 @@
+import { describe, test, expect } from "bun:test";
+import { isContextOverflow, buildRecoveryMessage } from "../src/index";
+
+describe("isContextOverflow", () => {
+ test("detects Anthropic 'prompt is too long' error", () => {
+ expect(isContextOverflow({
+ message: "prompt is too long: 214636 tokens > 200000 maximum",
+ })).toBe(true);
+ });
+
+ test("detects wrapped APIError shape (error.data.message)", () => {
+ expect(isContextOverflow({
+ name: "APIError",
+ data: { message: "prompt is too long: 214636 tokens > 200000 maximum" },
+ })).toBe(true);
+ });
+
+ test("detects OpenAI 'context length exceeded'", () => {
+ expect(isContextOverflow({
+ message: "This model's maximum context length is 128000 tokens. However, your messages resulted in 150000 tokens. context length exceeded",
+ })).toBe(true);
+ });
+
+ test("detects 'maximum context length'", () => {
+ expect(isContextOverflow({
+ message: "maximum context length exceeded",
+ })).toBe(true);
+ });
+
+ test("detects 'too many tokens'", () => {
+ expect(isContextOverflow({ message: "too many tokens" })).toBe(true);
+ });
+
+ test("detects 'ContextWindowExceededError'", () => {
+ expect(isContextOverflow({
+ message: "ContextWindowExceededError: request too large",
+ })).toBe(true);
+ });
+
+ test("returns false for unrelated errors", () => {
+ expect(isContextOverflow({ message: "rate limit exceeded" })).toBe(false);
+ expect(isContextOverflow({ message: "internal server error" })).toBe(false);
+ expect(isContextOverflow({ name: "TimeoutError" })).toBe(false);
+ });
+
+ test("returns false for null/undefined", () => {
+ expect(isContextOverflow(null)).toBe(false);
+ expect(isContextOverflow(undefined)).toBe(false);
+ });
+});
+
+describe("buildRecoveryMessage", () => {
+ test("includes distilled history when summaries exist", () => {
+ const msg = buildRecoveryMessage([
+ { observations: "Fixed the bug in auth.ts", generation: 0 },
+ ]);
+ expect(msg).toContain("");
+ expect(msg).toContain("");
+ expect(msg).toContain("context overflow error");
+ expect(msg).toContain("Fixed the bug in auth.ts");
+ });
+
+ test("includes meta and recent sections from formatDistillations", () => {
+ const msg = buildRecoveryMessage([
+ { observations: "Earlier consolidated work", generation: 1 },
+ { observations: "Recent detailed work", generation: 0 },
+ ]);
+ expect(msg).toContain("Earlier Work (summarized)");
+ expect(msg).toContain("Recent Work (distilled)");
+ expect(msg).toContain("Earlier consolidated work");
+ expect(msg).toContain("Recent detailed work");
+ });
+
+ test("shows fallback message when no summaries available", () => {
+ const msg = buildRecoveryMessage([]);
+ expect(msg).toContain("");
+ expect(msg).toContain("No distilled history available");
+ });
+});
diff --git a/packages/opencode/tsconfig.json b/packages/opencode/tsconfig.json
new file mode 100644
index 0000000..e943d76
--- /dev/null
+++ b/packages/opencode/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "noEmit": true
+ },
+ "include": ["src", "test", "script", "scripts", "eval"]
+}
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 0000000..3df0b2b
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "declaration": true,
+ "types": ["bun"]
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index fc0043c..f405837 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,16 +1,14 @@
{
+ "extends": "./tsconfig.base.json",
"compilerOptions": {
- "target": "ESNext",
- "module": "ESNext",
- "moduleResolution": "bundler",
- "strict": true,
- "esModuleInterop": true,
- "skipLibCheck": true,
- "outDir": "dist",
- "rootDir": "src",
- "declaration": true,
- "types": ["bun"]
+ "noEmit": true
},
- "include": ["src"],
- "exclude": ["node_modules", "dist", "test"]
+ "include": [
+ "packages/*/src",
+ "packages/*/test",
+ "packages/*/script",
+ "packages/*/scripts",
+ "packages/*/eval"
+ ],
+ "exclude": ["node_modules", "**/dist"]
}