apsolut-cortex 0.1.1 → 0.2.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/cli.js +48 -343
- package/dist/hooks/post-tool-use.js +6 -4
- package/dist/hooks/session-end.js +9 -18
- package/dist/hooks/session-start.js +7 -5
- package/dist/hooks/stop.js +7 -5
- package/dist/mcp/server.js +5 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,335 +1,32 @@
|
|
|
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);
|
|
13
|
-
|
|
14
|
-
// 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
|
-
import Database from "better-sqlite3";
|
|
38
|
-
import { existsSync, mkdirSync } from "fs";
|
|
39
|
-
import { homedir } from "os";
|
|
40
|
-
import { join } from "path";
|
|
41
|
-
function getDb() {
|
|
42
|
-
if (_db)
|
|
43
|
-
return _db;
|
|
44
|
-
if (!existsSync(CORTEX_DIR))
|
|
45
|
-
mkdirSync(CORTEX_DIR, { recursive: true });
|
|
46
|
-
if (!existsSync(MODELS_DIR))
|
|
47
|
-
mkdirSync(MODELS_DIR, { recursive: true });
|
|
48
|
-
_db = new Database(DB_PATH);
|
|
49
|
-
_db.pragma("journal_mode = WAL");
|
|
50
|
-
_db.pragma("synchronous = NORMAL");
|
|
51
|
-
_db.pragma("foreign_keys = ON");
|
|
52
|
-
_db.pragma("cache_size = -32000");
|
|
53
|
-
_db.exec(`
|
|
54
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
55
|
-
id TEXT PRIMARY KEY,
|
|
56
|
-
name TEXT NOT NULL,
|
|
57
|
-
path TEXT,
|
|
58
|
-
created_at INTEGER NOT NULL,
|
|
59
|
-
last_session INTEGER
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
63
|
-
id TEXT PRIMARY KEY,
|
|
64
|
-
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
65
|
-
started_at INTEGER NOT NULL,
|
|
66
|
-
ended_at INTEGER,
|
|
67
|
-
summary TEXT,
|
|
68
|
-
memories_injected INTEGER NOT NULL DEFAULT 0,
|
|
69
|
-
memories_stored INTEGER NOT NULL DEFAULT 0,
|
|
70
|
-
tool_failures INTEGER NOT NULL DEFAULT 0
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
74
|
-
ON sessions(project_id, started_at DESC);
|
|
75
|
-
|
|
76
|
-
CREATE TABLE IF NOT EXISTS observations (
|
|
77
|
-
id TEXT PRIMARY KEY,
|
|
78
|
-
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
79
|
-
project_id TEXT NOT NULL,
|
|
80
|
-
tool_name TEXT,
|
|
81
|
-
content TEXT NOT NULL,
|
|
82
|
-
category TEXT,
|
|
83
|
-
created_at INTEGER NOT NULL,
|
|
84
|
-
promoted INTEGER NOT NULL DEFAULT 0
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
CREATE INDEX IF NOT EXISTS idx_obs_session ON observations(session_id);
|
|
88
|
-
CREATE INDEX IF NOT EXISTS idx_obs_project ON observations(project_id, created_at DESC);
|
|
89
|
-
|
|
90
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
91
|
-
id TEXT PRIMARY KEY,
|
|
92
|
-
project_id TEXT NOT NULL,
|
|
93
|
-
tier TEXT NOT NULL DEFAULT 'semantic',
|
|
94
|
-
category TEXT NOT NULL DEFAULT 'insight',
|
|
95
|
-
trust TEXT NOT NULL DEFAULT 'observed',
|
|
96
|
-
content TEXT NOT NULL,
|
|
97
|
-
context TEXT,
|
|
98
|
-
source TEXT NOT NULL DEFAULT 'manual',
|
|
99
|
-
embedding BLOB,
|
|
100
|
-
weight REAL NOT NULL DEFAULT 1.0,
|
|
101
|
-
used_count INTEGER NOT NULL DEFAULT 0,
|
|
102
|
-
last_used INTEGER,
|
|
103
|
-
created_at INTEGER NOT NULL,
|
|
104
|
-
session_id TEXT REFERENCES sessions(id),
|
|
105
|
-
flagged INTEGER NOT NULL DEFAULT 0,
|
|
106
|
-
flag_reason TEXT
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
CREATE INDEX IF NOT EXISTS idx_mem_project ON memories(project_id);
|
|
110
|
-
CREATE INDEX IF NOT EXISTS idx_mem_weight ON memories(project_id, weight DESC);
|
|
111
|
-
CREATE INDEX IF NOT EXISTS idx_mem_tier ON memories(project_id, tier);
|
|
112
|
-
CREATE INDEX IF NOT EXISTS idx_mem_trust ON memories(project_id, trust);
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_mem_flagged ON memories(project_id, flagged)
|
|
114
|
-
WHERE flagged = 1;
|
|
115
|
-
|
|
116
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
117
|
-
content, context,
|
|
118
|
-
content='memories',
|
|
119
|
-
content_rowid='rowid',
|
|
120
|
-
tokenize='porter ascii'
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
124
|
-
INSERT INTO memories_fts(rowid, content, context)
|
|
125
|
-
VALUES (new.rowid, new.content, COALESCE(new.context, ''));
|
|
126
|
-
END;
|
|
127
|
-
|
|
128
|
-
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
129
|
-
INSERT INTO memories_fts(memories_fts, rowid, content, context)
|
|
130
|
-
VALUES ('delete', old.rowid, old.content, COALESCE(old.context, ''));
|
|
131
|
-
INSERT INTO memories_fts(rowid, content, context)
|
|
132
|
-
VALUES (new.rowid, new.content, COALESCE(new.context, ''));
|
|
133
|
-
END;
|
|
134
|
-
|
|
135
|
-
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
136
|
-
INSERT INTO memories_fts(memories_fts, rowid, content, context)
|
|
137
|
-
VALUES ('delete', old.rowid, old.content, COALESCE(old.context, ''));
|
|
138
|
-
END;
|
|
139
|
-
`);
|
|
140
|
-
return _db;
|
|
141
|
-
}
|
|
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
2
|
|
|
316
3
|
// src/cli.ts
|
|
317
4
|
import {
|
|
318
|
-
existsSync as
|
|
319
|
-
mkdirSync as
|
|
5
|
+
existsSync as existsSync2,
|
|
6
|
+
mkdirSync as mkdirSync2,
|
|
320
7
|
readFileSync as readFileSync2,
|
|
321
8
|
writeFileSync as writeFileSync2
|
|
322
9
|
} from "fs";
|
|
323
10
|
import { join as join2, resolve, dirname as dirname2 } from "path";
|
|
324
11
|
import { homedir as homedir2 } from "os";
|
|
325
|
-
import { fileURLToPath } from "url";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
326
13
|
|
|
327
14
|
// src/registry.ts
|
|
328
|
-
|
|
329
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
330
16
|
import { dirname } from "path";
|
|
17
|
+
|
|
18
|
+
// src/db.ts
|
|
19
|
+
import Database from "better-sqlite3";
|
|
20
|
+
import { homedir } from "os";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
var CORTEX_DIR = join(homedir(), ".apsolut");
|
|
23
|
+
var DB_PATH = join(CORTEX_DIR, "memory.db");
|
|
24
|
+
var REGISTRY_PATH = join(CORTEX_DIR, "registry.json");
|
|
25
|
+
var MODELS_DIR = join(CORTEX_DIR, "models");
|
|
26
|
+
|
|
27
|
+
// src/registry.ts
|
|
331
28
|
function readRegistry() {
|
|
332
|
-
if (!
|
|
29
|
+
if (!existsSync(REGISTRY_PATH))
|
|
333
30
|
return { projects: {} };
|
|
334
31
|
try {
|
|
335
32
|
return JSON.parse(readFileSync(REGISTRY_PATH, "utf-8"));
|
|
@@ -339,8 +36,8 @@ function readRegistry() {
|
|
|
339
36
|
}
|
|
340
37
|
function writeRegistry(reg) {
|
|
341
38
|
const dir = dirname(REGISTRY_PATH);
|
|
342
|
-
if (!
|
|
343
|
-
|
|
39
|
+
if (!existsSync(dir))
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
344
41
|
writeFileSync(REGISTRY_PATH, JSON.stringify(reg, null, 2));
|
|
345
42
|
}
|
|
346
43
|
function registerProject(id, name, path) {
|
|
@@ -354,6 +51,7 @@ var __filename2 = fileURLToPath(import.meta.url);
|
|
|
354
51
|
var __dirname2 = dirname2(__filename2);
|
|
355
52
|
var PACKAGE_ROOT = resolve(__dirname2, "..");
|
|
356
53
|
var IS_DIST = __dirname2.endsWith("dist") || __dirname2.includes(`${process.sep}dist${process.sep}`);
|
|
54
|
+
var PKG_VERSION = JSON.parse(readFileSync2(join2(PACKAGE_ROOT, "package.json"), "utf-8")).version;
|
|
357
55
|
var PROJECT_ROOT = process.cwd();
|
|
358
56
|
var CLAUDE_SETTINGS = join2(homedir2(), ".claude", "settings.json");
|
|
359
57
|
var MCP_JSON = join2(PROJECT_ROOT, ".mcp.json");
|
|
@@ -384,7 +82,7 @@ switch (cmd) {
|
|
|
384
82
|
break;
|
|
385
83
|
default:
|
|
386
84
|
console.log(`
|
|
387
|
-
apsolut-cortex
|
|
85
|
+
apsolut-cortex v${PKG_VERSION}
|
|
388
86
|
|
|
389
87
|
Commands:
|
|
390
88
|
init Set up memory for this Claude Code project
|
|
@@ -397,23 +95,23 @@ Models: ~/.apsolut/models/
|
|
|
397
95
|
}
|
|
398
96
|
async function runHook(name) {
|
|
399
97
|
const hookPath = IS_DIST ? join2(__dirname2, "hooks", `${name}.js`) : join2(__dirname2, "hooks", `${name}.ts`);
|
|
400
|
-
if (!
|
|
98
|
+
if (!existsSync2(hookPath)) {
|
|
401
99
|
process.stderr.write(`[apsolut-cortex] hook not found: ${hookPath}
|
|
402
100
|
`);
|
|
403
101
|
process.exit(0);
|
|
404
102
|
}
|
|
405
|
-
await import(hookPath);
|
|
103
|
+
await import(pathToFileURL(hookPath).href);
|
|
406
104
|
}
|
|
407
105
|
async function init() {
|
|
408
106
|
console.log(`
|
|
409
107
|
apsolut-cortex init
|
|
410
108
|
`);
|
|
411
|
-
if (!
|
|
412
|
-
|
|
109
|
+
if (!existsSync2(PROJECT_APSOLUT)) {
|
|
110
|
+
mkdirSync2(PROJECT_APSOLUT, { recursive: true });
|
|
413
111
|
}
|
|
414
112
|
let projectId;
|
|
415
113
|
let projectName;
|
|
416
|
-
if (
|
|
114
|
+
if (existsSync2(PROJECT_CONFIG)) {
|
|
417
115
|
const existing = JSON.parse(readFileSync2(PROJECT_CONFIG, "utf-8"));
|
|
418
116
|
projectId = existing.id;
|
|
419
117
|
projectName = existing.name;
|
|
@@ -436,7 +134,7 @@ apsolut-cortex init
|
|
|
436
134
|
const mcpCommand = IS_DIST ? "node" : "bun";
|
|
437
135
|
const mcpArgs = [mcpServerPath];
|
|
438
136
|
let mcp = {};
|
|
439
|
-
if (
|
|
137
|
+
if (existsSync2(MCP_JSON)) {
|
|
440
138
|
try {
|
|
441
139
|
mcp = JSON.parse(readFileSync2(MCP_JSON, "utf-8"));
|
|
442
140
|
} catch {}
|
|
@@ -452,16 +150,16 @@ apsolut-cortex init
|
|
|
452
150
|
console.log(`✓ Written .mcp.json`);
|
|
453
151
|
const hookCmd = IS_DIST ? "apsolut-cortex" : `bun run ${join2(__dirname2, "cli.ts")}`;
|
|
454
152
|
const hookEntries = {
|
|
455
|
-
SessionStart: [{ command: `${hookCmd} hook:session-start` }],
|
|
456
|
-
PostToolUse: [{ command: `${hookCmd} hook:post-tool-use` }],
|
|
457
|
-
Stop: [{ command: `${hookCmd} hook:stop` }],
|
|
458
|
-
SessionEnd: [{ command: `${hookCmd} hook:session-end` }]
|
|
153
|
+
SessionStart: [{ matcher: "", hooks: [{ type: "command", command: `${hookCmd} hook:session-start` }] }],
|
|
154
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: `${hookCmd} hook:post-tool-use` }] }],
|
|
155
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: `${hookCmd} hook:stop` }] }],
|
|
156
|
+
SessionEnd: [{ matcher: "", hooks: [{ type: "command", command: `${hookCmd} hook:session-end` }] }]
|
|
459
157
|
};
|
|
460
158
|
let settings = {};
|
|
461
159
|
const settingsDir = dirname2(CLAUDE_SETTINGS);
|
|
462
|
-
if (!
|
|
463
|
-
|
|
464
|
-
if (
|
|
160
|
+
if (!existsSync2(settingsDir))
|
|
161
|
+
mkdirSync2(settingsDir, { recursive: true });
|
|
162
|
+
if (existsSync2(CLAUDE_SETTINGS)) {
|
|
465
163
|
try {
|
|
466
164
|
settings = JSON.parse(readFileSync2(CLAUDE_SETTINGS, "utf-8"));
|
|
467
165
|
} catch {}
|
|
@@ -480,7 +178,7 @@ apsolut-cortex init
|
|
|
480
178
|
writeFileSync2(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
481
179
|
console.log(added > 0 ? `✓ Registered ${added} hooks in ~/.claude/settings.json` : `✓ Hooks already registered`);
|
|
482
180
|
const gitignore = join2(PROJECT_ROOT, ".gitignore");
|
|
483
|
-
if (
|
|
181
|
+
if (existsSync2(gitignore)) {
|
|
484
182
|
const content = readFileSync2(gitignore, "utf-8");
|
|
485
183
|
if (!content.includes(".apsolut/")) {
|
|
486
184
|
writeFileSync2(gitignore, content + `
|
|
@@ -500,7 +198,7 @@ apsolut-cortex init
|
|
|
500
198
|
│ ██║ ██║██║ ███████║╚██████╔╝███████╗╚██████╔╝ ██║ │
|
|
501
199
|
│ ╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ │
|
|
502
200
|
│ │
|
|
503
|
-
│ c o r t e x · v
|
|
201
|
+
│ c o r t e x · v ${PKG_VERSION} │
|
|
504
202
|
│ │
|
|
505
203
|
└─────────────────────────────────────────────────────────┘
|
|
506
204
|
|
|
@@ -518,13 +216,13 @@ apsolut-cortex init
|
|
|
518
216
|
console.log(BANNER);
|
|
519
217
|
}
|
|
520
218
|
async function status() {
|
|
521
|
-
const { getDb
|
|
522
|
-
if (!
|
|
219
|
+
const { getDb } = await import(pathToFileURL(join2(__dirname2, "db.js")).href);
|
|
220
|
+
if (!existsSync2(PROJECT_CONFIG)) {
|
|
523
221
|
console.log("No project found. Run: apsolut-cortex init");
|
|
524
222
|
process.exit(1);
|
|
525
223
|
}
|
|
526
224
|
const project = JSON.parse(readFileSync2(PROJECT_CONFIG, "utf-8"));
|
|
527
|
-
const db =
|
|
225
|
+
const db = getDb();
|
|
528
226
|
const total = db.prepare("SELECT COUNT(*) as n FROM memories WHERE project_id = ?").get(project.id).n;
|
|
529
227
|
const sessions = db.prepare("SELECT COUNT(*) as n FROM sessions WHERE project_id = ?").get(project.id).n;
|
|
530
228
|
const byTrust = db.prepare(`
|
|
@@ -554,7 +252,7 @@ DB: ~/.apsolut/memory.db
|
|
|
554
252
|
`);
|
|
555
253
|
}
|
|
556
254
|
function uninstall() {
|
|
557
|
-
if (
|
|
255
|
+
if (existsSync2(MCP_JSON)) {
|
|
558
256
|
try {
|
|
559
257
|
const mcp = JSON.parse(readFileSync2(MCP_JSON, "utf-8"));
|
|
560
258
|
if (mcp.mcpServers?.["apsolut-cortex"]) {
|
|
@@ -564,14 +262,21 @@ function uninstall() {
|
|
|
564
262
|
}
|
|
565
263
|
} catch {}
|
|
566
264
|
}
|
|
567
|
-
if (
|
|
265
|
+
if (existsSync2(CLAUDE_SETTINGS)) {
|
|
568
266
|
try {
|
|
569
267
|
const settings = JSON.parse(readFileSync2(CLAUDE_SETTINGS, "utf-8"));
|
|
570
268
|
const hooks = settings.hooks;
|
|
571
269
|
if (hooks) {
|
|
572
270
|
for (const event of ["SessionStart", "PostToolUse", "Stop", "SessionEnd"]) {
|
|
573
271
|
if (hooks[event]) {
|
|
574
|
-
hooks[event] = hooks[event].filter((e) =>
|
|
272
|
+
hooks[event] = hooks[event].filter((e) => {
|
|
273
|
+
if (typeof e !== "object")
|
|
274
|
+
return true;
|
|
275
|
+
if (Array.isArray(e.hooks)) {
|
|
276
|
+
return !e.hooks.some((h) => h.command?.includes("apsolut-cortex"));
|
|
277
|
+
}
|
|
278
|
+
return !e.command?.includes("apsolut-cortex");
|
|
279
|
+
});
|
|
575
280
|
}
|
|
576
281
|
}
|
|
577
282
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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}
|
|
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
|
}
|
package/dist/hooks/stop.js
CHANGED
|
@@ -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}[
|
|
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
|
|
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);
|
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
|
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");
|