@alaarab/cortex 1.13.6 → 1.14.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.
@@ -4,7 +4,7 @@ import * as fs from "fs";
4
4
  import { isValidProjectName, buildRobustFtsQuery } from "./utils.js";
5
5
  import { keywordFallbackSearch } from "./core-search.js";
6
6
  import { readFindings } from "./data-access.js";
7
- import { debugLog, runtimeFile, } from "./shared.js";
7
+ import { debugLog, runtimeFile, DOC_TYPES, FINDING_TAGS, } from "./shared.js";
8
8
  import { queryRows, cosineFallback, queryEntityLinks, extractSnippet, queryDocBySourceKey, } from "./shared-index.js";
9
9
  import { runCustomHooks } from "./hooks.js";
10
10
  import { entryScoreKey, getQualityMultiplier } from "./shared-governance.js";
@@ -72,10 +72,10 @@ export function register(server, ctx) {
72
72
  query: z.string().describe("Search query (supports FTS5 syntax: AND, OR, NOT, phrase matching with quotes)"),
73
73
  limit: z.number().min(1).max(20).optional().describe("Max results to return (1-20, default 5)"),
74
74
  project: z.string().optional().describe("Filter by project name."),
75
- type: z.enum(["claude", "findings", "reference", "skills", "summary", "backlog", "changelog", "canonical", "memory-queue", "skill", "other"])
75
+ type: z.enum(DOC_TYPES)
76
76
  .optional()
77
77
  .describe("Filter by document type: claude, findings, reference, summary, backlog, skill"),
78
- tag: z.enum(["decision", "pitfall", "pattern", "tradeoff", "architecture", "bug"])
78
+ tag: z.enum(FINDING_TAGS)
79
79
  .optional()
80
80
  .describe("Filter findings by type tag (decision, pitfall, pattern are canonical; tradeoff, architecture, bug are legacy aliases)."),
81
81
  since: z.string().optional().describe('Filter findings by creation date. Formats: "7d" (last 7 days), "30d" (last 30 days), "YYYY-MM" (since start of month), "YYYY-MM-DD" (since date).'),
@@ -277,8 +277,8 @@ export function register(server, ctx) {
277
277
  boost = 1.2;
278
278
  }
279
279
  catch { /* file may not exist on disk */ }
280
+ const scoreKey = entryScoreKey(rowProject, filename, content);
280
281
  const snippet = extractSnippet(content, query);
281
- const scoreKey = entryScoreKey(rowProject, filename, snippet);
282
282
  boost *= getQualityMultiplier(cortexPath, scoreKey);
283
283
  return { row, rank: (rows.length - idx) * boost };
284
284
  });
@@ -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 files = [];
114
- for (const dir of projectDirs) {
115
- try {
116
- const matched = new Set();
117
- for (const pattern of policy.includeGlobs) {
118
- const dot = policy.includeHidden || pattern.startsWith(".") || pattern.includes("/.");
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
- for (const f of matched)
124
- files.push(path.join(dir, f));
119
+ catch { /* skip */ }
125
120
  }
126
- catch { /* skip unreadable dirs */ }
127
121
  }
128
- files.sort();
129
- const hash = crypto.createHash("sha1");
130
- for (const f of files) {
131
- try {
132
- const stat = fs.statSync(f);
133
- hash.update(`${f}:${stat.mtimeMs}:${stat.size}`);
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 collectAllFiles(cortexPath, profile) {
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
- return entries;
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 hash = computeCortexHash(cortexPath, profile);
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 = collectAllFiles(cortexPath, profile);
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 = collectAllFiles(cortexPath, profile);
509
+ const allFiles = globResult.entries;
491
510
  const newHashes = {};
492
511
  let fileCount = 0;
493
512
  // Try loading cached entity graph
@@ -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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alaarab/cortex",
3
- "version": "1.13.6",
4
- "description": "Long-term memory for AI agents stored as markdown in a git repo you own.",
3
+ "version": "1.14.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"
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) 1.11.1, which gives Claude Code persistent context across sessions and machines.
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:sync` to pull everything in.
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