cortex-mcp 2.7.5 → 2.8.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 CHANGED
@@ -64,7 +64,7 @@ Next conversation → Cortex injects relevant memories → AI already knows your
64
64
 
65
65
  ---
66
66
 
67
- ## All 35 Tools (All Free)
67
+ ## All 30 Tools (All Free)
68
68
 
69
69
  ### Core Memory
70
70
  | Tool | What it does |
@@ -121,6 +121,9 @@ Next conversation → Cortex injects relevant memories → AI already knows your
121
121
  | Tool | What it does |
122
122
  |------|-------------|
123
123
  | `health_check` | Server health, embedding status, vector search status |
124
+ | `repair_brain` | Rebuild FTS index, integrity check, VACUUM, fix orphaned data |
125
+ | `detect_conflicts` | Find contradictory memories (e.g. "use X" vs "avoid X") |
126
+ | `suggest_cleanup` | Identify stale low-value memories you should delete |
124
127
 
125
128
  ---
126
129
 
@@ -357,6 +357,32 @@ const MCP_TOOLS = [
357
357
  properties: {},
358
358
  },
359
359
  },
360
+ {
361
+ name: 'repair_brain',
362
+ description: 'Repair and optimize the memory database. Rebuilds the full-text search index, checks database integrity, removes orphaned vectors, and runs VACUUM. Use this if search stops working or the brain feels slow.',
363
+ inputSchema: {
364
+ type: 'object',
365
+ properties: {},
366
+ },
367
+ },
368
+ {
369
+ name: 'detect_conflicts',
370
+ description: 'Scan all active memories for contradictions. Finds pairs of memories that say opposite things (e.g. "use PostgreSQL" vs "use MongoDB"). Returns conflicts so the user can resolve which is correct.',
371
+ inputSchema: {
372
+ type: 'object',
373
+ properties: {},
374
+ },
375
+ },
376
+ {
377
+ name: 'suggest_cleanup',
378
+ description: 'Identify stale, low-value memories that should be deleted. Finds memories older than 30 days with low confidence, zero access count, and no pins. Returns a list the user can review and bulk-delete.',
379
+ inputSchema: {
380
+ type: 'object',
381
+ properties: {
382
+ olderThanDays: { type: 'number', description: 'Only suggest memories older than N days (default 30)' },
383
+ },
384
+ },
385
+ },
360
386
  ];
361
387
  // --- Dynamic Context via ContextBuilder ---
362
388
  let cachedContextBuilder = null;
@@ -580,6 +606,15 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
580
606
  else if (toolName === 'analytics') {
581
607
  return handleAnalytics(id);
582
608
  }
609
+ else if (toolName === 'repair_brain') {
610
+ return handleRepairBrain(id);
611
+ }
612
+ else if (toolName === 'detect_conflicts') {
613
+ return handleDetectConflicts(id);
614
+ }
615
+ else if (toolName === 'suggest_cleanup') {
616
+ return handleSuggestCleanup(id, args);
617
+ }
583
618
  else {
584
619
  return {
585
620
  jsonrpc: '2.0', id,
@@ -1134,6 +1169,28 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1134
1169
  for (const s of sessions)
1135
1170
  parts.push(s);
1136
1171
  }
1172
+ // ─── BRAIN LAYER 3.5: What's New Since Last Session ──────────────
1173
+ try {
1174
+ const db = memoryStore.db || memoryStore.connection;
1175
+ const oneDayAgo = Date.now() - 86400000;
1176
+ const recentNew = db.prepare(
1177
+ 'SELECT type, intent FROM memory_units WHERE is_active = 1 AND created_at > ? ORDER BY created_at DESC LIMIT 10'
1178
+ ).all(oneDayAgo);
1179
+ if (recentNew.length > 0) {
1180
+ const typeCounts = {};
1181
+ for (const m of recentNew) {
1182
+ typeCounts[m.type] = (typeCounts[m.type] || 0) + 1;
1183
+ }
1184
+ const summary = Object.entries(typeCounts).map(([t, c]) => `${c} ${t.toLowerCase()}${c > 1 ? 's' : ''}`).join(', ');
1185
+ parts.push(`\n## 🆕 Since Last Session\n_${recentNew.length} new memories (${summary})_`);
1186
+ for (const m of recentNew.slice(0, 5)) {
1187
+ parts.push(`- [${m.type}] ${m.intent}`);
1188
+ }
1189
+ if (recentNew.length > 5) {
1190
+ parts.push(`_...and ${recentNew.length - 5} more_`);
1191
+ }
1192
+ }
1193
+ } catch { /* non-fatal */ }
1137
1194
  // ─── BRAIN LAYER 4: Hot Corrections (learning rate) ──────────────
1138
1195
  const hotCorrections = (0, learning_rate_1.formatHotCorrections)(memoryStore);
1139
1196
  if (hotCorrections)
@@ -2025,6 +2082,162 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
2025
2082
  return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `analytics error: ${err.message}` }], isError: true } };
