coolmem 1.0.0 → 1.0.1

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/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "coolmem",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Cross-Agent Memory Fabric — a privacy-first MCP server for shared AI memory with local vector embeddings.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "coolmem": "./dist/index.js"
9
9
  },
10
+ "files": [
11
+ "dist",
12
+ "prisma"
13
+ ],
10
14
  "scripts": {
15
+ "postinstall": "prisma generate",
11
16
  "build": "tsc && chmod +x dist/index.js",
12
17
  "start": "node dist/index.js",
13
18
  "dev": "node --loader ts-node/esm src/index.ts",
@@ -18,11 +23,11 @@
18
23
  "@modelcontextprotocol/sdk": "^1.6.1",
19
24
  "@prisma/client": "^5.22.0",
20
25
  "@xenova/transformers": "^2.17.2",
26
+ "prisma": "^5.22.0",
21
27
  "zod": "^3.23.8"
22
28
  },
23
29
  "devDependencies": {
24
30
  "@types/node": "^22.0.0",
25
- "prisma": "^5.22.0",
26
31
  "ts-node": "^10.9.2",
27
32
  "typescript": "^5.7.2"
28
33
  }
Binary file
package/seed.mjs DELETED
@@ -1,69 +0,0 @@
1
- /**
2
- * seed.mjs — stores architectural decisions about coolmem into coolmem itself.
3
- * Run once: node seed.mjs
4
- */
5
- import { PrismaClient } from "@prisma/client";
6
- import { pipeline, env } from "@xenova/transformers";
7
-
8
- const prisma = new PrismaClient();
9
- env.cacheDir = "./models";
10
-
11
- function encodeEmbedding(vector) {
12
- return Buffer.from(new Float32Array(vector).buffer);
13
- }
14
- function decodeEmbedding(buf) {
15
- return Array.from(new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4));
16
- }
17
- function dotProduct(a, b) {
18
- let dot = 0;
19
- for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
20
- return dot;
21
- }
22
-
23
- const DEDUP = 0.88;
24
-
25
- process.stdout.write("⏳ Loading model…\n");
26
- const extractor = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
27
- process.stdout.write("✅ Model ready.\n\n");
28
-
29
- async function embed(text) {
30
- const out = await extractor(text, { pooling: "mean", normalize: true });
31
- return Array.from(out.data);
32
- }
33
-
34
- async function store(content, category, project = "coolmem") {
35
- const vec = await embed(content);
36
- const existing = await prisma.memory.findMany({ where: { project }, select: { id: true, embedding: true } });
37
- for (const m of existing) {
38
- if (dotProduct(vec, decodeEmbedding(m.embedding)) >= DEDUP) {
39
- await prisma.memory.update({ where: { id: m.id }, data: { content, category, embedding: encodeEmbedding(vec) } });
40
- process.stdout.write(`♻️ Updated #${m.id} (duplicate)\n`);
41
- return;
42
- }
43
- }
44
- const r = await prisma.memory.create({ data: { content, category, project, embedding: encodeEmbedding(vec) } });
45
- process.stdout.write(`✅ Stored #${r.id} [${category}]\n`);
46
- }
47
-
48
- await store(
49
- "cosineSimilarity in similarity.ts is implemented as a pure dot product, not full cosine. This is correct and optimal because embeddings.ts always uses { normalize: true } with all-MiniLM-L6-v2, making every vector a unit vector (magnitude = 1). For unit vectors, cosine similarity = dot product. This saves ~60% of operations per comparison: removes 2 magnitude accumulators and 2 Math.sqrt calls across all 384 dimensions. If normalize is ever changed to false, full cosine must be restored.",
50
- "architecture"
51
- );
52
-
53
- await store(
54
- "Similarity search is brute-force O(n): all embeddings are loaded into RAM and scored per query. This is fine up to ~1,000 memories. Beyond that, switch to sqlite-vss (ANN index inside SQLite) or ChromaDB. Each embedding costs 1,536 bytes as Float32 BLOB.",
55
- "architecture"
56
- );
57
-
58
- await store(
59
- "DEDUP_THRESHOLD = 0.88 for store_memory deduplication. 0.95 was too strict (missed paraphrased rewrites of the same fact). 0.88 catches semantic duplicates without false-positives on genuinely different memories. MIN_SCORE_THRESHOLD = 0.30 for search (below this, results are irrelevant noise).",
60
- "decision"
61
- );
62
-
63
- await store(
64
- "all-MiniLM-L6-v2 (Xenova) is loaded lazily on first tool call and cached in ./models/. First-run downloads ~22 MB. It produces 384-dimensional L2-normalized float32 sentence embeddings. The pipeline instance is a module-level singleton — only one load per server process regardless of how many tool calls arrive.",
65
- "architecture"
66
- );
67
-
68
- await prisma.$disconnect();
69
- process.stdout.write("\n🎉 coolmem seeded with its own architecture decisions.\n");
package/test.mjs DELETED
@@ -1,185 +0,0 @@
1
- /**
2
- * test.mjs — Integration test for coolmem v2 (all optimizations)
3
- * Run with: node test.mjs
4
- */
5
-
6
- import { PrismaClient } from "@prisma/client";
7
- import { pipeline, env } from "@xenova/transformers";
8
-
9
- // ── color helpers ─────────────────────────────────────────────────────────────
10
- const bold = (s) => `\x1b[1m${s}\x1b[0m`;
11
- const dim = (s) => `\x1b[2m${s}\x1b[0m`;
12
- const green = (s) => `\x1b[32m${s}\x1b[0m`;
13
- const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
14
- const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
15
- const red = (s) => `\x1b[31m${s}\x1b[0m`;
16
- const hr = (c = "─", n = 60) => c.repeat(n);
17
-
18
- // ── binary codec (mirrors src/index.ts) ───────────────────────────────────────
19
- function encodeEmbedding(vector) {
20
- return Buffer.from(new Float32Array(vector).buffer);
21
- }
22
- function decodeEmbedding(buf) {
23
- return Array.from(new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4));
24
- }
25
- function cosineSimilarity(a, b) {
26
- let dot = 0, magA = 0, magB = 0;
27
- for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; magA += a[i] * a[i]; magB += b[i] * b[i]; }
28
- return dot / (Math.sqrt(magA) * Math.sqrt(magB));
29
- }
30
-
31
- // ── setup ─────────────────────────────────────────────────────────────────────
32
- const prisma = new PrismaClient();
33
- env.cacheDir = "./models";
34
- let extractor;
35
-
36
- async function loadModel() {
37
- process.stdout.write(yellow("⏳ Loading model…\n"));
38
- extractor = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
39
- process.stdout.write(green("✅ Model ready.\n\n"));
40
- }
41
- async function embed(text) {
42
- const out = await extractor(text, { pooling: "mean", normalize: true });
43
- return Array.from(out.data);
44
- }
45
-
46
- // ── tool simulations ──────────────────────────────────────────────────────────
47
- const DEDUP_THRESHOLD = 0.95;
48
- const MIN_SCORE = 0.30;
49
-
50
- async function storeMemory(content, category, project = "default") {
51
- const vector = await embed(content);
52
- const existing = await prisma.memory.findMany({ where: { project }, select: { id: true, embedding: true } });
53
- for (const m of existing) {
54
- const score = cosineSimilarity(vector, decodeEmbedding(m.embedding));
55
- if (score >= DEDUP_THRESHOLD) {
56
- await prisma.memory.update({ where: { id: m.id }, data: { content, category, embedding: encodeEmbedding(vector) } });
57
- return { id: m.id, duplicate: true, score };
58
- }
59
- }
60
- const r = await prisma.memory.create({ data: { content, category, project, embedding: encodeEmbedding(vector) } });
61
- return { id: r.id, duplicate: false };
62
- }
63
-
64
- async function searchMemories(query, project, limit = 3) {
65
- const qVec = await embed(query);
66
- const all = await prisma.memory.findMany({
67
- where: project ? { project } : undefined,
68
- select: { id: true, content: true, category: true, project: true, embedding: true }
69
- });
70
- return all
71
- .map(m => ({ ...m, score: cosineSimilarity(qVec, decodeEmbedding(m.embedding)) }))
72
- .filter(m => m.score >= MIN_SCORE)
73
- .sort((a, b) => b.score - a.score)
74
- .slice(0, limit);
75
- }
76
-
77
- async function deleteMemory(id) {
78
- return prisma.memory.delete({ where: { id } });
79
- }
80
-
81
- async function updateMemory(id, content, category) {
82
- const newEmbed = content ? encodeEmbedding(await embed(content)) : undefined;
83
- return prisma.memory.update({
84
- where: { id }, data: {
85
- ...(content && { content }), ...(category && { category }), ...(newEmbed && { embedding: newEmbed }),
86
- }
87
- });
88
- }
89
-
90
- // ── test runner ───────────────────────────────────────────────────────────────
91
- async function main() {
92
- console.log("\n" + bold("🧠 coolmem v2 — Integration Test"));
93
- console.log(hr("═") + "\n");
94
- await loadModel();
95
-
96
- // PHASE 1 — Store (two projects)
97
- console.log(bold("PHASE 1 — store_memory (with project scoping)"));
98
- console.log(hr() + "\n");
99
- const toStore = [
100
- { content: "We use SQLite with Prisma ORM. Embeddings stored as binary BLOBs (Float32Array).", category: "architecture", project: "coolmem" },
101
- { content: "all-MiniLM-L6-v2 runs locally on CPU: 22 MB, 384-dim, zero API cost.", category: "decision", project: "coolmem" },
102
- { content: "MCP server uses stdio transport — never write to stdout or you corrupt the protocol.", category: "architecture", project: "coolmem" },
103
- { content: "The admin dashboard uses MongoDB with Next.js API routes for the revenue KPI endpoint.", category: "architecture", project: "admin-dashboard" },
104
- ];
105
- for (const m of toStore) {
106
- const r = await storeMemory(m.content, m.category, m.project);
107
- const tag = r.duplicate ? yellow(`♻️ Deduplicated → updated #${r.id} (score ${r.score?.toFixed(4)})`) : green(`✅ Stored as #${r.id}`);
108
- console.log(` [${m.project}] ${tag}`);
109
- console.log(dim(` "${m.content.slice(0, 80)}…"\n`));
110
- }
111
-
112
- // PHASE 2 — Deduplication
113
- console.log(bold("PHASE 2 — Deduplication (store near-identical content)"));
114
- console.log(hr() + "\n");
115
- const dupResult = await storeMemory(
116
- "all-MiniLM-L6-v2 runs on CPU locally: 22 MB model, produces 384-dimensional embeddings, no API key needed.",
117
- "decision", "coolmem"
118
- );
119
- if (dupResult.duplicate) {
120
- console.log(yellow(` ♻️ Correctly detected duplicate! Updated #${dupResult.id} (score: ${dupResult.score.toFixed(4)})\n`));
121
- } else {
122
- console.log(red(` ❌ Duplicate NOT detected — threshold may need tuning.\n`));
123
- }
124
-
125
- // PHASE 3 — Search (project-scoped + score threshold)
126
- console.log(bold("PHASE 3 — search_memories (scoped + score threshold 0.30)"));
127
- console.log(hr() + "\n");
128
- const queries = [
129
- { q: "how are embeddings stored?", project: "coolmem" },
130
- { q: "what AI model do we use?", project: "coolmem" },
131
- { q: "what database does admin dashboard use?", project: "admin-dashboard" },
132
- { q: "quantum physics lecture notes", project: undefined }, // should return nothing
133
- ];
134
- for (const { q, project } of queries) {
135
- console.log(cyan(` 🔍 "${q}"${project ? ` [project: ${project}]` : " [all projects]"}`));
136
- const results = await searchMemories(q, project);
137
- if (results.length === 0) {
138
- console.log(dim(` → No results above threshold (correct for irrelevant query)\n`));
139
- } else {
140
- results.forEach((r, i) =>
141
- console.log(` #${i + 1} score=${r.score.toFixed(4)} [${r.project}/${r.category}]\n ${dim(r.content.slice(0, 90) + "…")}`)
142
- );
143
- console.log();
144
- }
145
- }
146
-
147
- // PHASE 4 — Update
148
- console.log(bold("PHASE 4 — update_memory"));
149
- console.log(hr() + "\n");
150
- const all = await prisma.memory.findMany({ take: 1, orderBy: { id: "asc" }, select: { id: true, content: true } });
151
- if (all.length > 0) {
152
- const { id } = all[0];
153
- await updateMemory(id, undefined, "architecture-v2"); // only category changed
154
- console.log(green(` ✅ Updated category of Memory #${id} to "architecture-v2" (no re-embed needed)\n`));
155
- }
156
-
157
- // PHASE 5 — Delete
158
- console.log(bold("PHASE 5 — delete_memory"));
159
- console.log(hr() + "\n");
160
- const last = await prisma.memory.findFirst({ orderBy: { id: "desc" }, select: { id: true } });
161
- if (last) {
162
- await deleteMemory(last.id);
163
- console.log(green(` ✅ Deleted Memory #${last.id}\n`));
164
- }
165
-
166
- // PHASE 6 — Timeline
167
- console.log(bold("PHASE 6 — project_timeline resource"));
168
- console.log(hr() + "\n");
169
- const timeline = await prisma.memory.findMany({
170
- orderBy: { createdAt: "desc" }, take: 10,
171
- select: { id: true, content: true, category: true, project: true, createdAt: true },
172
- });
173
- for (const m of timeline) {
174
- const ts = m.createdAt.toISOString().replace("T", " ").slice(0, 19);
175
- console.log(` [${ts}] #${m.id} | ${m.project} | ${m.category}`);
176
- console.log(dim(` ${m.content.slice(0, 90)}…\n`));
177
- }
178
-
179
- console.log(hr("═"));
180
- console.log(green(bold("🎉 All phases passed!")));
181
-
182
- await prisma.$disconnect();
183
- }
184
-
185
- main().catch(e => { console.error(red("FATAL:"), e); prisma.$disconnect(); process.exit(1); });