apsolut-cortex 0.1.2 → 0.2.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/dist/cli.js CHANGED
@@ -1,43 +1,30 @@
1
1
  #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __export = (target, all) => {
4
- for (var name in all)
5
- __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true,
8
- configurable: true,
9
- set: (newValue) => all[name] = () => newValue
10
- });
11
- };
12
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
2
+
3
+ // src/cli.ts
4
+ import {
5
+ existsSync as existsSync3,
6
+ mkdirSync as mkdirSync3,
7
+ readFileSync as readFileSync2,
8
+ writeFileSync as writeFileSync2
9
+ } from "fs";
10
+ import { join as join2, resolve, dirname as dirname2 } from "path";
11
+ import { homedir as homedir2 } from "os";
12
+ import { fileURLToPath, pathToFileURL } from "url";
13
+
14
+ // src/registry.ts
15
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
16
+ import { dirname } from "path";
13
17
 
14
18
  // src/db.ts
15
- var exports_db = {};
16
- __export(exports_db, {
17
- upsertSession: () => upsertSession,
18
- upsertProject: () => upsertProject,
19
- updateWeight: () => updateWeight,
20
- searchVector: () => searchVector,
21
- searchBM25: () => searchBM25,
22
- mergeRRF: () => mergeRRF,
23
- markObservationsPromoted: () => markObservationsPromoted,
24
- insertObservation: () => insertObservation,
25
- insertMemory: () => insertMemory,
26
- getSessionObservations: () => getSessionObservations,
27
- getRecentSummaries: () => getRecentSummaries,
28
- getDb: () => getDb,
29
- decayAndPrune: () => decayAndPrune,
30
- cosineSimilarity: () => cosineSimilarity,
31
- applyMMR: () => applyMMR,
32
- REGISTRY_PATH: () => REGISTRY_PATH,
33
- MODELS_DIR: () => MODELS_DIR,
34
- DB_PATH: () => DB_PATH,
35
- CORTEX_DIR: () => CORTEX_DIR
36
- });
37
19
  import Database from "better-sqlite3";
38
20
  import { existsSync, mkdirSync } from "fs";
39
21
  import { homedir } from "os";
40
22
  import { join } from "path";