2026
2083
  }
2027
2084
  }
2085
+ // ═══ NEW CORE TOOLS: repair_brain, detect_conflicts, suggest_cleanup ═══
2086
+ function handleRepairBrain(id) {
2087
+ try {
2088
+ const db = memoryStore.db || memoryStore.connection;
2089
+ const repairs = [];
2090
+ // 1. Integrity check
2091
+ const integrityResult = db.pragma('integrity_check');
2092
+ const integrityOk = integrityResult && integrityResult[0] && integrityResult[0].integrity_check === 'ok';
2093
+ repairs.push(integrityOk ? '✅ Database integrity: OK' : '⚠️ Database integrity issues detected');
2094
+ // 2. Rebuild FTS index
2095
+ try {
2096
+ db.exec("INSERT INTO memory_fts(memory_fts) VALUES('rebuild')");
2097
+ repairs.push('✅ Full-text search index: Rebuilt');
2098
+ } catch (e) {
2099
+ repairs.push('⚠️ FTS rebuild failed: ' + e.message);
2100
+ }
2101
+ // 3. Fix orphaned vectors (vectors for deleted memories)
2102
+ try {
2103
+ const orphaned = db.prepare(
2104
+ "DELETE FROM memory_vectors WHERE memory_id NOT IN (SELECT id FROM memory_units WHERE is_active = 1)"
2105
+ ).run();
2106
+ repairs.push(`✅ Orphaned vectors: Cleaned ${orphaned.changes} entries`);
2107
+ } catch { repairs.push('✅ Orphaned vectors: None (table may not exist)'); }
2108
+ // 4. Fix memories with missing created_at
2109
+ try {
2110
+ const fixed = db.prepare(
2111
+ "UPDATE memory_units SET created_at = timestamp WHERE created_at = 0 OR created_at IS NULL"
2112
+ ).run();
2113
+ if (fixed.changes > 0) {
2114
+ repairs.push(`✅ Fixed ${fixed.changes} memories with missing created_at`);
2115
+ }
2116
+ } catch { }
2117
+ // 5. WAL checkpoint + VACUUM
2118
+ try {
2119
+ db.pragma('wal_checkpoint(TRUNCATE)');
2120
+ db.exec('VACUUM');
2121
+ repairs.push('✅ Database compacted (VACUUM)');
2122
+ } catch (e) {
2123
+ repairs.push('⚠️ VACUUM failed: ' + e.message);
2124
+ }
2125
+ // 6. Stats after repair
2126
+ const activeCount = memoryStore.activeCount();
2127
+ repairs.push(`\n📊 Active memories: ${activeCount}`);
2128
+ (0, memory_cache_1.invalidateCache)();
2129
+ return {
2130
+ jsonrpc: '2.0', id,
2131
+ result: { content: [{ type: 'text', text: `# 🔧 Brain Repair Complete\n\n${repairs.join('\n')}` }] },
2132
+ };
2133
+ } catch (err) {
2134
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Repair error: ${err.message}` }], isError: true } };
2135
+ }
2136
+ }
2137
+ function handleDetectConflicts(id) {
2138
+ try {
2139
+ const memories = memoryStore.getActive(500);
2140
+ // Only look at decisions and conventions — these are the ones that conflict
2141
+ const candidates = memories.filter(m =>
2142
+ ['DECISION', 'CONVENTION', 'CORRECTION'].includes(m.type)
2143
+ );
2144
+ const conflicts = [];
2145
+ // Compare each pair for contradictions
2146
+ const OPPOSITE_PAIRS = [
2147
+ ['always', 'never'], ['use', 'avoid'], ['enable', 'disable'],
2148
+ ['allow', 'block'], ['include', 'exclude'], ['add', 'remove'],
2149
+ ['yes', 'no'], ['true', 'false'], ['must', 'must not'],
2150
+ ['do', "don't"], ['do', 'do not'], ['can', 'cannot'],
2151
+ ];
2152
+ for (let i = 0; i < candidates.length; i++) {
2153
+ for (let j = i + 1; j < candidates.length; j++) {
2154
+ const a = candidates[i];
2155
+ const b = candidates[j];
2156
+ const aText = (a.intent + ' ' + (a.action || '')).toLowerCase();
2157
+ const bText = (b.intent + ' ' + (b.action || '')).toLowerCase();
2158
+ // Check if they share a subject but have opposite verbs
2159
+ let isConflict = false;
2160
+ for (const [pos, neg] of OPPOSITE_PAIRS) {
2161
+ if ((aText.includes(pos) && bText.includes(neg)) ||
2162
+ (aText.includes(neg) && bText.includes(pos))) {
2163
+ // Check they share at least one significant word (3+ chars)
2164
+ const aWords = aText.split(/\s+/).filter(w => w.length > 3);
2165
+ const bWords = new Set(bText.split(/\s+/).filter(w => w.length > 3));
2166
+ const shared = aWords.filter(w => bWords.has(w));
2167
+ if (shared.length >= 1) {
2168
+ isConflict = true;
2169
+ break;
2170
+ }
2171
+ }
2172
+ }
2173
+ if (isConflict) {
2174
+ conflicts.push({
2175
+ a: { id: a.id, type: a.type, intent: a.intent, age: Math.floor((Date.now() - a.createdAt) / 86400000) },
2176
+ b: { id: b.id, type: b.type, intent: b.intent, age: Math.floor((Date.now() - b.createdAt) / 86400000) },
2177
+ });
2178
+ }
2179
+ }
2180
+ }
2181
+ if (conflicts.length === 0) {
2182
+ return {
2183
+ jsonrpc: '2.0', id,
2184
+ result: { content: [{ type: 'text', text: '✅ No conflicts detected! All memories are consistent.' }] },
2185
+ };
2186
+ }
2187
+ const lines = [`# ⚠️ ${conflicts.length} Potential Conflict(s) Detected\n`];
2188
+ for (let i = 0; i < conflicts.length; i++) {
2189
+ const c = conflicts[i];
2190
+ lines.push(`### Conflict ${i + 1}`);
2191
+ lines.push(`**A:** [${c.a.type}] ${c.a.intent} _(${c.a.age}d old, id: ${c.a.id})_`);
2192
+ lines.push(`**B:** [${c.b.type}] ${c.b.intent} _(${c.b.age}d old, id: ${c.b.id})_`);
2193
+ lines.push(`→ Use \`delete_memory\` to remove the outdated one.\n`);
2194
+ }
2195
+ return {
2196
+ jsonrpc: '2.0', id,
2197
+ result: { content: [{ type: 'text', text: lines.join('\n') }] },
2198
+ };
2199
+ } catch (err) {
2200
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `conflict detection error: ${err.message}` }], isError: true } };
2201
+ }
2202
+ }
2203
+ function handleSuggestCleanup(id, args) {
2204
+ try {
2205
+ const olderThanDays = args.olderThanDays || 30;
2206
+ const cutoff = Date.now() - (olderThanDays * 86400000);
2207
+ const db = memoryStore.db || memoryStore.connection;
2208
+ // Find stale, low-value memories
2209
+ const stale = db.prepare(`
2210
+ SELECT * FROM memory_units
2211
+ WHERE is_active = 1
2212
+ AND created_at < ?
2213
+ AND confidence < 0.4
2214
+ AND access_count <= 1
2215
+ AND (tags IS NULL OR tags NOT LIKE '%pinned%')
2216
+ ORDER BY confidence ASC, access_count ASC
2217
+ LIMIT 25
2218
+ `).all(cutoff);
2219
+ if (stale.length === 0) {
2220
+ return {
2221
+ jsonrpc: '2.0', id,
2222
+ result: { content: [{ type: 'text', text: `✅ Brain is clean! No stale memories older than ${olderThanDays} days with low value found.` }] },
2223
+ };
2224
+ }
2225
+ const lines = [`# 🧹 ${stale.length} Stale Memories (Cleanup Suggestions)\n`];
2226
+ lines.push(`These memories are older than ${olderThanDays} days, have low confidence, and are rarely accessed:\n`);
2227
+ for (const row of stale) {
2228
+ const age = Math.floor((Date.now() - row.created_at) / 86400000);
2229
+ lines.push(`- **[${row.type}]** ${row.intent}`);
2230
+ lines.push(` _Confidence: ${(row.confidence * 100).toFixed(0)}% · Accessed: ${row.access_count}x · Age: ${age}d · id: ${row.id}_`);
2231
+ }
2232
+ lines.push(`\n→ Use \`delete_memory\` with each id to remove, or \`clear_memories\` for bulk cleanup.`);
2233
+ return {
2234
+ jsonrpc: '2.0', id,
2235
+ result: { content: [{ type: 'text', text: lines.join('\n') }] },
2236
+ };
2237
+ } catch (err) {
2238
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `cleanup suggestion error: ${err.message}` }], isError: true } };
2239
+ }
2240
+ }
2028
2241
  return { handleMCPRequest };
2029
2242
  }
2030
2243
  // ─── NEW: Export/Import/Graph Handlers ─────────────────────────────────────
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "cortex-mcp",
3
3
  "displayName": "Cortex MCP Server",
4
4
  "description": "Persistent memory for AI coding assistants. Injects context from past sessions into every LLM request.",
5
- "version": "2.7.5",
5
+ "version": "2.8.0",
6
6
  "publisher": "cortex",
7
7
  "license": "MIT",
8
8
  "engines": {