@alaarab/cortex 1.14.0 → 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.
@@ -8,7 +8,6 @@ function getCortexPath() {
8
8
  _cortexPath = ensureCortexPath();
9
9
  return _cortexPath;
10
10
  }
11
- const profile = process.env.CORTEX_PROFILE || "";
12
11
  // ── Config router ────────────────────────────────────────────────────────────
13
12
  export async function handleConfig(args) {
14
13
  const sub = args[0];
@@ -20,7 +20,7 @@ export function clearCitationValidCache() {
20
20
  citationValidCache.clear();
21
21
  }
22
22
  /** @deprecated Legacy citation formats. Use `<!-- cortex:cite {...} -->` instead. */
23
- const CITATION_PATTERN = /<!-- source: ([^:]+):(\d+) -->|\[file:([^:]+):(\d+)\]/g;
23
+ const CITATION_PATTERN = /<!-- source: ((?:[a-zA-Z]:[\\\/])?[^:]+):(\d+) -->|\[file:((?:[a-zA-Z]:[\\\/])?[^:]+):(\d+)\]/g;
24
24
  export function parseCitations(text) {
25
25
  const results = [];
26
26
  let m;
@@ -122,7 +122,7 @@ function semanticFallbackDocs(db, prompt, project) {
122
122
  const queryTokens = tokenizeForOverlap(prompt);
123
123
  if (!queryTokens.length)
124
124
  return [];
125
- const sampleLimit = project ? 180 : 260;
125
+ const sampleLimit = 100;
126
126
  const docs = project
127
127
  ? queryDocRows(db, "SELECT project, filename, type, content, path FROM docs WHERE project = ? LIMIT ?", [project, sampleLimit]) || []
128
128
  : queryDocRows(db, "SELECT project, filename, type, content, path FROM docs LIMIT ?", [sampleLimit]) || [];
@@ -132,7 +132,7 @@ function semanticFallbackDocs(db, prompt, project) {
132
132
  const score = overlapScore(queryTokens, corpus);
133
133
  return { doc, score };
134
134
  })
135
- .filter((x) => x.score >= 0.15)
135
+ .filter((x) => x.score >= 0.25)
136
136
  .sort((a, b) => b.score - a.score)
137
137
  .slice(0, 8)
138
138
  .map((x) => x.doc);
@@ -219,6 +219,22 @@ function mostRecentDate(content) {
219
219
  return "0000-00-00";
220
220
  return matches.map((m) => m.slice(3)).sort().reverse()[0];
221
221
  }
222
+ function crossProjectAgeMultiplier(doc, detectedProject) {
223
+ if (doc.type !== "findings" || !detectedProject || doc.project === detectedProject)
224
+ return 1;
225
+ const decayDaysRaw = Number.parseInt(process.env.CORTEX_CROSS_PROJECT_DECAY_DAYS ?? "30", 10);
226
+ const decayDays = Number.isFinite(decayDaysRaw) && decayDaysRaw > 0 ? decayDaysRaw : 30;
227
+ const latest = mostRecentDate(doc.content);
228
+ const todayUtc = Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate());
229
+ let ageInDays = 90;
230
+ if (/^\d{4}-\d{2}-\d{2}$/.test(latest) && latest !== "0000-00-00") {
231
+ const entryUtc = Date.parse(`${latest}T00:00:00Z`);
232
+ if (!Number.isNaN(entryUtc)) {
233
+ ageInDays = Math.max(0, Math.floor((todayUtc - entryUtc) / 86_400_000));
234
+ }
235
+ }
236
+ return Math.max(0.1, 1 - (ageInDays / decayDays));
237
+ }
222
238
  export function rankResults(rows, intent, gitCtx, detectedProject, cortexPathLocal, db, cwd, query) {
223
239
  let ranked = [...rows];
224
240
  if (detectedProject) {
@@ -260,31 +276,48 @@ export function rankResults(rows, intent, gitCtx, detectedProject, cortexPathLoc
260
276
  if (byDate !== 0)
261
277
  return byDate;
262
278
  }
279
+ const changedFiles = gitCtx?.changedFiles || new Set();
280
+ const globBoostA = getProjectGlobBoost(cortexPathLocal, a.project, cwd, gitCtx?.changedFiles);
281
+ const globBoostB = getProjectGlobBoost(cortexPathLocal, b.project, cwd, gitCtx?.changedFiles);
282
+ const keyA = entryScoreKey(a.project, a.filename, a.content);
283
+ const keyB = entryScoreKey(b.project, b.filename, b.content);
284
+ const entityA = entityBoostPaths.has(a.path) ? 1.3 : 1;
285
+ const entityB = entityBoostPaths.has(b.path) ? 1.3 : 1;
286
+ const scoreA = (intentBoost(intent, a.type) +
287
+ fileRelevanceBoost(a.path, changedFiles) +
288
+ branchMatchBoost(a.content, gitCtx?.branch) +
289
+ globBoostA +
290
+ getQualityMultiplier(cortexPathLocal, keyA) +
291
+ entityA -
292
+ lowValuePenalty(a.content, a.type)) * crossProjectAgeMultiplier(a, detectedProject);
293
+ const scoreB = (intentBoost(intent, b.type) +
294
+ fileRelevanceBoost(b.path, changedFiles) +
295
+ branchMatchBoost(b.content, gitCtx?.branch) +
296
+ globBoostB +
297
+ getQualityMultiplier(cortexPathLocal, keyB) +
298
+ entityB -
299
+ lowValuePenalty(b.content, b.type)) * crossProjectAgeMultiplier(b, detectedProject);
300
+ const scoreDelta = scoreB - scoreA;
301
+ if (Math.abs(scoreDelta) > 0.01)
302
+ return scoreDelta;
263
303
  const intentDelta = intentBoost(intent, b.type) - intentBoost(intent, a.type);
264
304
  if (intentDelta !== 0)
265
305
  return intentDelta;
266
- const changedFiles = gitCtx?.changedFiles || new Set();
267
306
  const fileDelta = fileRelevanceBoost(b.path, changedFiles) - fileRelevanceBoost(a.path, changedFiles);
268
307
  if (fileDelta !== 0)
269
308
  return fileDelta;
270
309
  const branchDelta = branchMatchBoost(b.content, gitCtx?.branch) - branchMatchBoost(a.content, gitCtx?.branch);
271
310
  if (branchDelta !== 0)
272
311
  return branchDelta;
273
- const globBoostA = getProjectGlobBoost(cortexPathLocal, a.project, cwd, gitCtx?.changedFiles);
274
- const globBoostB = getProjectGlobBoost(cortexPathLocal, b.project, cwd, gitCtx?.changedFiles);
275
312
  const globDelta = globBoostB - globBoostA;
276
313
  if (Math.abs(globDelta) > 0.01)
277
314
  return globDelta;
278
- const keyA = entryScoreKey(a.project, a.filename, a.content);
279
- const keyB = entryScoreKey(b.project, b.filename, b.content);
280
315
  const qualityDelta = getQualityMultiplier(cortexPathLocal, keyB) - getQualityMultiplier(cortexPathLocal, keyA);
281
316
  if (qualityDelta !== 0)
282
317
  return qualityDelta;
283
318
  const penaltyDelta = lowValuePenalty(a.content, a.type) - lowValuePenalty(b.content, b.type);
284
319
  if (penaltyDelta !== 0)
285
320
  return penaltyDelta;
286
- const entityA = entityBoostPaths.has(a.path) ? 1.3 : 1;
287
- const entityB = entityBoostPaths.has(b.path) ? 1.3 : 1;
288
321
  if (entityB !== entityA)
289
322
  return entityB - entityA;
290
323
  return 0;
@@ -40,7 +40,7 @@ const profile = process.env.CORTEX_PROFILE || "";
40
40
  async function readStdin() {
41
41
  return new Promise((resolve, reject) => {
42
42
  const chunks = [];
43
- process.stdin.on("data", (chunk) => chunks.push(chunk));
43
+ process.stdin.on("data", (chunk) => chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk));
44
44
  process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
45
45
  process.stdin.on("error", reject);
46
46
  });
@@ -89,7 +89,7 @@ export async function handleHookPrompt() {
89
89
  const detectedProject = cwd ? detectProject(getCortexPath(), cwd, profile) : null;
90
90
  if (detectedProject)
91
91
  debugLog(`Detected project: ${detectedProject}`);
92
- const safeQuery = buildRobustFtsQuery(keywords);
92
+ const safeQuery = buildRobustFtsQuery(keywords, detectedProject);
93
93
  if (!safeQuery)
94
94
  process.exit(0);
95
95
  try {
package/mcp/dist/cli.js CHANGED
@@ -21,6 +21,7 @@ import { handleHookPrompt, handleHookSessionStart, handleHookStop, handleHookCon
21
21
  import { handleExtractMemories } from "./cli-extract.js";
22
22
  import { handleGovernMemories, handlePruneMemories, handleConsolidateMemories, handleMigrateFindings, handleMaintain, handleBackgroundMaintenance, } from "./cli-govern.js";
23
23
  import { handleConfig, handleIndexPolicy, handleRetentionPolicy, handleWorkflowPolicy, handleAccessControl, } from "./cli-config.js";
24
+ import { readInstallPreferences, writeInstallPreferences } from "./init-preferences.js";
24
25
  let _cortexPath;
25
26
  function getCortexPath() {
26
27
  if (!_cortexPath)
@@ -275,6 +276,10 @@ export async function runCliCommand(command, args) {
275
276
  return handleMaintain(args);
276
277
  case "skill-list":
277
278
  return handleSkillList();
279
+ case "skills":
280
+ return handleSkillsNamespace(args);
281
+ case "hooks":
282
+ return handleHooksNamespace(args);
278
283
  case "backlog":
279
284
  return handleBacklogView();
280
285
  case "quickstart":
@@ -305,7 +310,7 @@ async function handleSearch(opts) {
305
310
  const where = [];
306
311
  const params = [];
307
312
  if (opts.query) {
308
- const safeQuery = buildRobustFtsQuery(opts.query);
313
+ const safeQuery = buildRobustFtsQuery(opts.query, opts.project);
309
314
  if (!safeQuery) {
310
315
  console.error("Query empty after sanitization.");
311
316
  process.exit(1);
@@ -487,14 +492,149 @@ async function handleUpdate(args) {
487
492
  const result = await runCortexUpdate();
488
493
  console.log(result);
489
494
  }
495
+ const HOOK_TOOLS = ["claude", "copilot", "cursor", "codex"];
496
+ function printSkillsUsage() {
497
+ console.log("Usage:");
498
+ console.log(" cortex skills list");
499
+ console.log(" cortex skills add <project> <path>");
500
+ console.log(" cortex skills remove <project> <name>");
501
+ }
502
+ function printHooksUsage() {
503
+ console.log("Usage:");
504
+ console.log(" cortex hooks list");
505
+ console.log(" cortex hooks enable <tool>");
506
+ console.log(" cortex hooks disable <tool>");
507
+ console.log(" tools: claude|copilot|cursor|codex");
508
+ }
509
+ function normalizeHookTool(raw) {
510
+ if (!raw)
511
+ return null;
512
+ const tool = raw.toLowerCase();
513
+ return HOOK_TOOLS.includes(tool) ? tool : null;
514
+ }
515
+ function handleSkillsNamespace(args) {
516
+ const subcommand = args[0];
517
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
518
+ printSkillsUsage();
519
+ return;
520
+ }
521
+ if (subcommand === "list") {
522
+ handleSkillList();
523
+ return;
524
+ }
525
+ if (subcommand === "add") {
526
+ const project = args[1];
527
+ const skillPath = args[2];
528
+ if (!project || !skillPath) {
529
+ printSkillsUsage();
530
+ process.exit(1);
531
+ }
532
+ if (!isValidProjectName(project)) {
533
+ console.error(`Invalid project name: "${project}"`);
534
+ process.exit(1);
535
+ }
536
+ const source = path.resolve(skillPath.replace(/^~/, os.homedir()));
537
+ if (!fs.existsSync(source) || !fs.statSync(source).isFile()) {
538
+ console.error(`Skill file not found: ${source}`);
539
+ process.exit(1);
540
+ }
541
+ const baseName = path.basename(source);
542
+ const fileName = baseName.toLowerCase().endsWith(".md") ? baseName : `${baseName}.md`;
543
+ const destDir = path.join(getCortexPath(), project, ".claude", "skills");
544
+ const dest = path.join(destDir, fileName);
545
+ fs.mkdirSync(destDir, { recursive: true });
546
+ if (fs.existsSync(dest)) {
547
+ console.error(`Skill already exists: ${dest}`);
548
+ process.exit(1);
549
+ }
550
+ try {
551
+ fs.symlinkSync(source, dest);
552
+ console.log(`Linked skill ${fileName} into ${project}.`);
553
+ }
554
+ catch {
555
+ fs.copyFileSync(source, dest);
556
+ console.log(`Copied skill ${fileName} into ${project}.`);
557
+ }
558
+ return;
559
+ }
560
+ if (subcommand === "remove") {
561
+ const project = args[1];
562
+ const name = args[2];
563
+ if (!project || !name) {
564
+ printSkillsUsage();
565
+ process.exit(1);
566
+ }
567
+ if (!isValidProjectName(project)) {
568
+ console.error(`Invalid project name: "${project}"`);
569
+ process.exit(1);
570
+ }
571
+ const dest = path.join(getCortexPath(), project, ".claude", "skills", `${name.replace(/\.md$/i, "")}.md`);
572
+ if (!fs.existsSync(dest)) {
573
+ console.error(`Skill not found: ${dest}`);
574
+ process.exit(1);
575
+ }
576
+ fs.unlinkSync(dest);
577
+ console.log(`Removed skill ${name.replace(/\.md$/i, "")}.md from ${project}.`);
578
+ return;
579
+ }
580
+ console.error(`Unknown skills subcommand: ${subcommand}`);
581
+ printSkillsUsage();
582
+ process.exit(1);
583
+ }
584
+ function handleHooksNamespace(args) {
585
+ const subcommand = args[0];
586
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
587
+ printHooksUsage();
588
+ return;
589
+ }
590
+ if (subcommand === "list") {
591
+ const prefs = readInstallPreferences(getCortexPath());
592
+ const hooksEnabled = prefs.hooksEnabled !== false;
593
+ const toolPrefs = prefs.hookTools && typeof prefs.hookTools === "object" ? prefs.hookTools : {};
594
+ const rows = HOOK_TOOLS.map((tool) => ({
595
+ tool,
596
+ hookType: "lifecycle",
597
+ status: hooksEnabled && toolPrefs[tool] !== false ? "enabled" : "disabled",
598
+ }));
599
+ console.log(`Tool Hook Type Status`);
600
+ console.log(`-------- --------- --------`);
601
+ for (const row of rows) {
602
+ console.log(`${row.tool.padEnd(8)} ${row.hookType.padEnd(9)} ${row.status}`);
603
+ }
604
+ return;
605
+ }
606
+ if (subcommand === "enable" || subcommand === "disable") {
607
+ const tool = normalizeHookTool(args[1]);
608
+ if (!tool) {
609
+ printHooksUsage();
610
+ process.exit(1);
611
+ }
612
+ const prefs = readInstallPreferences(getCortexPath());
613
+ writeInstallPreferences(getCortexPath(), {
614
+ hookTools: {
615
+ ...(prefs.hookTools && typeof prefs.hookTools === "object" ? prefs.hookTools : {}),
616
+ [tool]: subcommand === "enable",
617
+ },
618
+ });
619
+ console.log(`${subcommand === "enable" ? "Enabled" : "Disabled"} hooks for ${tool}.`);
620
+ return;
621
+ }
622
+ console.error(`Unknown hooks subcommand: ${subcommand}`);
623
+ printHooksUsage();
624
+ process.exit(1);
625
+ }
490
626
  function handleSkillList() {
491
627
  const sources = [];
628
+ const seenPaths = new Set();
492
629
  function collectSkills(root, sourceLabel) {
493
630
  if (!fs.existsSync(root))
494
631
  return;
495
632
  for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
496
633
  const entryPath = path.join(root, entry.name);
497
634
  if (entry.isFile() && entry.name.endsWith(".md")) {
635
+ if (seenPaths.has(entryPath))
636
+ continue;
637
+ seenPaths.add(entryPath);
498
638
  sources.push({
499
639
  name: entry.name.replace(/\.md$/, ""),
500
640
  source: sourceLabel,
@@ -507,6 +647,9 @@ function handleSkillList() {
507
647
  const skillFile = path.join(entryPath, "SKILL.md");
508
648
  if (!fs.existsSync(skillFile))
509
649
  continue;
650
+ if (seenPaths.has(skillFile))
651
+ continue;
652
+ seenPaths.add(skillFile);
510
653
  sources.push({
511
654
  name: entry.name,
512
655
  source: sourceLabel,
@@ -523,8 +666,8 @@ function handleSkillList() {
523
666
  const projectName = path.basename(dir);
524
667
  if (projectName === "global")
525
668
  continue;
526
- const projectSkillsDir = path.join(dir, "skills");
527
- collectSkills(projectSkillsDir, projectName);
669
+ collectSkills(path.join(dir, "skills"), projectName);
670
+ collectSkills(path.join(dir, ".claude", "skills"), projectName);
528
671
  }
529
672
  if (!sources.length) {
530
673
  console.log("No skills found.");
package/mcp/dist/index.js CHANGED
@@ -25,6 +25,12 @@ Usage:
25
25
  cortex init [--machine <n>] [--profile <n>] [--mcp on|off] [--template <t>] [--from-existing <path>] [--dry-run] [-y]
26
26
  Set up cortex (templates: python-project, monorepo, library, frontend)
27
27
  cortex detect-skills [--import] Find untracked skills in ~/.claude/skills/
28
+ cortex skills list List installed skills
29
+ cortex skills add <project> <path> Link or copy a skill file into one project
30
+ cortex skills remove <project> <name> Remove a project skill by name
31
+ cortex hooks list Show hook tool preferences
32
+ cortex hooks enable <tool> Enable hooks for one tool
33
+ cortex hooks disable <tool> Disable hooks for one tool
28
34
  cortex status Health, active project, stats
29
35
  cortex search <query> [--project <n>] [--type <t>] [--limit <n>]
30
36
  Search your cortex
@@ -206,6 +212,8 @@ const CLI_COMMANDS = [
206
212
  "review-ui",
207
213
  "quality-feedback",
208
214
  "skill-list",
215
+ "skills",
216
+ "hooks",
209
217
  "detect-skills",
210
218
  "backlog",
211
219
  "quickstart",
package/mcp/dist/link.js CHANGED
@@ -124,6 +124,18 @@ function currentPackageVersion() {
124
124
  return null;
125
125
  }
126
126
  }
127
+ function readProjectConfig(cortexPath, project) {
128
+ const configPath = path.join(cortexPath, project, "cortex.project.yaml");
129
+ if (!fs.existsSync(configPath))
130
+ return {};
131
+ try {
132
+ const parsed = yaml.load(fs.readFileSync(configPath, "utf8"), { schema: yaml.CORE_SCHEMA });
133
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
134
+ }
135
+ catch {
136
+ return {};
137
+ }
138
+ }
127
139
  function maybeOfferStarterTemplateUpdate(cortexPath) {
128
140
  const current = currentPackageVersion();
129
141
  if (!current)
@@ -318,7 +330,8 @@ function linkProject(cortexPath, project, tools) {
318
330
  }
319
331
  // Project-level skills
320
332
  const projectSkills = path.join(cortexPath, project, ".claude", "skills");
321
- if (fs.existsSync(projectSkills)) {
333
+ const config = readProjectConfig(cortexPath, project);
334
+ if (config.skills !== false && fs.existsSync(projectSkills)) {
322
335
  const targetSkills = path.join(target, ".claude", "skills");
323
336
  linkSkillsDir(projectSkills, targetSkills, cortexPath, symlinkFile);
324
337
  }
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 (SYNONYMS[bg]) {
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 && SYNONYMS[termText]) {
330
- for (const syn of SYNONYMS[termText]) {
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/cortex",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Long-term memory for AI agents. Stored as markdown in a git repo you own.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,21 +12,21 @@
12
12
  "skills"
13
13
  ],
14
14
  "dependencies": {
15
- "@modelcontextprotocol/sdk": "^1.0.0",
15
+ "@modelcontextprotocol/sdk": "^1.27.1",
16
16
  "@xenova/transformers": "^2.17.2",
17
- "glob": "^12.0.0",
18
- "js-yaml": "^4.1.0",
17
+ "glob": "^13.0.6",
18
+ "js-yaml": "^4.1.1",
19
19
  "sql.js-fts5": "^1.4.0",
20
- "zod": "^4.0.0"
20
+ "zod": "^4.3.6"
21
21
  },
22
22
  "devDependencies": {
23
- "@types/js-yaml": "^4.0.0",
24
- "@types/node": "^22.0.0",
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.2",
28
- "tsx": "^4.0.0",
29
- "typescript": "^5.7.0",
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": {