23
+ var CORTEX_DIR = join(homedir(), ".apsolut");
24
+ var DB_PATH = join(CORTEX_DIR, "memory.db");
25
+ var REGISTRY_PATH = join(CORTEX_DIR, "registry.json");
26
+ var MODELS_DIR = join(CORTEX_DIR, "models");
27
+ var _db = null;
41
28
  function getDb() {
42
29
  if (_db)
43
30
  return _db;
@@ -139,195 +126,8 @@ function getDb() {
139
126
  `);
140
127
  return _db;
141
128
  }
142
- function upsertProject(db, project) {
143
- const existing = db.prepare("SELECT id FROM projects WHERE id = ?").get(project.id);
144
- if (existing) {
145
- db.prepare("UPDATE projects SET last_session = ? WHERE id = ?").run(Date.now(), project.id);
146
- } else {
147
- db.prepare("INSERT INTO projects (id, name, path, created_at) VALUES (?, ?, ?, ?)").run(project.id, project.name, project.path ?? null, Date.now());
148
- }
149
- }
150
- function upsertSession(db, s) {
151
- const existing = db.prepare("SELECT id FROM sessions WHERE id = ?").get(s.id);
152
- if (existing) {
153
- const sets = [];
154
- const vals = [];
155
- if (s.ended_at !== undefined) {
156
- sets.push("ended_at = ?");
157
- vals.push(s.ended_at);
158
- }
159
- if (s.summary !== undefined) {
160
- sets.push("summary = ?");
161
- vals.push(s.summary);
162
- }
163
- if (s.memories_stored !== undefined) {
164
- sets.push("memories_stored = ?");
165
- vals.push(s.memories_stored);
166
- }
167
- if (s.tool_failures !== undefined) {
168
- sets.push("tool_failures = ?");
169
- vals.push(s.tool_failures);
170
- }
171
- if (sets.length) {
172
- vals.push(s.id);
173
- db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...vals);
174
- }
175
- } else {
176
- db.prepare("INSERT INTO sessions (id, project_id, started_at) VALUES (?, ?, ?)").run(s.id, s.project_id, Date.now());
177
- }
178
- }
179
- function getRecentSummaries(db, projectId, limit = 3) {
180
- const rows = db.prepare(`
181
- SELECT summary FROM sessions
182
- WHERE project_id = ? AND summary IS NOT NULL AND summary != ''
183
- ORDER BY started_at DESC LIMIT ?
184
- `).all(projectId, limit);
185
- return rows.map((r) => r.summary);
186
- }
187
- function insertObservation(db, obs) {
188
- db.prepare(`
189
- INSERT INTO observations (id, session_id, project_id, tool_name, content, category, created_at)
190
- VALUES (?, ?, ?, ?, ?, ?, ?)
191
- `).run(crypto.randomUUID(), obs.session_id, obs.project_id, obs.tool_name ?? null, obs.content, obs.category ?? null, Date.now());
192
- }
193
- function getSessionObservations(db, sessionId) {
194
- return db.prepare("SELECT tool_name, content, category FROM observations WHERE session_id = ? AND promoted = 0 ORDER BY created_at ASC").all(sessionId);
195
- }
196
- function markObservationsPromoted(db, sessionId) {
197
- db.prepare("UPDATE observations SET promoted = 1 WHERE session_id = ?").run(sessionId);
198
- }
199
- function insertMemory(db, m) {
200
- const id = crypto.randomUUID();
201
- db.prepare(`
202
- INSERT INTO memories
203
- (id, project_id, tier, category, trust, content, context,
204
- source, embedding, weight, used_count, created_at, session_id)
205
- VALUES
206
- (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
207
- `).run(id, m.project_id, m.tier, m.category, m.trust, m.content, m.context ?? null, m.source, m.embedding ?? null, m.weight, Date.now(), m.session_id ?? null);
208
- return id;
209
- }
210
- function searchBM25(db, projectId, query, limit) {
211
- return db.prepare(`
212
- SELECT m.* FROM memories_fts
213
- JOIN memories m ON m.rowid = memories_fts.rowid
214
- WHERE memories_fts MATCH ? AND m.project_id = ?
215
- ORDER BY bm25(memories_fts) LIMIT ?
216
- `).all(query, projectId, limit);
217
- }
218
- function cosineSimilarity(a, b) {
219
- let dot = 0, na = 0, nb = 0;
220
- for (let i = 0;i < a.length; i++) {
221
- dot += a[i] * b[i];
222
- na += a[i] * a[i];
223
- nb += b[i] * b[i];
224
- }
225
- const d = Math.sqrt(na) * Math.sqrt(nb);
226
- return d === 0 ? 0 : dot / d;
227
- }
228
- function searchVector(db, projectId, queryEmb, limit) {
229
- const candidates = db.prepare("SELECT * FROM memories WHERE project_id = ? AND embedding IS NOT NULL").all(projectId);
230
- return candidates.map((m) => ({
231
- ...m,
232
- similarity: cosineSimilarity(queryEmb, new Float32Array(m.embedding.buffer))
233
- })).sort((a, b) => b.similarity - a.similarity).slice(0, limit);
234
- }
235
- function mergeRRF(list1, list2, limit, allItems) {
236
- const k = 60;
237
- const scores = new Map;
238
- list1.forEach((r, i) => scores.set(r.id, (scores.get(r.id) ?? 0) + 1 / (i + k)));
239
- list2.forEach((r, i) => scores.set(r.id, (scores.get(r.id) ?? 0) + 1 / (i + k)));
240
- return [...scores.entries()].sort(([, a], [, b]) => b - a).slice(0, limit).map(([id]) => allItems.get(id)).filter(Boolean);
241
- }
242
- function applyMMR(candidates, queryEmb, limit, lambda = 0.7) {
243
- if (!queryEmb || candidates.length <= limit)
244
- return candidates.slice(0, limit);
245
- const selected = [];
246
- const remaining = [...candidates];
247
- while (selected.length < limit && remaining.length > 0) {
248
- let bestIdx = 0;
249
- let bestScore = -Infinity;
250
- for (let i = 0;i < remaining.length; i++) {
251
- const cand = remaining[i];
252
- const candEmb = cand.embedding ? new Float32Array(cand.embedding.buffer) : null;
253
- if (!candEmb) {
254
- bestIdx = i;
255
- break;
256
- }
257
- const relevance = cand.similarity ?? cosineSimilarity(queryEmb, candEmb);
258
- const maxSim = selected.reduce((max, s) => {
259
- if (!s.embedding)
260
- return max;
261
- const sim = cosineSimilarity(candEmb, new Float32Array(s.embedding.buffer));
262
- return Math.max(max, sim);
263
- }, 0);
264
- const score = lambda * relevance - (1 - lambda) * maxSim;
265
- if (score > bestScore) {
266
- bestScore = score;
267
- bestIdx = i;
268
- }
269
- }
270
- selected.push(remaining[bestIdx]);
271
- remaining.splice(bestIdx, 1);
272
- }
273
- return selected;
274
- }
275
- function updateWeight(db, id, score) {
276
- const mem = db.prepare("SELECT weight, used_count FROM memories WHERE id = ?").get(id);
277
- if (!mem)
278
- return;
279
- const alpha = 0.3;
280
- const newWeight = alpha * (score / 3) + (1 - alpha) * mem.weight;
281
- const newTrust = newWeight > 1.4 || mem.used_count + 1 >= 3 ? "validated" : undefined;
282
- if (newTrust) {
283
- db.prepare("UPDATE memories SET weight = ?, used_count = used_count + 1, last_used = ?, trust = CASE WHEN trust = 'observed' THEN ? ELSE trust END WHERE id = ?").run(newWeight, Date.now(), newTrust, id);
284
- } else {
285
- db.prepare("UPDATE memories SET weight = ?, used_count = used_count + 1, last_used = ? WHERE id = ?").run(newWeight, Date.now(), id);
286
- }
287
- }
288
- function decayAndPrune(db, projectId) {
289
- const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
290
- const decayed = db.prepare(`
291
- UPDATE memories
292
- SET weight = weight * CASE
293
- WHEN trust IN ('proven', 'canonical') THEN 1.0
294
- WHEN trust = 'validated' THEN 0.98
295
- ELSE 0.95
296
- END
297
- WHERE project_id = ?
298
- AND trust NOT IN ('canonical')
299
- AND (last_used IS NULL OR last_used < ?)
300
- `).run(projectId, cutoff).changes;
301
- const pruned = db.prepare(`
302
- DELETE FROM memories
303
- WHERE project_id = ? AND weight < 0.1 AND used_count > 3
304
- AND trust NOT IN ('proven', 'canonical')
305
- `).run(projectId).changes;
306
- return { decayed, pruned };
307
- }
308
- var CORTEX_DIR, DB_PATH, REGISTRY_PATH, MODELS_DIR, _db = null;
309
- var init_db = __esm(() => {
310
- CORTEX_DIR = join(homedir(), ".apsolut");
311
- DB_PATH = join(CORTEX_DIR, "memory.db");
312
- REGISTRY_PATH = join(CORTEX_DIR, "registry.json");
313
- MODELS_DIR = join(CORTEX_DIR, "models");
314
- });
315
-
316
- // src/cli.ts
317
- import {
318
- existsSync as existsSync3,
319
- mkdirSync as mkdirSync3,
320
- readFileSync as readFileSync2,
321
- writeFileSync as writeFileSync2
322
- } from "fs";
323
- import { join as join2, resolve, dirname as dirname2 } from "path";
324
- import { homedir as homedir2 } from "os";
325
- import { fileURLToPath } from "url";
326
129
 
327
130
  // src/registry.ts
328
- init_db();
329
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
330
- import { dirname } from "path";
331
131
  function readRegistry() {
332
132
  if (!existsSync2(REGISTRY_PATH))
333
133
  return { projects: {} };
@@ -403,7 +203,7 @@ async function runHook(name) {
403
203
  `);
404
204
  process.exit(0);
405
205
  }
406
- await import(hookPath);
206
+ await import(pathToFileURL(hookPath).href);
407
207
  }
