@alaarab/cortex 1.13.6 → 1.15.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/README.md +16 -15
- package/mcp/dist/cli-config.js +20 -16
- package/mcp/dist/cli-extract.js +15 -10
- package/mcp/dist/cli-govern.js +37 -32
- package/mcp/dist/cli-hooks-citations.js +1 -1
- package/mcp/dist/cli-hooks-retrieval.js +42 -9
- package/mcp/dist/cli-hooks-session.js +46 -41
- package/mcp/dist/cli-hooks.js +26 -21
- package/mcp/dist/cli.js +172 -24
- package/mcp/dist/data-access.js +7 -43
- package/mcp/dist/index.js +23 -15
- package/mcp/dist/link-skills.js +0 -6
- package/mcp/dist/link.js +14 -1
- package/mcp/dist/mcp-finding.js +2 -2
- package/mcp/dist/mcp-search.js +4 -4
- package/mcp/dist/shared-index.js +45 -26
- package/mcp/dist/shared.js +6 -0
- package/mcp/dist/utils.js +49 -5
- package/package.json +11 -11
- package/starter/README.md +2 -2
package/mcp/dist/shared-index.js
CHANGED
|
@@ -107,32 +107,43 @@ function touchSentinel(cortexPath) {
|
|
|
107
107
|
}
|
|
108
108
|
catch { /* best-effort */ }
|
|
109
109
|
}
|
|
110
|
-
function computeCortexHash(cortexPath, profile) {
|
|
111
|
-
const projectDirs = getProjectDirs(cortexPath, profile);
|
|
110
|
+
function computeCortexHash(cortexPath, profile, preGlobbed) {
|
|
112
111
|
const policy = getIndexPolicy(cortexPath);
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const mdFiles = globSync(pattern, { cwd: dir, nodir: true, dot, ignore: policy.excludeGlobs });
|
|
120
|
-
for (const f of mdFiles)
|
|
121
|
-
matched.add(f);
|
|
112
|
+
const hash = crypto.createHash("sha1");
|
|
113
|
+
if (preGlobbed) {
|
|
114
|
+
for (const f of preGlobbed) {
|
|
115
|
+
try {
|
|
116
|
+
const stat = fs.statSync(f);
|
|
117
|
+
hash.update(`${f}:${stat.mtimeMs}:${stat.size}`);
|
|
122
118
|
}
|
|
123
|
-
|
|
124
|
-
files.push(path.join(dir, f));
|
|
119
|
+
catch { /* skip */ }
|
|
125
120
|
}
|
|
126
|
-
catch { /* skip unreadable dirs */ }
|
|
127
121
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
122
|
+
else {
|
|
123
|
+
const projectDirs = getProjectDirs(cortexPath, profile);
|
|
124
|
+
const files = [];
|
|
125
|
+
for (const dir of projectDirs) {
|
|
126
|
+
try {
|
|
127
|
+
const matched = new Set();
|
|
128
|
+
for (const pattern of policy.includeGlobs) {
|
|
129
|
+
const dot = policy.includeHidden || pattern.startsWith(".") || pattern.includes("/.");
|
|
130
|
+
const mdFiles = globSync(pattern, { cwd: dir, nodir: true, dot, ignore: policy.excludeGlobs });
|
|
131
|
+
for (const f of mdFiles)
|
|
132
|
+
matched.add(f);
|
|
133
|
+
}
|
|
134
|
+
for (const f of matched)
|
|
135
|
+
files.push(path.join(dir, f));
|
|
136
|
+
}
|
|
137
|
+
catch { /* skip unreadable dirs */ }
|
|
138
|
+
}
|
|
139
|
+
files.sort();
|
|
140
|
+
for (const f of files) {
|
|
141
|
+
try {
|
|
142
|
+
const stat = fs.statSync(f);
|
|
143
|
+
hash.update(`${f}:${stat.mtimeMs}:${stat.size}`);
|
|
144
|
+
}
|
|
145
|
+
catch { /* skip */ }
|
|
134
146
|
}
|
|
135
|
-
catch { /* skip */ }
|
|
136
147
|
}
|
|
137
148
|
for (const mem of collectNativeMemoryFiles()) {
|
|
138
149
|
try {
|
|
@@ -227,10 +238,11 @@ function getEntrySourceDocKey(entry, cortexPath) {
|
|
|
227
238
|
}
|
|
228
239
|
return buildSourceDocKey(entry.project, entry.fullPath, cortexPath, entry.filename);
|
|
229
240
|
}
|
|
230
|
-
function
|
|
241
|
+
function globAllFiles(cortexPath, profile) {
|
|
231
242
|
const projectDirs = getProjectDirs(cortexPath, profile);
|
|
232
243
|
const indexPolicy = getIndexPolicy(cortexPath);
|
|
233
244
|
const entries = [];
|
|
245
|
+
const allAbsolutePaths = [];
|
|
234
246
|
for (const dir of projectDirs) {
|
|
235
247
|
const projectName = path.basename(dir);
|
|
236
248
|
const mdFilesSet = new Set();
|
|
@@ -255,12 +267,18 @@ function collectAllFiles(cortexPath, profile) {
|
|
|
255
267
|
const fullPath = path.join(dir, relFile);
|
|
256
268
|
const type = classifyFile(filename, relFile);
|
|
257
269
|
entries.push({ fullPath, project: projectName, filename, type, relFile });
|
|
270
|
+
allAbsolutePaths.push(fullPath);
|
|
258
271
|
}
|
|
259
272
|
}
|
|
260
273
|
for (const mem of collectNativeMemoryFiles()) {
|
|
261
274
|
entries.push({ fullPath: mem.fullPath, project: mem.project, filename: mem.file, type: "findings" });
|
|
275
|
+
allAbsolutePaths.push(mem.fullPath);
|
|
262
276
|
}
|
|
263
|
-
|
|
277
|
+
allAbsolutePaths.sort();
|
|
278
|
+
return { filePaths: allAbsolutePaths, entries };
|
|
279
|
+
}
|
|
280
|
+
function collectAllFiles(cortexPath, profile) {
|
|
281
|
+
return globAllFiles(cortexPath, profile).entries;
|
|
264
282
|
}
|
|
265
283
|
function insertFileIntoIndex(db, entry, cortexPath) {
|
|
266
284
|
try {
|
|
@@ -341,7 +359,8 @@ async function buildIndexImpl(cortexPath, profile) {
|
|
|
341
359
|
userSuffix = crypto.createHash("sha1").update(os.homedir()).digest("hex").slice(0, 12);
|
|
342
360
|
}
|
|
343
361
|
const cacheDir = path.join(os.tmpdir(), `cortex-fts-${userSuffix}`);
|
|
344
|
-
const
|
|
362
|
+
const globResult = globAllFiles(cortexPath, profile);
|
|
363
|
+
const hash = computeCortexHash(cortexPath, profile, globResult.filePaths);
|
|
345
364
|
const cacheFile = path.join(cacheDir, `${hash}.db`);
|
|
346
365
|
const wasmBinary = findWasmBinary();
|
|
347
366
|
const SQL = await initSqlJs(wasmBinary ? { wasmBinary } : {});
|
|
@@ -358,7 +377,7 @@ async function buildIndexImpl(cortexPath, profile) {
|
|
|
358
377
|
try {
|
|
359
378
|
db = new SQL.Database(cached);
|
|
360
379
|
// Compute current file hashes and determine what changed
|
|
361
|
-
const allFiles =
|
|
380
|
+
const allFiles = globResult.entries;
|
|
362
381
|
const currentHashes = {};
|
|
363
382
|
const changedFiles = [];
|
|
364
383
|
const newFiles = [];
|
|
@@ -487,7 +506,7 @@ async function buildIndexImpl(cortexPath, profile) {
|
|
|
487
506
|
db.run(`CREATE TABLE IF NOT EXISTS entity_links (source_id INTEGER REFERENCES entities(id), target_id INTEGER REFERENCES entities(id), rel_type TEXT NOT NULL, source_doc TEXT, PRIMARY KEY (source_id, target_id, rel_type))`);
|
|
488
507
|
// Q20: Cross-project entity index
|
|
489
508
|
ensureGlobalEntitiesTable(db);
|
|
490
|
-
const allFiles =
|
|
509
|
+
const allFiles = globResult.entries;
|
|
491
510
|
const newHashes = {};
|
|
492
511
|
let fileCount = 0;
|
|
493
512
|
// Try loading cached entity graph
|
package/mcp/dist/shared.js
CHANGED
|
@@ -209,6 +209,12 @@ export function collectNativeMemoryFiles() {
|
|
|
209
209
|
}
|
|
210
210
|
return results;
|
|
211
211
|
}
|
|
212
|
+
/** Canonical finding type tags */
|
|
213
|
+
export const FINDING_TYPES = ["decision", "pitfall", "pattern"];
|
|
214
|
+
/** All searchable finding tags (canonical + legacy aliases) */
|
|
215
|
+
export const FINDING_TAGS = ["decision", "pitfall", "pattern", "tradeoff", "architecture", "bug"];
|
|
216
|
+
/** Document types in the FTS index */
|
|
217
|
+
export const DOC_TYPES = ["claude", "findings", "reference", "skills", "summary", "backlog", "changelog", "canonical", "memory-queue", "skill", "other"];
|
|
212
218
|
export function appendAuditLog(cortexPath, event, details) {
|
|
213
219
|
// Migrate: check old location, use new .runtime/ path
|
|
214
220
|
const legacyPath = path.join(cortexPath, ".cortex-audit.log");
|
package/mcp/dist/utils.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
1
2
|
import * as path from "path";
|
|
2
3
|
import { execFileSync } from "child_process";
|
|
4
|
+
import * as yaml from "js-yaml";
|
|
5
|
+
import { findCortexPath } from "./shared.js";
|
|
3
6
|
// ── Shared Git helper ────────────────────────────────────────────────────────
|
|
4
7
|
export function runGit(cwd, args, timeoutMs, debugLogFn) {
|
|
5
8
|
try {
|
|
@@ -266,17 +269,59 @@ export function sanitizeFts5Query(raw) {
|
|
|
266
269
|
q = q.replace(/\s+/g, " ");
|
|
267
270
|
return q.trim();
|
|
268
271
|
}
|
|
272
|
+
function parseSynonymsYaml(filePath) {
|
|
273
|
+
if (!fs.existsSync(filePath))
|
|
274
|
+
return {};
|
|
275
|
+
try {
|
|
276
|
+
const parsed = yaml.load(fs.readFileSync(filePath, "utf8"), { schema: yaml.CORE_SCHEMA });
|
|
277
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
278
|
+
return {};
|
|
279
|
+
const loaded = {};
|
|
280
|
+
for (const [rawKey, value] of Object.entries(parsed)) {
|
|
281
|
+
const key = String(rawKey).trim().toLowerCase();
|
|
282
|
+
if (!key || !Array.isArray(value))
|
|
283
|
+
continue;
|
|
284
|
+
const synonyms = value
|
|
285
|
+
.filter((item) => typeof item === "string")
|
|
286
|
+
.map((item) => item.replace(/"/g, "").trim())
|
|
287
|
+
.filter((item) => item.length > 1);
|
|
288
|
+
if (synonyms.length > 0)
|
|
289
|
+
loaded[key] = synonyms;
|
|
290
|
+
}
|
|
291
|
+
return loaded;
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return {};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function loadUserSynonyms(project) {
|
|
298
|
+
const cortexPath = findCortexPath();
|
|
299
|
+
if (!cortexPath)
|
|
300
|
+
return {};
|
|
301
|
+
const globalSynonyms = parseSynonymsYaml(path.join(cortexPath, "global", "synonyms.yaml"));
|
|
302
|
+
if (!project || !isValidProjectName(project))
|
|
303
|
+
return globalSynonyms;
|
|
304
|
+
const projectSynonyms = parseSynonymsYaml(path.join(cortexPath, project, "synonyms.yaml"));
|
|
305
|
+
return {
|
|
306
|
+
...globalSynonyms,
|
|
307
|
+
...projectSynonyms,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
269
310
|
// Build a defensive FTS5 MATCH query:
|
|
270
311
|
// - sanitizes user input
|
|
271
312
|
// - extracts bigrams and treats them as quoted phrases
|
|
272
313
|
// - expands known synonyms (capped at 10 total terms)
|
|
273
314
|
// - applies AND between core terms, with synonyms as OR alternatives
|
|
274
|
-
export function buildRobustFtsQuery(raw) {
|
|
315
|
+
export function buildRobustFtsQuery(raw, project) {
|
|
275
316
|
const MAX_TOTAL_TERMS = 10;
|
|
276
317
|
const MAX_SYNONYM_GROUPS = 3;
|
|
277
318
|
const safe = sanitizeFts5Query(raw);
|
|
278
319
|
if (!safe)
|
|
279
320
|
return "";
|
|
321
|
+
const synonymsMap = {
|
|
322
|
+
...SYNONYMS,
|
|
323
|
+
...loadUserSynonyms(project),
|
|
324
|
+
};
|
|
280
325
|
const baseWords = safe.split(/\s+/).filter((t) => t.length > 1);
|
|
281
326
|
if (baseWords.length === 0)
|
|
282
327
|
return "";
|
|
@@ -286,12 +331,11 @@ export function buildRobustFtsQuery(raw) {
|
|
|
286
331
|
bigrams.push(`${baseWords[i]} ${baseWords[i + 1]}`);
|
|
287
332
|
}
|
|
288
333
|
// Determine which words are consumed by bigrams that match synonym keys
|
|
289
|
-
const lowered = safe.toLowerCase();
|
|
290
334
|
const consumedIndices = new Set();
|
|
291
335
|
const matchedBigrams = [];
|
|
292
336
|
for (let i = 0; i < bigrams.length; i++) {
|
|
293
337
|
const bg = bigrams[i].toLowerCase();
|
|
294
|
-
if (
|
|
338
|
+
if (synonymsMap[bg]) {
|
|
295
339
|
consumedIndices.add(i);
|
|
296
340
|
consumedIndices.add(i + 1);
|
|
297
341
|
matchedBigrams.push(bigrams[i]);
|
|
@@ -326,8 +370,8 @@ export function buildRobustFtsQuery(raw) {
|
|
|
326
370
|
for (const coreTerm of coreTerms) {
|
|
327
371
|
const termText = coreTerm.slice(1, -1).toLowerCase(); // strip quotes
|
|
328
372
|
const synonyms = [];
|
|
329
|
-
if (groupsExpanded < MAX_SYNONYM_GROUPS &&
|
|
330
|
-
for (const syn of
|
|
373
|
+
if (groupsExpanded < MAX_SYNONYM_GROUPS && synonymsMap[termText]) {
|
|
374
|
+
for (const syn of synonymsMap[termText]) {
|
|
331
375
|
if (totalTermCount >= MAX_TOTAL_TERMS)
|
|
332
376
|
break;
|
|
333
377
|
const cleanSyn = syn.replace(/"/g, "").trim();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/cortex",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Long-term memory for AI agents
|
|
3
|
+
"version": "1.15.0",
|
|
4
|
+
"description": "Long-term memory for AI agents. Stored as markdown in a git repo you own.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cortex": "mcp/dist/index.js"
|
|
@@ -12,21 +12,21 @@
|
|
|
12
12
|
"skills"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
16
16
|
"@xenova/transformers": "^2.17.2",
|
|
17
|
-
"glob": "^
|
|
18
|
-
"js-yaml": "^4.1.
|
|
17
|
+
"glob": "^13.0.6",
|
|
18
|
+
"js-yaml": "^4.1.1",
|
|
19
19
|
"sql.js-fts5": "^1.4.0",
|
|
20
|
-
"zod": "^4.
|
|
20
|
+
"zod": "^4.3.6"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@types/js-yaml": "^4.0.
|
|
24
|
-
"@types/node": "^
|
|
23
|
+
"@types/js-yaml": "^4.0.9",
|
|
24
|
+
"@types/node": "^25.3.5",
|
|
25
25
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
26
26
|
"@typescript-eslint/parser": "^8.56.1",
|
|
27
|
-
"eslint": "^10.0.
|
|
28
|
-
"tsx": "^4.
|
|
29
|
-
"typescript": "^5.
|
|
27
|
+
"eslint": "^10.0.3",
|
|
28
|
+
"tsx": "^4.21.0",
|
|
29
|
+
"typescript": "^5.9.3",
|
|
30
30
|
"vitest": "^4.0.18"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
package/starter/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# My Cortex
|
|
2
2
|
|
|
3
|
-
Your personal project store for [cortex](https://github.com/alaarab/cortex)
|
|
3
|
+
Your personal project store for [cortex](https://github.com/alaarab/cortex), which gives Claude Code persistent context across sessions and machines.
|
|
4
4
|
|
|
5
5
|
## Structure
|
|
6
6
|
|
|
@@ -67,7 +67,7 @@ work-desktop: work
|
|
|
67
67
|
home-laptop: personal
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Each profile in `profiles/` lists which projects that machine should see. After cloning on a new machine, run `/cortex
|
|
70
|
+
Each profile in `profiles/` lists which projects that machine should see. After cloning on a new machine, run `/cortex-sync` to pull everything in.
|
|
71
71
|
|
|
72
72
|
## Troubleshooting
|
|
73
73
|
|