apsolut-cortex 0.1.2 → 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 +46 -342
- 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) {
|
|
@@ -398,23 +95,23 @@ Models: ~/.apsolut/models/
|
|
|
398
95
|
}
|
|
399
96
|
async function runHook(name) {
|
|
400
97
|
const hookPath = IS_DIST ? join2(__dirname2, "hooks", `${name}.js`) : join2(__dirname2, "hooks", `${name}.ts`);
|
|
401
|
-
if (!
|
|
98
|
+
if (!existsSync2(hookPath)) {
|
|
402
99
|
process.stderr.write(`[apsolut-cortex] hook not found: ${hookPath}
|
|
403
100
|
`);
|
|
404
101
|
process.exit(0);
|
|
405
102
|
}
|
|
406
|
-
await import(hookPath);
|
|
103
|
+
await import(pathToFileURL(hookPath).href);
|
|
407
104
|
}
|
|
408
105
|
async function init() {
|
|
409
106
|
console.log(`
|
|
410
107
|
apsolut-cortex init
|
|
411
108
|
`);
|
|
412
|
-
if (!
|
|
413
|
-
|
|
109
|
+
if (!existsSync2(PROJECT_APSOLUT)) {
|
|
110
|
+
mkdirSync2(PROJECT_APSOLUT, { recursive: true });
|
|
414
111
|
}
|
|
415
112
|
let projectId;
|
|
416
113
|
let projectName;
|
|
417
|
-
if (
|
|
114
|
+
if (existsSync2(PROJECT_CONFIG)) {
|
|
418
115
|
const existing = JSON.parse(readFileSync2(PROJECT_CONFIG, "utf-8"));
|
|
419
116
|
projectId = existing.id;
|
|
420
117
|
projectName = existing.name;
|
|
@@ -437,7 +134,7 @@ apsolut-cortex init
|
|
|
437
134
|
const mcpCommand = IS_DIST ? "node" : "bun";
|
|
438
135
|
const mcpArgs = [mcpServerPath];
|
|
439
136
|
let mcp = {};
|
|
440
|
-
if (
|
|
137
|
+
if (existsSync2(MCP_JSON)) {
|
|
441
138
|
try {
|
|
442
139
|
mcp = JSON.parse(readFileSync2(MCP_JSON, "utf-8"));
|
|
443
140
|
} catch {}
|
|
@@ -453,16 +150,16 @@ apsolut-cortex init
|
|
|
453
150
|
console.log(`✓ Written .mcp.json`);
|
|
454
151
|
const hookCmd = IS_DIST ? "apsolut-cortex" : `bun run ${join2(__dirname2, "cli.ts")}`;
|
|
455
152
|
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` }]
|
|
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` }] }]
|
|
460
157
|
};
|
|
461
158
|
let settings = {};
|
|
462
159
|
const settingsDir = dirname2(CLAUDE_SETTINGS);
|
|
463
|
-
if (!
|
|
464
|
-
|
|
465
|
-
if (
|
|
160
|
+
if (!existsSync2(settingsDir))
|
|
161
|
+
mkdirSync2(settingsDir, { recursive: true });
|
|
162
|
+
if (existsSync2(CLAUDE_SETTINGS)) {
|
|
466
163
|
try {
|
|
467
164
|
settings = JSON.parse(readFileSync2(CLAUDE_SETTINGS, "utf-8"));
|
|
468
165
|
} catch {}
|
|
@@ -481,7 +178,7 @@ apsolut-cortex init
|
|
|
481
178
|
writeFileSync2(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
482
179
|
console.log(added > 0 ? `✓ Registered ${added} hooks in ~/.claude/settings.json` : `✓ Hooks already registered`);
|
|
483
180
|
const gitignore = join2(PROJECT_ROOT, ".gitignore");
|
|
484
|
-
if (
|
|
181
|
+
if (existsSync2(gitignore)) {
|
|
485
182
|
const content = readFileSync2(gitignore, "utf-8");
|
|
486
183
|
if (!content.includes(".apsolut/")) {
|
|
487
184
|
writeFileSync2(gitignore, content + `
|
|
@@ -501,7 +198,7 @@ apsolut-cortex init
|
|
|
501
198
|
│ ██║ ██║██║ ███████║╚██████╔╝███████╗╚██████╔╝ ██║ │
|
|
502
199
|
│ ╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ │
|
|
503
200
|
│ │
|
|
504
|
-
│ c o r t e x · v
|
|
201
|
+
│ c o r t e x · v ${PKG_VERSION} │
|
|
505
202
|
│ │
|
|
506
203
|
└─────────────────────────────────────────────────────────┘
|
|
507
204
|
|
|
@@ -519,13 +216,13 @@ apsolut-cortex init
|
|
|
519
216
|
console.log(BANNER);
|
|
520
217
|
}
|
|
521
218
|
async function status() {
|
|
522
|
-
const { getDb
|
|
523
|
-
if (!
|
|
219
|
+
const { getDb } = await import(pathToFileURL(join2(__dirname2, "db.js")).href);
|
|
220
|
+
if (!existsSync2(PROJECT_CONFIG)) {
|
|
524
221
|
console.log("No project found. Run: apsolut-cortex init");
|
|
525
222
|
process.exit(1);
|
|
526
223
|
}
|
|
527
224
|
const project = JSON.parse(readFileSync2(PROJECT_CONFIG, "utf-8"));
|
|
528
|
-
const db =
|
|
225
|
+
const db = getDb();
|
|
529
226
|
const total = db.prepare("SELECT COUNT(*) as n FROM memories WHERE project_id = ?").get(project.id).n;
|
|
530
227
|
const sessions = db.prepare("SELECT COUNT(*) as n FROM sessions WHERE project_id = ?").get(project.id).n;
|
|
531
228
|
const byTrust = db.prepare(`
|
|
@@ -555,7 +252,7 @@ DB: ~/.apsolut/memory.db
|
|
|
555
252
|
`);
|
|
556
253
|
}
|
|
557
254
|
function uninstall() {
|
|
558
|
-
if (
|
|
255
|
+
if (existsSync2(MCP_JSON)) {
|
|
559
256
|
try {
|
|
560
257
|
const mcp = JSON.parse(readFileSync2(MCP_JSON, "utf-8"));
|
|
561
258
|
if (mcp.mcpServers?.["apsolut-cortex"]) {
|
|
@@ -565,14 +262,21 @@ function uninstall() {
|
|
|
565
262
|
}
|
|
566
263
|
} catch {}
|
|
567
264
|
}
|
|
568
|
-
if (
|
|
265
|
+
if (existsSync2(CLAUDE_SETTINGS)) {
|
|
569
266
|
try {
|
|
570
267
|
const settings = JSON.parse(readFileSync2(CLAUDE_SETTINGS, "utf-8"));
|
|
571
268
|
const hooks = settings.hooks;
|
|
572
269
|
if (hooks) {
|
|
573
270
|
for (const event of ["SessionStart", "PostToolUse", "Stop", "SessionEnd"]) {
|
|
574
271
|
if (hooks[event]) {
|
|
575
|
-
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
|
+
});
|
|
576
280
|
}
|
|
577
281
|
}
|
|
578
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");
|