408
208
  async function init() {
409
209
  console.log(`
@@ -453,10 +253,10 @@ apsolut-cortex init
453
253
  console.log(`✓ Written .mcp.json`);
454
254
  const hookCmd = IS_DIST ? "apsolut-cortex" : `bun run ${join2(__dirname2, "cli.ts")}`;
455
255
  const hookEntries = {
456
- SessionStart: [{ command: `${hookCmd} hook:session-start` }],
457
- PostToolUse: [{ command: `${hookCmd} hook:post-tool-use` }],
458
- Stop: [{ command: `${hookCmd} hook:stop` }],
459
- SessionEnd: [{ command: `${hookCmd} hook:session-end` }]
256
+ SessionStart: [{ matcher: "", hooks: [{ type: "command", command: `${hookCmd} hook:session-start` }] }],
257
+ PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: `${hookCmd} hook:post-tool-use` }] }],
258
+ Stop: [{ matcher: "", hooks: [{ type: "command", command: `${hookCmd} hook:stop` }] }],
259
+ SessionEnd: [{ matcher: "", hooks: [{ type: "command", command: `${hookCmd} hook:session-end` }] }]
460
260
  };
461
261
  let settings = {};
462
262
  const settingsDir = dirname2(CLAUDE_SETTINGS);
@@ -501,7 +301,7 @@ apsolut-cortex init
501
301
  │ ██║ ██║██║ ███████║╚██████╔╝███████╗╚██████╔╝ ██║ │
502
302
  │ ╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ │
503
303
  │ │
504
- │ c o r t e x · v 0 . 1 . 0
304
+ │ c o r t e x · v ${PKG_VERSION}
505
305
  │ │
506
306
  └─────────────────────────────────────────────────────────┘
507
307
 
@@ -519,13 +319,12 @@ apsolut-cortex init
519
319
  console.log(BANNER);
520
320
  }
521
321
  async function status() {
522
- const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
523
322
  if (!existsSync3(PROJECT_CONFIG)) {
524
323
  console.log("No project found. Run: apsolut-cortex init");
525
324
  process.exit(1);
526
325
  }
527
326
  const project = JSON.parse(readFileSync2(PROJECT_CONFIG, "utf-8"));
528
- const db = getDb2();
327
+ const db = getDb();
529
328
  const total = db.prepare("SELECT COUNT(*) as n FROM memories WHERE project_id = ?").get(project.id).n;
530
329
  const sessions = db.prepare("SELECT COUNT(*) as n FROM sessions WHERE project_id = ?").get(project.id).n;
531
330
  const byTrust = db.prepare(`
@@ -572,7 +371,14 @@ function uninstall() {
572
371
  if (hooks) {
573
372
  for (const event of ["SessionStart", "PostToolUse", "Stop", "SessionEnd"]) {
574
373
  if (hooks[event]) {
575
- hooks[event] = hooks[event].filter((e) => !(typeof e === "object" && e.command?.includes("apsolut-cortex")));
374
+ hooks[event] = hooks[event].filter((e) => {
375
+ if (typeof e !== "object")
376
+ return true;
377
+ if (Array.isArray(e.hooks)) {
378
+ return !e.hooks.some((h) => h.command?.includes("apsolut-cortex"));
379
+ }
380
+ return !e.command?.includes("apsolut-cortex");
381
+ });
576
382
  }
577
383
  }
578
384
  writeFileSync2(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
@@ -1,6 +1,3 @@
1
- #!/usr/bin/env bun
2
- // @bun
3
-
4
1
  // src/hooks/post-tool-use.ts
5
2
  import { readFileSync, existsSync as existsSync2 } from "fs";
6
3
  import { join as join2 } from "path";
@@ -190,7 +187,12 @@ function classifyToolUse(toolName, toolInput, toolResponse) {
190
187
 
191
188
  // src/hooks/post-tool-use.ts
192
189
  async function main() {
193
- const raw = await Bun.stdin.text();
190
+ const raw = await new Promise((resolve) => {
191
+ let d = "";
192
+ process.stdin.setEncoding("utf-8");
193
+ process.stdin.on("data", (c) => d += c);
194
+ process.stdin.on("end", () => resolve(d));
195
+ });
194
196
  let data = {};
195
197
  try {
196
198
  data = JSON.parse(raw);
@@ -1,9 +1,6 @@
1
- #!/usr/bin/env bun
2
- // @bun
3
-
4
1
  // src/hooks/session-end.ts
5
2
  import { readFileSync, existsSync as existsSync2 } from "fs";
6
- import { join as join3 } from "path";
3
+ import { join as join2 } from "path";
7
4
 
8
5
  // src/db.ts
9
6
  import Database from "better-sqlite3";
@@ -324,18 +321,7 @@ async function compressSession(observations, project) {
324
321
 
325
322
  // src/embed.ts
326
323
  import { pipeline, env } from "@xenova/transformers";
327
-
328
- // src/db.ts
329
- import Database2 from "better-sqlite3";
330
- import { homedir as homedir2 } from "os";
331
- import { join as join2 } from "path";
332
- var CORTEX_DIR2 = join2(homedir2(), ".apsolut");
333
- var DB_PATH2 = join2(CORTEX_DIR2, "memory.db");
334
- var REGISTRY_PATH2 = join2(CORTEX_DIR2, "registry.json");
335
- var MODELS_DIR2 = join2(CORTEX_DIR2, "models");
336
-
337
- // src/embed.ts
338
- env.cacheDir = MODELS_DIR2;
324
+ env.cacheDir = MODELS_DIR;
339
325
  env.allowRemoteModels = true;
340
326
  var _embedder = null;
341
327
  async function getEmbedder() {
@@ -355,7 +341,12 @@ function float32ToBuffer(arr) {
355
341
 
356
342
  // src/hooks/session-end.ts
357
343
  async function main() {
358
- const raw = await Bun.stdin.text();
344
+ const raw = await new Promise((resolve) => {
345
+ let d = "";
346
+ process.stdin.setEncoding("utf-8");
347
+ process.stdin.on("data", (c) => d += c);
348
+ process.stdin.on("end", () => resolve(d));
349
+ });
359
350
  let data = {};
360
351
  try {
361
352
  data = JSON.parse(raw);
@@ -364,7 +355,7 @@ async function main() {
364
355
  }
365
356
  const cwd = data.cwd ?? process.cwd();
366
357
  const sessionId = data.session_id ?? "unknown";
367
- const projectFile = join3(cwd, ".apsolut", "project.json");
358
+ const projectFile = join2(cwd, ".apsolut", "project.json");
368
359
  if (!existsSync2(projectFile))
369
360
  process.exit(0);
370
361
  let project = null;
@@ -1,6 +1,3 @@
1
- #!/usr/bin/env bun
2
- // @bun
3
-
4
1
  // src/hooks/session-start.ts
5
2
  import { readFileSync, existsSync as existsSync2 } from "fs";
6
3
  import { join as join2 } from "path";
@@ -156,7 +153,12 @@ function upsertSession(db, s) {
156
153
 
157
154
  // src/hooks/session-start.ts
158
155
  async function main() {
159
- const raw = await Bun.stdin.text();
156
+ const raw = await new Promise((resolve) => {
157
+ let d = "";
158
+ process.stdin.setEncoding("utf-8");
159
+ process.stdin.on("data", (c) => d += c);
160
+ process.stdin.on("end", () => resolve(d));
161
+ });
160
162
  let data = {};
161
163
  try {
162
164
  data = JSON.parse(raw);
@@ -180,7 +182,7 @@ async function main() {
180
182
  const db = getDb();
181
183
  upsertProject(db, { id: project.id, name: project.name, path: cwd });
182
184
  upsertSession(db, { id: sessionId, project_id: project.id });
183
- process.stdout.write(`[apsolut-cortex] Project: ${project.name} \u2014 say "remember <topic>" to search memory.`);
185
+ process.stdout.write(`[apsolut-cortex] Project: ${project.name} say "remember <topic>" to search memory.`);
184
186
  } catch {
185
187
  process.exit(0);
186
188
  }
@@ -1,6 +1,3 @@
1
- #!/usr/bin/env bun
2
- // @bun
3
-
4
1
  // src/hooks/stop.ts
5
2
  import { readFileSync, existsSync as existsSync2 } from "fs";
6
3
  import { join as join2 } from "path";
@@ -158,7 +155,7 @@ var CORRECTION_PATTERNS = [
158
155
  /(?:my mistake|i was wrong|incorrect)[^.]*[.!]\s*(.{20,150})/gi,
159
156
  /(?:wait|oops)[,.]?\s+(.{20,150})/gi,
160
157
  /(?:turns? out|it seems?)\s+(.{20,150})/gi,
161
- /(?:should(?:n'?t)? have|shouldn'?t).{0,30}[\u2014\u2013-]\s*(.{20,150})/gi,
158
+ /(?:should(?:n'?t)? have|shouldn'?t).{0,30}[—–-]\s*(.{20,150})/gi,
162
159
  /(?:the correct|correct(?:ly)?)\s+(?:way|path|file|approach)\s+is\s+(.{20,150})/gi
163
160
  ];
164
161
  function extractCorrections(transcript) {
@@ -175,7 +172,12 @@ function extractCorrections(transcript) {
175
172
  return [...new Set(found)].slice(0, 5);
176
173
  }
177
174
  async function main() {
178
- const raw = await Bun.stdin.text();
175
+ const raw = await new Promise((resolve) => {
176
+ let d = "";
177
+ process.stdin.setEncoding("utf-8");
178
+ process.stdin.on("data", (c) => d += c);
179
+ process.stdin.on("end", () => resolve(d));
180
+ });
179
181
  let data = {};
180
182
  try {
181
183
  data = JSON.parse(raw);
@@ -9,7 +9,8 @@ import {
9
9
  ListToolsRequestSchema
10
10
  } from "@modelcontextprotocol/sdk/types.js";
11
11
  import { readFileSync, existsSync as existsSync2 } from "fs";
12
- import { join as join3 } from "path";
12
+ import { join as join3, dirname, resolve } from "path";
13
+ import { fileURLToPath } from "url";
13
14
 
14
15
  // src/db.ts
15
16
  import Database from "better-sqlite3";
@@ -264,7 +265,9 @@ var db = getDb();
264
265
  if (project?.id) {
265
266
  upsertProject(db, { id: project.id, name: project.name, path: PROJECT_PATH });
266
267
  }
267
- var server = new Server({ name: "apsolut-cortex", version: "0.1.0" }, { capabilities: { tools: {} } });
268
+ var __mcp_dirname = dirname(fileURLToPath(import.meta.url));
269
+ var PKG_VERSION = JSON.parse(readFileSync(resolve(__mcp_dirname, "..", "package.json"), "utf-8")).version;
270
+ var server = new Server({ name: "apsolut-cortex", version: PKG_VERSION }, { capabilities: { tools: {} } });
268
271
  function requireProject() {
269
272
  if (!project?.id)
270
273
  throw new Error("No project found. Run: apsolut-cortex init");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apsolut-cortex",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Persistent memory for Claude Code projects — stores corrections, decisions, and patterns across sessions",
5
5
  "type": "module",
6
6
  "bin": {