cortex-mcp 2.5.0 → 2.7.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.
Files changed (166) hide show
  1. package/CHANGELOG.md +58 -279
  2. package/README.md +224 -393
  3. package/dist/cli/setup.js +60 -58
  4. package/dist/cli/setup.js.map +1 -1
  5. package/dist/config/config.js +2 -2
  6. package/dist/config/config.js.map +1 -1
  7. package/dist/db/database.d.ts +0 -8
  8. package/dist/db/database.d.ts.map +1 -1
  9. package/dist/db/database.js +2 -34
  10. package/dist/db/database.js.map +1 -1
  11. package/dist/db/event-log.d.ts +0 -4
  12. package/dist/db/event-log.d.ts.map +1 -1
  13. package/dist/db/event-log.js +10 -14
  14. package/dist/db/event-log.js.map +1 -1
  15. package/dist/db/memory-store.d.ts +3 -30
  16. package/dist/db/memory-store.d.ts.map +1 -1
  17. package/dist/db/memory-store.js +44 -140
  18. package/dist/db/memory-store.js.map +1 -1
  19. package/dist/embedding-worker.js +1 -1
  20. package/dist/embedding-worker.js.map +1 -1
  21. package/dist/hooks/git-capture.js +3 -3
  22. package/dist/hooks/git-hooks.js +2 -5
  23. package/dist/mcp-stdio.js +3 -34
  24. package/dist/mcp-stdio.js.map +1 -1
  25. package/dist/memory/anticipation-engine.d.ts.map +1 -1
  26. package/dist/memory/anticipation-engine.js +10 -18
  27. package/dist/memory/anticipation-engine.js.map +1 -1
  28. package/dist/memory/auto-learner.d.ts.map +1 -1
  29. package/dist/memory/auto-learner.js +43 -190
  30. package/dist/memory/auto-learner.js.map +1 -1
  31. package/dist/memory/confidence-decay.d.ts.map +1 -1
  32. package/dist/memory/confidence-decay.js +9 -13
  33. package/dist/memory/confidence-decay.js.map +1 -1
  34. package/dist/memory/embedding-manager.d.ts.map +1 -1
  35. package/dist/memory/embedding-manager.js +4 -6
  36. package/dist/memory/embedding-manager.js.map +1 -1
  37. package/dist/memory/export-import.js +2 -2
  38. package/dist/memory/export-import.js.map +1 -1
  39. package/dist/memory/git-memory.d.ts.map +1 -1
  40. package/dist/memory/git-memory.js +26 -20
  41. package/dist/memory/git-memory.js.map +1 -1
  42. package/dist/memory/learning-rate.js +7 -8
  43. package/dist/memory/learning-rate.js.map +1 -1
  44. package/dist/memory/llm-enhancer.d.ts +14 -2
  45. package/dist/memory/llm-enhancer.d.ts.map +1 -1
  46. package/dist/memory/llm-enhancer.js +46 -66
  47. package/dist/memory/llm-enhancer.js.map +1 -1
  48. package/dist/memory/memory-cache.d.ts.map +1 -1
  49. package/dist/memory/memory-cache.js +0 -10
  50. package/dist/memory/memory-cache.js.map +1 -1
  51. package/dist/memory/memory-consolidator.d.ts.map +1 -1
  52. package/dist/memory/memory-consolidator.js +14 -20
  53. package/dist/memory/memory-consolidator.js.map +1 -1
  54. package/dist/memory/memory-decay.d.ts.map +1 -1
  55. package/dist/memory/memory-decay.js +51 -77
  56. package/dist/memory/memory-decay.js.map +1 -1
  57. package/dist/memory/memory-quality.d.ts +1 -1
  58. package/dist/memory/memory-quality.d.ts.map +1 -1
  59. package/dist/memory/memory-quality.js +6 -9
  60. package/dist/memory/memory-quality.js.map +1 -1
  61. package/dist/memory/memory-ranker.d.ts.map +1 -1
  62. package/dist/memory/memory-ranker.js +3 -7
  63. package/dist/memory/memory-ranker.js.map +1 -1
  64. package/dist/memory/meta-memory.js +3 -3
  65. package/dist/memory/meta-memory.js.map +1 -1
  66. package/dist/memory/session-tracker.d.ts +0 -2
  67. package/dist/memory/session-tracker.d.ts.map +1 -1
  68. package/dist/memory/session-tracker.js +8 -26
  69. package/dist/memory/session-tracker.js.map +1 -1
  70. package/dist/memory/temporal-engine.d.ts.map +1 -1
  71. package/dist/memory/temporal-engine.js +13 -9
  72. package/dist/memory/temporal-engine.js.map +1 -1
  73. package/dist/retrieval/hybrid-retriever.d.ts +2 -0
  74. package/dist/retrieval/hybrid-retriever.d.ts.map +1 -1
  75. package/dist/retrieval/hybrid-retriever.js +13 -3
  76. package/dist/retrieval/hybrid-retriever.js.map +1 -1
  77. package/dist/scanners/architecture-graph.js +2 -2
  78. package/dist/scanners/architecture-graph.js.map +1 -1
  79. package/dist/scanners/code-verifier.d.ts +0 -1
  80. package/dist/scanners/code-verifier.d.ts.map +1 -1
  81. package/dist/scanners/code-verifier.js +14 -14
  82. package/dist/scanners/code-verifier.js.map +1 -1
  83. package/dist/scanners/context-builder.d.ts.map +1 -1
  84. package/dist/scanners/context-builder.js +45 -33
  85. package/dist/scanners/context-builder.js.map +1 -1
  86. package/dist/scanners/export-map.js +2 -2
  87. package/dist/scanners/export-map.js.map +1 -1
  88. package/dist/scanners/project-scanner.js +2 -2
  89. package/dist/scanners/project-scanner.js.map +1 -1
  90. package/dist/security/encryption.js +1 -1
  91. package/dist/security/encryption.js.map +1 -1
  92. package/dist/security/feature-gate.d.ts.map +1 -1
  93. package/dist/security/feature-gate.js +20 -62
  94. package/dist/security/feature-gate.js.map +1 -1
  95. package/dist/security/license.js +35 -282
  96. package/dist/security/license.js.map +1 -1
  97. package/dist/security/rate-limiter.d.ts +3 -4
  98. package/dist/security/rate-limiter.d.ts.map +1 -1
  99. package/dist/security/rate-limiter.js +29 -11
  100. package/dist/security/rate-limiter.js.map +1 -1
  101. package/dist/server/dashboard.js +327 -166
  102. package/dist/server/dashboard.js.map +1 -1
  103. package/dist/server/mcp-handler.d.ts.map +1 -1
  104. package/dist/server/mcp-handler.js +771 -891
  105. package/dist/server/mcp-handler.js.map +1 -1
  106. package/package.json +8 -18
  107. package/dist/memory/completion-resolver.d.ts +0 -38
  108. package/dist/memory/completion-resolver.d.ts.map +0 -1
  109. package/dist/memory/completion-resolver.js +0 -127
  110. package/dist/memory/completion-resolver.js.map +0 -1
  111. package/dist/memory/convention-detector.d.ts +0 -11
  112. package/dist/memory/convention-detector.d.ts.map +0 -1
  113. package/dist/memory/convention-detector.js +0 -294
  114. package/dist/memory/convention-detector.js.map +0 -1
  115. package/dist/memory/correction-detector.d.ts +0 -33
  116. package/dist/memory/correction-detector.d.ts.map +0 -1
  117. package/dist/memory/correction-detector.js +0 -129
  118. package/dist/memory/correction-detector.js.map +0 -1
  119. package/dist/memory/error-learner.d.ts +0 -26
  120. package/dist/memory/error-learner.d.ts.map +0 -1
  121. package/dist/memory/error-learner.js +0 -145
  122. package/dist/memory/error-learner.js.map +0 -1
  123. package/dist/memory/file-relationships.d.ts +0 -47
  124. package/dist/memory/file-relationships.d.ts.map +0 -1
  125. package/dist/memory/file-relationships.js +0 -130
  126. package/dist/memory/file-relationships.js.map +0 -1
  127. package/dist/memory/impact-analyzer.d.ts +0 -16
  128. package/dist/memory/impact-analyzer.d.ts.map +0 -1
  129. package/dist/memory/impact-analyzer.js +0 -189
  130. package/dist/memory/impact-analyzer.js.map +0 -1
  131. package/dist/memory/instructions-generator.d.ts +0 -30
  132. package/dist/memory/instructions-generator.d.ts.map +0 -1
  133. package/dist/memory/instructions-generator.js +0 -117
  134. package/dist/memory/instructions-generator.js.map +0 -1
  135. package/dist/memory/pre-flight.d.ts +0 -24
  136. package/dist/memory/pre-flight.d.ts.map +0 -1
  137. package/dist/memory/pre-flight.js +0 -121
  138. package/dist/memory/pre-flight.js.map +0 -1
  139. package/dist/memory/preference-learner.d.ts +0 -28
  140. package/dist/memory/preference-learner.d.ts.map +0 -1
  141. package/dist/memory/preference-learner.js +0 -144
  142. package/dist/memory/preference-learner.js.map +0 -1
  143. package/dist/memory/regression-guard.d.ts +0 -35
  144. package/dist/memory/regression-guard.d.ts.map +0 -1
  145. package/dist/memory/regression-guard.js +0 -90
  146. package/dist/memory/regression-guard.js.map +0 -1
  147. package/dist/memory/resume-work.d.ts +0 -37
  148. package/dist/memory/resume-work.d.ts.map +0 -1
  149. package/dist/memory/resume-work.js +0 -141
  150. package/dist/memory/resume-work.js.map +0 -1
  151. package/dist/memory/success-tracker.d.ts +0 -33
  152. package/dist/memory/success-tracker.d.ts.map +0 -1
  153. package/dist/memory/success-tracker.js +0 -75
  154. package/dist/memory/success-tracker.js.map +0 -1
  155. package/dist/memory/tool-recommender.d.ts +0 -29
  156. package/dist/memory/tool-recommender.d.ts.map +0 -1
  157. package/dist/memory/tool-recommender.js +0 -117
  158. package/dist/memory/tool-recommender.js.map +0 -1
  159. package/dist/memory/usage-stats.d.ts +0 -98
  160. package/dist/memory/usage-stats.d.ts.map +0 -1
  161. package/dist/memory/usage-stats.js +0 -345
  162. package/dist/memory/usage-stats.js.map +0 -1
  163. package/dist/utils/extract-tags.d.ts +0 -16
  164. package/dist/utils/extract-tags.d.ts.map +0 -1
  165. package/dist/utils/extract-tags.js +0 -40
  166. package/dist/utils/extract-tags.js.map +0 -1
@@ -23,24 +23,10 @@ const git_memory_1 = require("../memory/git-memory");
23
23
  const export_map_1 = require("../scanners/export-map");
24
24
  const architecture_graph_1 = require("../scanners/architecture-graph");
25
25
  const rate_limiter_1 = require("../security/rate-limiter");
26
+ const license_1 = require("../security/license");
26
27
  const feature_gate_1 = require("../security/feature-gate");
27
28
  const export_import_1 = require("../memory/export-import");
28
29
  const llm_enhancer_1 = require("../memory/llm-enhancer");
29
- const usage_stats_1 = require("../memory/usage-stats");
30
- const correction_detector_1 = require("../memory/correction-detector");
31
- const success_tracker_1 = require("../memory/success-tracker");
32
- const error_learner_1 = require("../memory/error-learner");
33
- const completion_resolver_1 = require("../memory/completion-resolver");
34
- const pre_flight_1 = require("../memory/pre-flight");
35
- const impact_analyzer_1 = require("../memory/impact-analyzer");
36
- const resume_work_1 = require("../memory/resume-work");
37
- const preference_learner_1 = require("../memory/preference-learner");
38
- const convention_detector_1 = require("../memory/convention-detector");
39
- const export_map_2 = require("../scanners/export-map");
40
- const file_relationships_1 = require("../memory/file-relationships");
41
- const instructions_generator_1 = require("../memory/instructions-generator");
42
- const tool_recommender_1 = require("../memory/tool-recommender");
43
- const regression_guard_1 = require("../memory/regression-guard");
44
30
  // --- Query Expansion (Synonym Map) ---
45
31
  const SYNONYMS = {
46
32
  auth: ['authentication', 'login', 'signin', 'sign-in', 'credentials'],
@@ -157,7 +143,7 @@ const MCP_TOOLS = [
157
143
  },
158
144
  {
159
145
  name: 'force_recall',
160
- description: 'MANDATORY: Call this at the START of every conversation. Returns ALL corrections, decisions, conventions, and bug fixes. Also searches for topic-specific memories. This is the single entry point for complete context injection. Without calling this, you WILL repeat past mistakes, contradict previous decisions, and miss critical conventions. The user expects you to remember everything from past sessions.',
146
+ description: 'MANDATORY: Call this at the START of every conversation. Returns ALL corrections, decisions, conventions, and bug fixes. Also searches for topic-specific memories. This is the single entry point for complete context injection.',
161
147
  inputSchema: {
162
148
  type: 'object',
163
149
  properties: {
@@ -216,7 +202,7 @@ const MCP_TOOLS = [
216
202
  },
217
203
  {
218
204
  name: 'auto_learn',
219
- description: 'CALL THIS AFTER EVERY RESPONSE. Pass the text of your response and Cortex will automatically extract and store any decisions, corrections, conventions, or bug fixes — with zero manual effort. This is how Cortex learns passively. If you skip this, everything you said in this conversation is LOST FOREVER — the user will have to repeat themselves next time.',
205
+ description: 'CALL THIS AFTER EVERY RESPONSE. Pass the text of your response and Cortex will automatically extract and store any decisions, corrections, conventions, or bug fixes — with zero manual effort. This is how Cortex learns passively.',
220
206
  inputSchema: {
221
207
  type: 'object',
222
208
  properties: {
@@ -254,47 +240,157 @@ const MCP_TOOLS = [
254
240
  },
255
241
  },
256
242
  {
257
- name: 'review_code',
258
- description: 'Review code against your stored conventions, past bug patterns, and project decisions. Returns specific violations with memory references like having a senior dev review your code.',
243
+ name: 'search_by_file',
244
+ description: 'Find all memories related to a specific file. Use this when working on a file to see what decisions, bugs, and conventions have been recorded for it.',
259
245
  inputSchema: {
260
246
  type: 'object',
261
247
  properties: {
262
- code: { type: 'string', description: 'The code to review' },
263
- filename: { type: 'string', description: 'Optional filename for context-aware review' },
248
+ filePath: { type: 'string', description: 'The file path to search for (e.g. "src/auth.ts" or "auth")' },
249
+ limit: { type: 'number', description: 'Maximum results to return (default 20)' },
250
+ },
251
+ required: ['filePath'],
252
+ },
253
+ },
254
+ {
255
+ name: 'pin_memory',
256
+ description: 'Pin a memory so it is ALWAYS injected into context, regardless of topic. Use for critical rules like "never commit to main" or "always use TypeScript strict mode".',
257
+ inputSchema: {
258
+ type: 'object',
259
+ properties: {
260
+ id: { type: 'string', description: 'ID of the memory to pin (from list_memories or recall_memory)' },
261
+ },
262
+ required: ['id'],
263
+ },
264
+ },
265
+ {
266
+ name: 'unpin_memory',
267
+ description: 'Unpin a previously pinned memory so it returns to normal priority ranking.',
268
+ inputSchema: {
269
+ type: 'object',
270
+ properties: {
271
+ id: { type: 'string', description: 'ID of the memory to unpin' },
272
+ },
273
+ required: ['id'],
274
+ },
275
+ },
276
+ {
277
+ name: 'undo_last',
278
+ description: 'Undo the last N memories that were stored. Use this if auto_learn stored garbage or if you made a mistake. Deletes the most recently created memories.',
279
+ inputSchema: {
280
+ type: 'object',
281
+ properties: {
282
+ count: { type: 'number', description: 'How many recent memories to undo (default 1, max 10)' },
264
283
  },
265
- required: ['code'],
266
284
  },
267
285
  },
268
286
  {
269
- name: 'pre_check',
270
- description: 'Pre-flight check: get ALL conventions, gotchas, past bugs, and corrections for a file BEFORE writing code. Like a pilot\'s checklist — call this before making any code changes to avoid repeating past mistakes. Skipping this risks reintroducing bugs that were already fixed.',
287
+ name: 'clear_memories',
288
+ description: 'Bulk delete memories by type or age. Use to clean up an entire category of memories or remove old stale memories.',
271
289
  inputSchema: {
272
290
  type: 'object',
273
291
  properties: {
274
- filename: { type: 'string', description: 'The file you are about to edit' },
275
- task: { type: 'string', description: 'What you plan to do (helps find relevant past failures)' },
292
+ type: { type: 'string', enum: ['DECISION', 'CORRECTION', 'CONVENTION', 'BUG_FIX', 'INSIGHT', 'ALL'], description: 'Delete all memories of this type (or ALL for everything)' },
293
+ olderThanDays: { type: 'number', description: 'Only delete memories older than N days (optional safety filter)' },
276
294
  },
295
+ required: ['type'],
277
296
  },
278
297
  },
279
298
  {
280
- name: 'check_impact',
281
- description: 'Impact analysis: before editing a file, check which other files depend on it. Shows direct and indirect dependents with risk level. Prevents breaking changes.',
299
+ name: 'search_timeline',
300
+ description: 'Search memories by date range. Use to answer questions like "what did we decide last week?" or "what bugs did we fix yesterday?".',
282
301
  inputSchema: {
283
302
  type: 'object',
284
303
  properties: {
285
- file: { type: 'string', description: 'The file you plan to modify' },
304
+ from: { type: 'string', description: 'Start date (ISO format or relative like "7 days ago", "yesterday")' },
305
+ to: { type: 'string', description: 'End date (ISO format or "now"). Default: now' },
306
+ type: { type: 'string', enum: ['DECISION', 'CORRECTION', 'CONVENTION', 'BUG_FIX', 'INSIGHT', 'ALL'], description: 'Filter by type (optional)' },
286
307
  },
287
- required: ['file'],
308
+ required: ['from'],
309
+ },
310
+ },
311
+ {
312
+ name: 'thumbs_up',
313
+ description: 'Mark a memory as useful/accurate. This boosts its importance and helps Cortex learn which memories matter most over time.',
314
+ inputSchema: {
315
+ type: 'object',
316
+ properties: {
317
+ id: { type: 'string', description: 'ID of the memory to upvote' },
318
+ },
319
+ required: ['id'],
288
320
  },
289
321
  },
290
322
  {
291
- name: 'resume_work',
292
- description: 'Resume work after a conversation break. Returns: last session summary, current tasks, recent corrections (don\'t repeat!), recent decisions, and activity summary. Call this when starting a new conversation about ongoing work.',
323
+ name: 'thumbs_down',
324
+ description: 'Mark a memory as not useful or incorrect. This demotes its importance. Repeatedly downvoted memories will be auto-deactivated.',
325
+ inputSchema: {
326
+ type: 'object',
327
+ properties: {
328
+ id: { type: 'string', description: 'ID of the memory to downvote' },
329
+ reason: { type: 'string', description: 'Why this memory is wrong or not useful (optional)' },
330
+ },
331
+ required: ['id'],
332
+ },
333
+ },
334
+ {
335
+ name: 'backup_brain',
336
+ description: 'Create a local backup of the entire memory database file. Run this periodically to ensure the user NEVER loses their project knowledge.',
293
337
  inputSchema: {
294
338
  type: 'object',
295
339
  properties: {},
296
340
  },
297
341
  },
342
+ {
343
+ name: 'get_daily_summary',
344
+ description: 'Read the AI-generated summary of what happened on a specific date. If no date is given, summarize today and store it.',
345
+ inputSchema: {
346
+ type: 'object',
347
+ properties: {
348
+ date: { type: 'string', description: 'Date in YYYY-MM-DD format (optional, defaults to today)' },
349
+ },
350
+ },
351
+ },
352
+ {
353
+ name: 'analytics',
354
+ description: 'Get deep analytics about the brain: memory distribution, top accessed memories, and database health. Use this to understand what the AI knows best.',
355
+ inputSchema: {
356
+ type: 'object',
357
+ properties: {},
358
+ },
359
+ },
360
+ {
361
+ name: 'export_memories',
362
+ description: 'Export ALL memories to a JSON file on disk. Use this to back up your project knowledge, share it with teammates, or migrate to a new machine. The JSON file can later be imported with import_memories.',
363
+ inputSchema: {
364
+ type: 'object',
365
+ properties: {
366
+ filePath: { type: 'string', description: 'Full path where the export JSON should be saved (e.g. /Users/me/cortex-backup.json)' },
367
+ },
368
+ required: ['filePath'],
369
+ },
370
+ },
371
+ {
372
+ name: 'import_memories',
373
+ description: 'Import memories from a previously exported JSON file. Skips duplicates automatically. Use this to restore a backup or share project knowledge with a teammate.',
374
+ inputSchema: {
375
+ type: 'object',
376
+ properties: {
377
+ filePath: { type: 'string', description: 'Full path to the cortex export JSON file to import' },
378
+ },
379
+ required: ['filePath'],
380
+ },
381
+ },
382
+ {
383
+ name: 'get_related_memories',
384
+ description: 'Follow the graph of memory relationships to find connected memories. Given a memory ID, traverses CAUSED_BY, FIXED_BY, RELATED_TO, and DEPENDS_ON edges to surface related context you might not have found with search.',
385
+ inputSchema: {
386
+ type: 'object',
387
+ properties: {
388
+ id: { type: 'string', description: 'ID of the starting memory (from recall_memory or list_memories)' },
389
+ maxHops: { type: 'number', description: 'How many relationship hops to traverse (default 2, max 4)' },
390
+ },
391
+ required: ['id'],
392
+ },
393
+ },
298
394
  ];
299
395
  // --- Dynamic Context via ContextBuilder ---
300
396
  let cachedContextBuilder = null;
@@ -316,12 +412,68 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
316
412
  result: {
317
413
  protocolVersion: '2024-11-05',
318
414
  capabilities: { tools: {}, resources: {}, prompts: {} },
319
- serverInfo: { name: 'Cortex', version: require('../../package.json').version },
415
+ serverInfo: { name: 'cortex', version: '2.7.0' },
320
416
  },
321
417
  };
322
418
  case 'notifications/initialized':
323
- case 'notifications/cancelled':
324
419
  return null;
420
+ case 'prompts/list':
421
+ return {
422
+ jsonrpc: '2.0',
423
+ id,
424
+ result: {
425
+ prompts: [
426
+ {
427
+ name: 'cortex_system_context',
428
+ description: 'Injects crucial persistent project memories, decisions, corrections, and conventions directly into the system context. Use this to start every conversation with full project awareness.',
429
+ arguments: [
430
+ {
431
+ name: 'currentFile',
432
+ description: 'The currently active file path (optional, improves relevance)',
433
+ required: false,
434
+ },
435
+ {
436
+ name: 'topic',
437
+ description: 'What the user is asking about (optional, improves topic search)',
438
+ required: false,
439
+ },
440
+ ],
441
+ },
442
+ ],
443
+ },
444
+ };
445
+ case 'prompts/get': {
446
+ const promptName = rpc.params?.name;
447
+ const promptArgs = rpc.params?.arguments || {};
448
+ if (promptName === 'cortex_system_context') {
449
+ const builder = getContextBuilder(memoryStore);
450
+ const memoryPayload = builder.build({
451
+ currentFile: promptArgs.currentFile,
452
+ maxChars: 6000,
453
+ });
454
+ return {
455
+ jsonrpc: '2.0',
456
+ id,
457
+ result: {
458
+ description: 'Cortex Auto-Injected System Context',
459
+ messages: [
460
+ {
461
+ role: 'user',
462
+ content: {
463
+ type: 'text',
464
+ text: `You are connected to Cortex MCP — a persistent memory system. The following are critical memories, rules, and decisions from previous sessions. YOU MUST OBEY THESE RULES and reference this context when relevant:\n\n${memoryPayload}`,
465
+ },
466
+ },
467
+ ],
468
+ },
469
+ };
470
+ }
471
+ return {
472
+ jsonrpc: '2.0',
473
+ id,
474
+ error: { code: -32602, message: `Unknown prompt: ${promptName}` },
475
+ };
476
+ }
325
477
  case 'tools/list':
326
478
  return {
327
479
  jsonrpc: '2.0',
@@ -335,7 +487,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
335
487
  result: {
336
488
  resources: [{
337
489
  uri: 'memory://brain/context',
338
- name: 'Cortex MCP Context',
490
+ name: 'Brain Context',
339
491
  description: 'Top memories — corrections, decisions, conventions. Read this before every response.',
340
492
  mimeType: 'text/plain',
341
493
  }],
@@ -362,97 +514,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
362
514
  error: { code: -32602, message: `Unknown resource: ${uri}` },
363
515
  };
364
516
  }
365
- case 'prompts/list':
366
- return {
367
- jsonrpc: '2.0', id,
368
- result: {
369
- prompts: [
370
- {
371
- name: 'cortex-review',
372
- description: 'Review code against stored conventions, past bugs, and project decisions. Returns specific violations.',
373
- arguments: [
374
- { name: 'code', description: 'The code to review', required: true },
375
- { name: 'filename', description: 'Filename for context-aware review', required: false },
376
- ],
377
- },
378
- {
379
- name: 'cortex-debug',
380
- description: 'Debug an issue using Cortex memory. Checks for similar past bugs, failed attempts, and gotchas.',
381
- arguments: [
382
- { name: 'error', description: 'The error message or issue description', required: true },
383
- { name: 'file', description: 'The file where the error occurs', required: false },
384
- ],
385
- },
386
- {
387
- name: 'cortex-new-feature',
388
- description: 'Pre-flight checklist before building a new feature. Gets conventions, gotchas, and architecture context.',
389
- arguments: [
390
- { name: 'feature', description: 'What feature you plan to build', required: true },
391
- { name: 'files', description: 'Files you plan to modify', required: false },
392
- ],
393
- },
394
- ],
395
- },
396
- };
397
- case 'prompts/get': {
398
- const promptName = rpc.params?.name;
399
- const promptArgs = rpc.params?.arguments || {};
400
- if (promptName === 'cortex-review') {
401
- return {
402
- jsonrpc: '2.0', id,
403
- result: {
404
- description: 'Code review against Cortex memory',
405
- messages: [
406
- {
407
- role: 'user',
408
- content: {
409
- type: 'text',
410
- text: `Review this code against all stored conventions, past bug patterns, and project decisions.\n\nFile: ${promptArgs.filename || 'unknown'}\n\n\`\`\`\n${promptArgs.code || '(no code provided)'}\n\`\`\`\n\nUse the review_code and pre_check tools to check against stored memory. Report any violations with memory IDs.`,
411
- },
412
- },
413
- ],
414
- },
415
- };
416
- }
417
- if (promptName === 'cortex-debug') {
418
- return {
419
- jsonrpc: '2.0', id,
420
- result: {
421
- description: 'Debug with Cortex memory context',
422
- messages: [
423
- {
424
- role: 'user',
425
- content: {
426
- type: 'text',
427
- text: `I'm debugging this issue:\n\n${promptArgs.error || '(no error provided)'}\n\nFile: ${promptArgs.file || 'unknown'}\n\nUse recall_memory to search for similar past bugs, failed attempts, and gotchas. Check if this matches any known patterns. Use check_impact to see what other files might be affected.`,
428
- },
429
- },
430
- ],
431
- },
432
- };
433
- }
434
- if (promptName === 'cortex-new-feature') {
435
- return {
436
- jsonrpc: '2.0', id,
437
- result: {
438
- description: 'New feature pre-flight checklist',
439
- messages: [
440
- {
441
- role: 'user',
442
- content: {
443
- type: 'text',
444
- text: `I'm building a new feature: ${promptArgs.feature || '(no feature described)'}\n\nFiles I plan to modify: ${promptArgs.files || 'unknown'}\n\nBefore I start, run pre_check for each file, check_impact for dependency risks, and recall_memory for any relevant past work. Give me a checklist of things to watch out for.`,
445
- },
446
- },
447
- ],
448
- },
449
- };
450
- }
451
- return {
452
- jsonrpc: '2.0', id,
453
- error: { code: -32602, message: `Unknown prompt: ${promptName}` },
454
- };
455
- }
456
517
  case 'tools/call': {
457
518
  const toolName = rpc.params?.name;
458
519
  const args = rpc.params?.arguments || {};
@@ -463,10 +524,10 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
463
524
  result: { content: [{ type: 'text', text: 'Error: query too long (max 1000 chars)' }], isError: true },
464
525
  };
465
526
  }
466
- if (args.content && typeof args.content === 'string' && args.content.length > 50000) {
527
+ if (args.content && typeof args.content === 'string' && args.content.length > 5000) {
467
528
  return {
468
529
  jsonrpc: '2.0', id,
469
- result: { content: [{ type: 'text', text: 'Error: content too long (max 50000 chars)' }], isError: true },
530
+ result: { content: [{ type: 'text', text: 'Error: content too long (max 5000 chars)' }], isError: true },
470
531
  };
471
532
  }
472
533
  if (toolName === 'recall_memory') {
@@ -509,25 +570,49 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
509
570
  return handleAutoLearn(id, args);
510
571
  }
511
572
  else if (toolName === 'export_memories') {
512
- return handleExportMemories(id);
573
+ return handleExportMemoriesFn(memoryStore, id, args);
513
574
  }
514
575
  else if (toolName === 'import_memories') {
515
- return handleImportMemories(id, args);
576
+ return handleImportMemoriesFn(memoryStore, memory_cache_1, id, args);
577
+ }
578
+ else if (toolName === 'get_related_memories') {
579
+ return handleGetRelatedMemoriesFn(memoryStore, id, args);
516
580
  }
517
581
  else if (toolName === 'health_check') {
518
582
  return handleHealthCheck(id);
519
583
  }
520
- else if (toolName === 'review_code') {
521
- return handleReviewCode(id, args);
584
+ else if (toolName === 'search_by_file') {
585
+ return handleSearchByFile(id, args);
586
+ }
587
+ else if (toolName === 'pin_memory') {
588
+ return handlePinMemory(id, args);
522
589
  }
523
- else if (toolName === 'pre_check') {
524
- return handlePreCheck(id, args);
590
+ else if (toolName === 'unpin_memory') {
591
+ return handleUnpinMemory(id, args);
525
592
  }
526
- else if (toolName === 'check_impact') {
527
- return handleCheckImpact(id, args);
593
+ else if (toolName === 'undo_last') {
594
+ return handleUndoLast(id, args);
528
595
  }
529
- else if (toolName === 'resume_work') {
530
- return handleResumeWork(id);
596
+ else if (toolName === 'clear_memories') {
597
+ return handleClearMemories(id, args);
598
+ }
599
+ else if (toolName === 'search_timeline') {
600
+ return handleSearchTimeline(id, args);
601
+ }
602
+ else if (toolName === 'thumbs_up') {
603
+ return handleThumbsUp(id, args);
604
+ }
605
+ else if (toolName === 'thumbs_down') {
606
+ return handleThumbsDown(id, args);
607
+ }
608
+ else if (toolName === 'backup_brain') {
609
+ return handleBackupBrain(id);
610
+ }
611
+ else if (toolName === 'get_daily_summary') {
612
+ return handleGetDailySummary(id, args);
613
+ }
614
+ else if (toolName === 'analytics') {
615
+ return handleAnalytics(id);
531
616
  }
532
617
  else {
533
618
  return {
@@ -597,32 +682,10 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
597
682
  ...r,
598
683
  matchMethod: 'hybrid'
599
684
  }));
600
- // 3b. Project-aware boost (memories from current project rank higher)
601
- if (workspaceRoot) {
602
- try {
603
- const pkgPath = require('path').join(workspaceRoot, 'package.json');
604
- let projectTag = '';
605
- if (require('fs').existsSync(pkgPath)) {
606
- const pkg = JSON.parse(require('fs').readFileSync(pkgPath, 'utf-8'));
607
- projectTag = (pkg.name || '').toLowerCase();
608
- }
609
- if (!projectTag)
610
- projectTag = require('path').basename(workspaceRoot).toLowerCase();
611
- if (projectTag) {
612
- ranked = ranked.map(r => {
613
- const tags = r.memory.tags || [];
614
- const hasProjectTag = tags.some(t => t.toLowerCase().includes(projectTag));
615
- const hasDiffProject = tags.some(t => t.startsWith('project:') && !t.toLowerCase().includes(projectTag));
616
- if (hasProjectTag)
617
- return { ...r, score: r.score * 1.3 };
618
- if (hasDiffProject)
619
- return { ...r, score: r.score * 0.7 };
620
- return r;
621
- });
622
- ranked.sort((a, b) => b.score - a.score);
623
- }
624
- }
625
- catch { /* project detection failed — skip boost */ }
685
+ // 3b. Context-based boost (if current file provided)
686
+ if (args.currentFile) {
687
+ // Boost memories related to this file or its directory
688
+ // Implementation in memory-ranker.ts (pending)
626
689
  }
627
690
  // 3c. Apply attention-based re-ranking (debugging→bugs, coding→conventions)
628
691
  const recallContext = (0, attention_ranker_1.detectActionContext)(queryText, currentFile);
@@ -655,14 +718,14 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
655
718
  ranked = ranked.slice(0, maxResults);
656
719
  // 4. Touch for access tracking (reinforcement — used memories get stronger)
657
720
  if (ranked.length > 0) {
658
- memoryStore.runTransaction(() => {
659
- for (const m of ranked) {
660
- try {
661
- memoryStore.touch(m.memory.id);
662
- }
663
- catch { /* non-fatal */ }
721
+ await Promise.all(ranked.map(m => {
722
+ try {
723
+ return memoryStore.touch(m.memory.id);
724
+ }
725
+ catch {
726
+ return Promise.resolve();
664
727
  }
665
- });
728
+ }));
666
729
  (0, confidence_decay_1.runDecayMaintenance)(memoryStore); // Opportunistic decay
667
730
  }
668
731
  (0, memory_cache_1.setCache)(cacheKey, ranked);
@@ -715,7 +778,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
715
778
  }
716
779
  try {
717
780
  // License check — gate memory storage
718
- const activeCount = memoryStore.activeCount();
781
+ const activeCount = memoryStore.getActive(9999).length;
719
782
  const storeCheck = (0, feature_gate_1.canStoreMemory)(activeCount);
720
783
  if (!storeCheck.allowed) {
721
784
  return {
@@ -813,15 +876,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
813
876
  }
814
877
  }
815
878
  function handleGetStats(id) {
816
- const health = (0, usage_stats_1.calculateBrainHealth)(memoryStore);
817
- const lifetime = (0, usage_stats_1.getLifetimeStats)();
818
- const streak = (0, usage_stats_1.getStreakDisplay)();
819
- let llmProvider = 'none';
820
- try {
821
- if ((0, llm_enhancer_1.isLLMAvailable)())
822
- llmProvider = (0, llm_enhancer_1.getLLMProvider)();
823
- }
824
- catch { /* */ }
825
879
  return {
826
880
  jsonrpc: '2.0', id,
827
881
  result: {
@@ -833,16 +887,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
833
887
  totalEvents: eventLog.count(),
834
888
  vectorSearchReady: (0, embedding_manager_1.isWorkerReady)(),
835
889
  cacheSize: (0, memory_cache_1.cacheSize)(),
836
- brainHealth: { score: health.score, grade: health.grade, tips: health.tips },
837
- savedYouCount: lifetime.savedYouCount,
838
- totalSessions: lifetime.totalSessions,
839
- timeSaved: lifetime.totalMemoriesServed * 15 + lifetime.totalHallucationsCaught * 300,
840
- streak: streak || 'Day 1',
841
- longestStreak: lifetime.longestStreak || 0,
842
- totalAutoLearns: lifetime.totalAutoLearns,
843
- successPatternsLearned: lifetime.totalSuccessPatterns || 0,
844
- errorsLearned: lifetime.totalErrorsLearned || 0,
845
- llmProvider,
846
890
  }, null, 2),
847
891
  }],
848
892
  },
@@ -872,46 +916,16 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
872
916
  extraMemories += (0, architecture_graph_1.storeArchitectureGraph)(memoryStore, archGraph);
873
917
  }
874
918
  catch { /* non-fatal */ }
875
- // Convention auto-detection — analyze actual code patterns
876
- try {
877
- const conventions = (0, convention_detector_1.detectConventions)(root);
878
- for (const conv of conventions) {
879
- try {
880
- (0, memory_quality_1.storeWithQuality)(memoryStore, {
881
- type: 'CONVENTION',
882
- intent: conv.pattern,
883
- action: conv.evidence,
884
- reason: `Auto-detected from code (${conv.category}, confidence: ${conv.confidence})`,
885
- confidence: conv.confidence,
886
- importance: conv.confidence,
887
- tags: ['convention', 'auto-detected', conv.category],
888
- });
889
- extraMemories++;
890
- }
891
- catch { /* skip duplicates */ }
892
- }
893
- }
894
- catch { /* non-fatal */ }
895
919
  (0, memory_cache_1.invalidateCache)();
896
- (0, usage_stats_1.trackScan)();
897
920
  const total = count + extraMemories;
898
- // Report knowledge gaps after scan
899
- let gapReport = '';
900
- try {
901
- const gaps = (0, meta_memory_1.detectKnowledgeGaps)(memoryStore, root);
902
- if (gaps.length > 0) {
903
- gapReport = `\n\n${(0, meta_memory_1.formatKnowledgeGaps)(gaps)}`;
904
- }
905
- }
906
- catch { /* non-fatal */ }
907
921
  return {
908
922
  jsonrpc: '2.0', id,
909
923
  result: {
910
924
  content: [{
911
925
  type: 'text',
912
926
  text: total > 0
913
- ? `Project scanned successfully. ${total} memories created (stack, structure, config, git history, export map, architecture graph, coding conventions).${gapReport}`
914
- : `Project was already scanned. No new memories created.${gapReport}`,
927
+ ? `Project scanned successfully. ${total} memories created (stack, structure, config, git history, export map, architecture graph).`
928
+ : 'Project was already scanned. No new memories created.',
915
929
  }],
916
930
  },
917
931
  };
@@ -987,9 +1001,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
987
1001
  };
988
1002
  }
989
1003
  try {
990
- // Track file for relationship mapping
991
- if (args.filename)
992
- (0, file_relationships_1.recordFileEdit)(args.filename);
993
1004
  const result = (0, code_verifier_1.verifyCode)(args.code, root);
994
1005
  const lines = [];
995
1006
  // Imports
@@ -1016,20 +1027,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1016
1027
  for (const [file, available] of Object.entries(result.exports.available)) {
1017
1028
  lines.push(` ${file} exports: ${available.join(', ')}`);
1018
1029
  }
1019
- // Smart fix suggestions — find closest real exports
1020
- try {
1021
- const wsRoot = args.workspaceRoot || workspaceRoot;
1022
- if (wsRoot) {
1023
- const exportMap = (0, export_map_1.buildExportMap)(wsRoot);
1024
- for (const invalid of result.exports.invalid) {
1025
- const suggestions = (0, export_map_2.suggestRealExport)(exportMap, invalid);
1026
- if (suggestions.length > 0) {
1027
- lines.push(` 💡 Did you mean: ${suggestions.slice(0, 3).join(', ')}?`);
1028
- }
1029
- }
1030
- }
1031
- }
1032
- catch { /* non-fatal */ }
1033
1030
  }
1034
1031
  }
1035
1032
  // Env vars
@@ -1048,12 +1045,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1048
1045
  if (lines.length === 0) {
1049
1046
  lines.push('No imports, exports, or env vars detected in the code.');
1050
1047
  }
1051
- // Track hallucination catches for usage stats
1052
- const catchCount = result.imports.invalid.length + result.exports.invalid.length + result.envVars.invalid.length;
1053
- if (catchCount > 0) {
1054
- for (let i = 0; i < catchCount; i++)
1055
- (0, usage_stats_1.trackCatch)();
1056
- }
1057
1048
  return {
1058
1049
  jsonrpc: '2.0', id,
1059
1050
  result: { content: [{ type: 'text', text: lines.join('\n') }] },
@@ -1137,67 +1128,12 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1137
1128
  }
1138
1129
  async function handleForceRecall(id, args) {
1139
1130
  try {
1140
- // Fix #17: Cache force_recall with short TTL to avoid redundant rebuilds
1141
- const cacheKey = `force_recall:${args.topic || ''}:${args.currentFile || ''}`;
1142
- const cached = (0, memory_cache_1.getCached)(cacheKey);
1143
- if (cached) {
1144
- console.log(` [CACHE] force_recall hit`);
1145
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: cached }] } };
1146
- }
1147
1131
  const parts = [];
1148
1132
  // ─── BRAIN LAYER 0: End previous session + start new one ─────────
1149
1133
  (0, session_tracker_1.endSession)(memoryStore); // Save previous session summary
1150
- (0, file_relationships_1.storeRelationships)(memoryStore); // Persist file co-edit relationships
1151
1134
  (0, session_tracker_1.startSession)();
1152
- (0, usage_stats_1.resetSessionStats)();
1153
1135
  if (args.topic)
1154
1136
  (0, session_tracker_1.feedSession)({ topic: args.topic });
1155
- // ─── Project Detection (for project isolation) ───────────────────
1156
- let projectName = '';
1157
- if (workspaceRoot) {
1158
- try {
1159
- const pkgPath = require('path').join(workspaceRoot, 'package.json');
1160
- if (require('fs').existsSync(pkgPath)) {
1161
- const pkg = JSON.parse(require('fs').readFileSync(pkgPath, 'utf-8'));
1162
- projectName = pkg.name || require('path').basename(workspaceRoot);
1163
- }
1164
- else {
1165
- projectName = require('path').basename(workspaceRoot);
1166
- }
1167
- }
1168
- catch {
1169
- projectName = require('path').basename(workspaceRoot || '');
1170
- }
1171
- if (projectName) {
1172
- (0, session_tracker_1.feedSession)({ project: projectName });
1173
- }
1174
- }
1175
- // ─── BRAIN LAYER 0.5: Auto-scan on first run (Day 1 Empty Brain fix) ───
1176
- if (memoryStore.activeCount() === 0 && workspaceRoot) {
1177
- try {
1178
- console.log(` [AUTO-SCAN] First run detected — scanning project...`);
1179
- const scanner = new project_scanner_1.ProjectScanner(memoryStore, workspaceRoot);
1180
- const scanCount = await scanner.scan();
1181
- let extraMemories = 0;
1182
- try {
1183
- extraMemories += (0, export_map_1.storeExportMap)(memoryStore, (0, export_map_1.buildExportMap)(workspaceRoot));
1184
- }
1185
- catch { }
1186
- try {
1187
- extraMemories += (0, architecture_graph_1.storeArchitectureGraph)(memoryStore, (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot));
1188
- }
1189
- catch { }
1190
- const total = scanCount + extraMemories;
1191
- if (total > 0) {
1192
- parts.push(`## Welcome to Cortex\n\nFirst run detected — auto-scanned your project and created ${total} memories (stack, structure, config, git history, exports, architecture). Cortex will now remember everything across sessions.`);
1193
- (0, memory_cache_1.invalidateCache)();
1194
- (0, usage_stats_1.trackScan)();
1195
- }
1196
- }
1197
- catch (scanErr) {
1198
- console.log(` [AUTO-SCAN] Failed: ${scanErr.message}`);
1199
- }
1200
- }
1201
1137
  // ─── BRAIN LAYER 1: Maintenance (runs in background) ─────────────
1202
1138
  try {
1203
1139
  // Decay old unused memories
@@ -1210,6 +1146,15 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1210
1146
  }
1211
1147
  }
1212
1148
  catch { /* maintenance errors are non-fatal */ }
1149
+ // ─── BRAIN LAYER 1.5: PINNED MEMORIES (Critical Rules) ───────────
1150
+ // Scan ALL memories for pinned — critical rules must never be missed
1151
+ const pinnedMemories = memoryStore.getActive(10000).filter(m => (m.tags || []).includes('pinned'));
1152
+ if (pinnedMemories.length > 0) {
1153
+ parts.push('\n## 📌 PINNED CRITICAL RULES (Must Follow)');
1154
+ for (const p of pinnedMemories) {
1155
+ parts.push(`- **[${p.type}]** ${p.intent}`);
1156
+ }
1157
+ }
1213
1158
  // ─── BRAIN LAYER 2: Attention Context ────────────────────────────
1214
1159
  const actionContext = (0, feature_gate_1.isFeatureAllowed)('attentionRanking') ? (0, attention_ranker_1.detectActionContext)(args.topic, args.currentFile) : {};
1215
1160
  const attentionLabel = (0, feature_gate_1.isFeatureAllowed)('attentionRanking') ? (0, attention_ranker_1.formatAttentionContext)(actionContext) : '';
@@ -1233,326 +1178,149 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1233
1178
  maxChars: 8000, // leave room for brain layers
1234
1179
  });
1235
1180
  parts.push(fullContext);
1236
- // ─── BRAIN LAYERS 6-12: Run in parallel (independent of each other) ───
1237
- const parallelResults = await Promise.allSettled([
1238
- // Layer 6: Anticipation
1239
- (async () => {
1240
- if (args.currentFile && (0, feature_gate_1.isFeatureAllowed)('anticipation')) {
1241
- return (0, anticipation_engine_1.formatAnticipation)((0, anticipation_engine_1.anticipate)(memoryStore, args.currentFile));
1242
- }
1243
- return '';
1244
- })(),
1245
- // Layer 6.5: Proactive Warnings — ⚠️ for files with past bugs/corrections
1246
- (async () => {
1247
- if (!args.currentFile)
1248
- return '';
1249
- try {
1250
- const fileMemories = memoryStore.getByFile(args.currentFile, 50);
1251
- const warnings = fileMemories.filter((m) => (m.type === 'CORRECTION' || m.type === 'BUG_FIX') && m.is_active);
1252
- if (warnings.length === 0)
1253
- return '';
1254
- const lines = warnings.slice(0, 5).map((m) => `⚠️ **${m.type}**: ${m.intent}${m.reason && !m.reason.startsWith('Auto-detected') ? ` — _${m.reason}_` : ''}`);
1255
- return `\n## ⚠️ Watch Out (past issues with this file)\n${lines.join('\n')}`;
1256
- }
1257
- catch {
1258
- return '';
1259
- }
1260
- })(),
1261
- // Layer 7: Temporal Context
1262
- (async () => {
1263
- if ((0, feature_gate_1.isFeatureAllowed)('temporalContext')) {
1264
- return (0, temporal_engine_1.formatTemporalContext)(memoryStore);
1265
- }
1266
- return '';
1267
- })(),
1268
- // Layer 8: Workspace State (git)
1269
- (async () => {
1270
- try {
1271
- return (0, temporal_engine_1.getWorkspaceDiff)(workspaceRoot || '');
1272
- }
1273
- catch {
1274
- return '';
1275
- }
1276
- })(),
1277
- // Layer 8.5: Git Memory
1278
- (async () => {
1279
- if (!(0, feature_gate_1.isFeatureAllowed)('gitMemory'))
1280
- return '';
1281
- try {
1282
- const commitsCaptured = (0, git_memory_1.captureGitCommits)(memoryStore, workspaceRoot || '', 5);
1283
- const commitText = commitsCaptured > 0
1284
- ? `\n> Captured ${commitsCaptured} new git commit(s) as memories`
1285
- : '';
1286
- const fileChanges = (0, git_memory_1.detectFileChanges)(workspaceRoot || '');
1287
- const fileChangeText = (0, git_memory_1.formatFileChanges)(fileChanges);
1288
- return [commitText, fileChangeText].filter(Boolean).join('\n');
1289
- }
1290
- catch {
1291
- return '';
1181
+ // ─── BRAIN LAYER 6: Anticipation (file-aware proactive recall) ───
1182
+ if (args.currentFile && (0, feature_gate_1.isFeatureAllowed)('anticipation')) {
1183
+ const anticipated = (0, anticipation_engine_1.formatAnticipation)((0, anticipation_engine_1.anticipate)(memoryStore, args.currentFile));
1184
+ if (anticipated)
1185
+ parts.push('\n' + anticipated);
1186
+ }
1187
+ // ─── BRAIN LAYER 7: Temporal Context (what changed recently) ─────
1188
+ if ((0, feature_gate_1.isFeatureAllowed)('temporalContext')) {
1189
+ const temporal = (0, temporal_engine_1.formatTemporalContext)(memoryStore);
1190
+ if (temporal)
1191
+ parts.push('\n' + temporal);
1192
+ }
1193
+ // ─── BRAIN LAYER 8: Workspace State (git changes) ────────────────
1194
+ try {
1195
+ const workspace = (0, temporal_engine_1.getWorkspaceDiff)(workspaceRoot || '');
1196
+ if (workspace)
1197
+ parts.push('\n' + workspace);
1198
+ }
1199
+ catch { /* git not available */ }
1200
+ // ─── BRAIN LAYER 8.5: Git Memory (commit capture + file changes) ───
1201
+ if ((0, feature_gate_1.isFeatureAllowed)('gitMemory')) {
1202
+ try {
1203
+ // Capture recent commits as memories
1204
+ const commitsCaptured = (0, git_memory_1.captureGitCommits)(memoryStore, workspaceRoot || '', 5);
1205
+ if (commitsCaptured > 0) {
1206
+ parts.push(`\n> Captured ${commitsCaptured} new git commit(s) as memories`);
1292
1207
  }
1293
- })(),
1294
- // Layer 9: Topic Search — Hybrid (FTS + Vector) for deeper relevance
1295
- (async () => {
1296
- if (!args.topic)
1297
- return '';
1298
- try {
1299
- // FTS search
1300
- const ftsResults = memoryStore.searchFTS(args.topic, 15);
1301
- // Vector search (semantic catches what FTS misses)
1302
- let vectorResults = [];
1303
- if ((0, embedding_manager_1.isWorkerReady)()) {
1304
- try {
1305
- const topicEmbedding = await (0, embedding_manager_1.embedText)(args.topic);
1306
- vectorResults = memoryStore.searchVector(new Float32Array(topicEmbedding), 10);
1307
- }
1308
- catch { /* vector search failure is non-fatal */ }
1309
- }
1310
- // Merge FTS + Vector, deduplicate by ID
1311
- const merged = (0, memory_ranker_1.rankResults)(ftsResults, vectorResults, 20, args.currentFile);
1312
- let ranked = merged.map(r => ({ ...r, matchMethod: 'hybrid' }));
1313
- ranked = (0, confidence_decay_1.applyConfidenceDecay)(ranked);
1314
- ranked = (0, attention_ranker_1.rankByAttention)(ranked, actionContext);
1315
- const seen = new Set();
1316
- const enriched = [];
1317
- for (const r of ranked) {
1318
- if (seen.has(r.memory.id))
1319
- continue;
1320
- seen.add(r.memory.id);
1321
- enriched.push(r);
1322
- try {
1323
- const related = memoryStore.getRelated(r.memory.id, 1, 3);
1324
- for (const rel of related) {
1325
- if (!seen.has(rel.memory.id)) {
1326
- seen.add(rel.memory.id);
1327
- enriched.push({ ...rel, score: rel.score * 0.7 });
1328
- }
1208
+ // Show uncommitted file changes
1209
+ const fileChanges = (0, git_memory_1.detectFileChanges)(workspaceRoot || '');
1210
+ const fileChangeText = (0, git_memory_1.formatFileChanges)(fileChanges);
1211
+ if (fileChangeText)
1212
+ parts.push('\n' + fileChangeText);
1213
+ }
1214
+ catch { /* git not available */ }
1215
+ } // end isPro() for git memory
1216
+ // ─── BRAIN LAYER 9: Topic-Specific Search ────────────────────────
1217
+ if (args.topic) {
1218
+ try {
1219
+ let ftsResults = memoryStore.searchFTS(args.topic, 15);
1220
+ // Apply confidence decay + attention ranking
1221
+ ftsResults = (0, confidence_decay_1.applyConfidenceDecay)(ftsResults);
1222
+ ftsResults = (0, attention_ranker_1.rankByAttention)(ftsResults, actionContext);
1223
+ // Causal chain: follow graph edges for top results
1224
+ const seen = new Set();
1225
+ const enriched = [];
1226
+ for (const r of ftsResults) {
1227
+ if (seen.has(r.memory.id))
1228
+ continue;
1229
+ seen.add(r.memory.id);
1230
+ enriched.push(r);
1231
+ // Follow causal links (1 hop)
1232
+ try {
1233
+ const related = memoryStore.getRelated(r.memory.id, 1, 3);
1234
+ for (const rel of related) {
1235
+ if (!seen.has(rel.memory.id)) {
1236
+ seen.add(rel.memory.id);
1237
+ enriched.push({ ...rel, score: rel.score * 0.7 });
1329
1238
  }
1330
1239
  }
1331
- catch { }
1332
1240
  }
1333
- if (enriched.length > 0) {
1334
- const lines = ['\n## Topic: "' + args.topic + '"'];
1335
- for (const m of enriched.slice(0, 15)) {
1336
- lines.push(`- [${m.memory.type}] ${m.memory.intent}${m.memory.reason ? ` — ${m.memory.reason}` : ''}`);
1337
- }
1338
- return lines.join('\n');
1339
- }
1340
- return '';
1341
- }
1342
- catch {
1343
- return '\n> Note: Topic search unavailable (FTS index needs rebuild).';
1344
- }
1345
- })(),
1346
- // Layer 10: Knowledge Gaps (skip when topic is specific — saves tokens)
1347
- (async () => {
1348
- if (args.topic)
1349
- return ''; // Skip heavy layer for focused queries
1350
- try {
1351
- const gaps = (0, meta_memory_1.detectKnowledgeGaps)(memoryStore, workspaceRoot || '');
1352
- return (0, meta_memory_1.formatKnowledgeGaps)(gaps);
1353
- }
1354
- catch {
1355
- return '';
1241
+ catch { }
1356
1242
  }
1357
- })(),
1358
- // Layer 11: Export Map (skip when topic is specific — saves tokens)
1359
- (async () => {
1360
- if (args.topic)
1361
- return ''; // Skip heavy layer for focused queries
1362
- if (!workspaceRoot || !(0, feature_gate_1.isFeatureAllowed)('exportMap'))
1363
- return '';
1364
- try {
1365
- const exportMap = (0, export_map_1.buildExportMap)(workspaceRoot);
1366
- return exportMap.totalExports > 0 ? (0, export_map_1.formatExportMap)(exportMap) : '';
1367
- }
1368
- catch {
1369
- return '';
1370
- }
1371
- })(),
1372
- // Layer 12: Architecture Graph (skip when topic is specific — saves tokens)
1373
- (async () => {
1374
- if (args.topic)
1375
- return ''; // Skip heavy layer for focused queries
1376
- if (!workspaceRoot || !(0, feature_gate_1.isFeatureAllowed)('architectureGraph'))
1377
- return '';
1378
- try {
1379
- const archGraph = (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot);
1380
- return archGraph.totalFiles > 0 ? (0, architecture_graph_1.formatArchitectureGraph)(archGraph) : '';
1381
- }
1382
- catch {
1383
- return '';
1243
+ if (enriched.length > 0) {
1244
+ parts.push('\n## Topic: "' + args.topic + '"');
1245
+ for (const m of enriched.slice(0, 15)) {
1246
+ parts.push(`- [${m.memory.type}] ${m.memory.intent}${m.memory.reason ? ` — ${m.memory.reason}` : ''}`);
1247
+ }
1384
1248
  }
1385
- })(),
1386
- ]);
1387
- // Collect results (in order) — only push non-empty strings
1388
- for (const result of parallelResults) {
1389
- if (result.status === 'fulfilled' && result.value) {
1390
- parts.push('\n' + result.value);
1391
1249
  }
1392
- }
1393
- // ─── SMART CONTEXT SELECTION: Priority-based trimming ────────────
1394
- // Instead of dumb slicing, trim lowest-priority sections first
1395
- const MAX_CHARS = 12000; // ~3000 tokens — fits any model
1396
- let output = parts.join('\n');
1397
- if (output.length > MAX_CHARS) {
1398
- // Priority: highest first (kept), lowest first (trimmed)
1399
- // Layers 0-5 are high priority (welcome, sessions, corrections, core context)
1400
- // Layers 6-12 are lower priority (anticipation, temporal, git, topic, gaps, exports, arch)
1401
- // Trim from end (lowest priority) working backwards
1402
- while (output.length > MAX_CHARS && parts.length > 4) {
1403
- parts.pop(); // Remove lowest-priority section
1404
- output = parts.join('\n');
1405
- }
1406
- if (output.length > MAX_CHARS) {
1407
- output = output.slice(0, MAX_CHARS);
1250
+ catch {
1251
+ parts.push('\n> Note: Topic search unavailable (FTS index needs rebuild).');
1408
1252
  }
1409
- output += '\n\n> (Some context trimmed to fit token budget. Use `recall_memory` for specific queries.)';
1410
- }
1411
- // Track usage stats
1412
- const memoriesInOutput = (output.match(/\[(?:CORRECTION|DECISION|CONVENTION|BUG_FIX|INSIGHT)\]/g) || []).length;
1413
- (0, usage_stats_1.trackRecall)(memoriesInOutput);
1414
- // Inject contextual instructions (DO/DON'T/WATCH-OUT)
1415
- try {
1416
- const instructions = (0, instructions_generator_1.generateInstructions)(memoryStore);
1417
- const instructionText = (0, instructions_generator_1.formatInstructions)(instructions);
1418
- if (instructionText)
1419
- output += '\n\n' + instructionText;
1420
1253
  }
1421
- catch { /* non-fatal */ }
1422
- // Inject tool recommendations (what to use based on context)
1423
- try {
1424
- const recs = (0, tool_recommender_1.recommendTools)({
1425
- topic: args.topic,
1426
- currentFile: args.currentFile,
1427
- isNewConversation: true,
1428
- });
1429
- const recText = (0, tool_recommender_1.formatRecommendations)(recs);
1430
- if (recText)
1431
- output += '\n\n' + recText;
1432
- }
1433
- catch { /* non-fatal */ }
1434
- // Inject user preferences (adapts AI behavior)
1254
+ // ─── BRAIN LAYER 10: Knowledge Gaps (meta-memory) ────────────────
1435
1255
  try {
1436
- const prefText = (0, preference_learner_1.getStoredPreferences)(memoryStore);
1437
- if (prefText)
1438
- output += '\n\n' + prefText;
1256
+ const gaps = (0, meta_memory_1.detectKnowledgeGaps)(memoryStore, workspaceRoot || '');
1257
+ const gapText = (0, meta_memory_1.formatKnowledgeGaps)(gaps);
1258
+ if (gapText)
1259
+ parts.push('\n' + gapText);
1439
1260
  }
1440
1261
  catch { /* non-fatal */ }
1441
- // Append stats footer (makes value visible THE KEY TO ADDICTION)
1442
- const statsFooter = (0, usage_stats_1.formatStatsFooter)(memoryStore);
1443
- if (statsFooter)
1444
- output += statsFooter;
1445
- // Count corrections recalled as "saved you" moments
1446
- const correctionsRecalled = (output.match(/\[CORRECTION\]/g) || []).length;
1447
- for (let i = 0; i < correctionsRecalled; i++)
1448
- (0, usage_stats_1.trackSaved)();
1449
- // Cache the result for short-lived reuse
1450
- (0, memory_cache_1.setCache)(cacheKey, output);
1451
- return {
1452
- jsonrpc: '2.0', id,
1453
- result: { content: [{ type: 'text', text: output }] },
1454
- };
1455
- }
1456
- catch (err) {
1457
- return {
1458
- jsonrpc: '2.0', id,
1459
- result: { content: [{ type: 'text', text: `Force recall error: ${err.message}` }], isError: true },
1460
- };
1461
- }
1462
- }
1463
- // ─── REVIEW CODE: Check against conventions + past bugs ─────────────
1464
- function handleReviewCode(id, args) {
1465
- try {
1466
- const code = args.code;
1467
- const filename = args.filename || '';
1468
- if (!code || code.length < 10) {
1469
- return {
1470
- jsonrpc: '2.0', id,
1471
- result: { content: [{ type: 'text', text: 'Error: provide code to review (min 10 chars)' }], isError: true },
1472
- };
1473
- }
1474
- const violations = [];
1475
- const suggestions = [];
1476
- const codeLower = code.toLowerCase();
1477
- // Check against stored CONVENTIONS
1478
- const conventions = memoryStore.getByType('CONVENTION', 100);
1479
- for (const conv of conventions) {
1480
- const intentLower = conv.intent.toLowerCase();
1481
- // Check for common pattern violations
1482
- if (intentLower.includes('never use') || intentLower.includes("don't use") || intentLower.includes('avoid')) {
1483
- // Extract the forbidden thing
1484
- const match = intentLower.match(/(?:never use|don't use|avoid)\s+(\w+(?:\s+\w+)?)/i);
1485
- if (match) {
1486
- const forbidden = match[1].toLowerCase();
1487
- if (codeLower.includes(forbidden)) {
1488
- violations.push(`⚠️ **Convention Violation** \`id:${conv.id}\`: "${conv.intent}" — Found \`${forbidden}\` in your code`);
1489
- }
1490
- }
1491
- }
1492
- if (intentLower.includes('always use') || intentLower.includes('must use')) {
1493
- const match = intentLower.match(/(?:always use|must use)\s+(\w+(?:\s+\w+)?)/i);
1494
- if (match) {
1495
- const required = match[1].toLowerCase();
1496
- if (!codeLower.includes(required) && code.length > 50) {
1497
- suggestions.push(`💡 **Convention Suggestion** \`id:${conv.id}\`: "${conv.intent}" — Consider using \`${required}\``);
1498
- }
1262
+ // ─── BRAIN LAYER 11: Export Map (anti-hallucination) ──────────────
1263
+ if (workspaceRoot && (0, feature_gate_1.isFeatureAllowed)('exportMap')) {
1264
+ try {
1265
+ const exportMap = (0, export_map_1.buildExportMap)(workspaceRoot);
1266
+ if (exportMap.totalExports > 0) {
1267
+ const exportText = (0, export_map_1.formatExportMap)(exportMap);
1268
+ if (exportText)
1269
+ parts.push('\n' + exportText);
1499
1270
  }
1500
1271
  }
1272
+ catch { /* non-fatal */ }
1501
1273
  }
1502
- // Check against stored BUG_FIX patterns
1503
- const bugFixes = memoryStore.getByType('BUG_FIX', 50);
1504
- for (const bug of bugFixes) {
1505
- const bugLower = bug.intent.toLowerCase();
1506
- // Extract key terms from bug description
1507
- const bugTerms = bugLower.split(/\s+/).filter(w => w.length > 4);
1508
- const matchCount = bugTerms.filter(t => codeLower.includes(t)).length;
1509
- if (matchCount >= 3) {
1510
- violations.push(`🐛 **Similar Bug Pattern** \`id:${bug.id}\`: "${bug.intent}" — This code has similarities to a past bug`);
1511
- }
1512
- }
1513
- // Check for CORRECTION patterns
1514
- const corrections = memoryStore.getByType('CORRECTION', 50);
1515
- for (const corr of corrections) {
1516
- const corrLower = corr.intent.toLowerCase();
1517
- const corrTerms = corrLower.split(/\s+/).filter(w => w.length > 4);
1518
- const matchCount = corrTerms.filter(t => codeLower.includes(t)).length;
1519
- if (matchCount >= 3) {
1520
- violations.push(`🔄 **Past Correction Applies** \`id:${corr.id}\`: "${corr.intent}"`);
1274
+ // ─── BRAIN LAYER 12: Architecture Graph (deep understanding) ──────
1275
+ if (workspaceRoot && (0, feature_gate_1.isFeatureAllowed)('architectureGraph')) {
1276
+ try {
1277
+ const archGraph = (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot);
1278
+ if (archGraph.totalFiles > 0) {
1279
+ const archText = (0, architecture_graph_1.formatArchitectureGraph)(archGraph);
1280
+ if (archText)
1281
+ parts.push('\n' + archText);
1282
+ }
1521
1283
  }
1284
+ catch { /* non-fatal */ }
1522
1285
  }
1523
- // File-specific memories
1524
- if (filename) {
1525
- const fileMemories = memoryStore.getByFile(filename, 10);
1526
- for (const fm of fileMemories) {
1527
- suggestions.push(`📄 **File Note** \`id:${fm.id}\`: "${fm.intent}"`);
1286
+ // ─── SMART CONTEXT SELECTION: Trim to token budget ───────────────
1287
+ // ─── SMART CONTEXT TRIMMING: Remove low-priority sections first ─
1288
+ const MAX_CHARS = 12000; // ~3000 tokens — fits any model
1289
+ let output;
1290
+ if (parts.join('\n').length > MAX_CHARS) {
1291
+ // Priority order: keep critical layers, trim optional ones from the end
1292
+ // Parts are added in priority order (sessions, corrections, core context first)
1293
+ // So we trim from the END (architecture graph, export map, knowledge gaps)
1294
+ let trimmedParts = [...parts];
1295
+ let totalLen = trimmedParts.join('\n').length;
1296
+ while (totalLen > MAX_CHARS && trimmedParts.length > 3) {
1297
+ trimmedParts.pop(); // Remove lowest priority section
1298
+ totalLen = trimmedParts.join('\n').length;
1299
+ }
1300
+ output = trimmedParts.join('\n');
1301
+ if (output.length > MAX_CHARS) {
1302
+ output = output.slice(0, MAX_CHARS);
1528
1303
  }
1304
+ output += '\n\n> (Lower-priority context removed to fit token budget. Use `recall_memory` for specific queries.)';
1305
+ } else {
1306
+ output = parts.join('\n');
1529
1307
  }
1530
- (0, usage_stats_1.trackReview)();
1531
- const lines = ['# Cortex Code Review\n'];
1532
- if (violations.length > 0) {
1533
- lines.push(`## ⚠️ ${violations.length} Issue${violations.length > 1 ? 's' : ''} Found\n`);
1534
- violations.forEach(v => lines.push(v));
1535
- }
1536
- if (suggestions.length > 0) {
1537
- lines.push(`\n## 💡 ${suggestions.length} Suggestion${suggestions.length > 1 ? 's' : ''}\n`);
1538
- suggestions.forEach(s => lines.push(s));
1539
- }
1540
- if (violations.length === 0 && suggestions.length === 0) {
1541
- lines.push('✅ **No issues found.** Code looks clean against your stored conventions and past bugs.');
1542
- lines.push(`\n_Checked against ${conventions.length} conventions, ${bugFixes.length} bug fixes, ${corrections.length} corrections._`);
1543
- }
1544
- else {
1545
- lines.push(`\n_Reviewed against ${conventions.length} conventions, ${bugFixes.length} bug fixes, ${corrections.length} corrections._`);
1308
+ // ─── DAY 1 EMPTY BRAIN: Guide new users ──────────────────────────
1309
+ if (memoryStore.activeCount() === 0) {
1310
+ return {
1311
+ jsonrpc: '2.0', id,
1312
+ result: { content: [{ type: 'text', text: '## 🧠 Cortex Brain — Day 1\n\nNo memories yet. Here is how to build your brain:\n\n1. **Scan this project** — call `scan_project` with the workspace root. This creates memories from your package.json, README, git history, and code structure.\n2. **Just work normally** — after every response, `auto_learn` will automatically extract and store decisions, corrections, and bug fixes.\n3. **Store manually** — use `store_memory` or `quick_store` for important rules.\n\nOnce you have memories, `force_recall` will inject them here automatically.' }] },
1313
+ };
1546
1314
  }
1547
1315
  return {
1548
1316
  jsonrpc: '2.0', id,
1549
- result: { content: [{ type: 'text', text: lines.join('\n') }] },
1317
+ result: { content: [{ type: 'text', text: output }] },
1550
1318
  };
1551
1319
  }
1552
1320
  catch (err) {
1553
1321
  return {
1554
1322
  jsonrpc: '2.0', id,
1555
- result: { content: [{ type: 'text', text: `Review error: ${err.message}` }], isError: true },
1323
+ result: { content: [{ type: 'text', text: `Force recall error: ${err.message}` }], isError: true },
1556
1324
  };
1557
1325
  }
1558
1326
  }
@@ -1696,7 +1464,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1696
1464
  }
1697
1465
  async function handleAutoLearn(id, args) {
1698
1466
  try {
1699
- // Feature gate (launch mode: all features unlocked)
1467
+ // Feature gate check (auto_learn is now free!)
1700
1468
  if (!(0, feature_gate_1.isFeatureAllowed)('autoLearn')) {
1701
1469
  return {
1702
1470
  jsonrpc: '2.0', id,
@@ -1720,50 +1488,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1720
1488
  }
1721
1489
  // Extract memory-worthy patterns (regex-based)
1722
1490
  const extracted = (0, auto_learner_1.extractMemories)(text);
1723
- (0, usage_stats_1.trackAutoLearn)(); // Track for Brain Health Score + Streak
1724
- // SUCCESS DETECTION: capture proven approaches
1725
- try {
1726
- const successSignals = (0, success_tracker_1.detectSuccess)(text);
1727
- for (const signal of successSignals) {
1728
- const successMemory = (0, success_tracker_1.buildSuccessMemory)(signal, text);
1729
- extracted.push({
1730
- type: 'INSIGHT',
1731
- content: successMemory.intent,
1732
- confidence: signal.confidence,
1733
- reason: successMemory.reason,
1734
- });
1735
- (0, usage_stats_1.trackSuccess)();
1736
- }
1737
- }
1738
- catch { /* success detection failed — non-fatal */ }
1739
- // ERROR FINGERPRINT: capture error patterns for instant recall
1740
- try {
1741
- if ((0, error_learner_1.containsErrors)(text)) {
1742
- const errorPatterns = (0, error_learner_1.extractErrorPatterns)(text);
1743
- for (const ep of errorPatterns.slice(0, 3)) {
1744
- extracted.push({
1745
- type: 'BUG_FIX',
1746
- content: ep.message,
1747
- confidence: ep.confidence,
1748
- reason: `Error pattern: ${ep.errorType} — auto-captured for instant fix recall`,
1749
- });
1750
- (0, usage_stats_1.trackErrorLearned)();
1751
- }
1752
- }
1753
- }
1754
- catch { /* error learning failed — non-fatal */ }
1755
- // COMPLETION DETECTION: demote old memories about completed topics
1756
- try {
1757
- const completionSignals = (0, completion_resolver_1.detectCompletion)(text);
1758
- for (const signal of completionSignals) {
1759
- const resolved = (0, completion_resolver_1.resolveRelatedMemories)(memoryStore, signal.topic, signal.confidence);
1760
- if (resolved > 0) {
1761
- console.log(` 🏁 Completion: "${signal.topic}" — demoted ${resolved} old memories`);
1762
- (0, memory_cache_1.invalidateCache)();
1763
- }
1764
- }
1765
- }
1766
- catch { /* completion detection failed — non-fatal */ }
1767
1491
  // LLM enhancement: when API key is available and regex found nothing,
1768
1492
  // use LLM to catch implicit patterns that keywords miss
1769
1493
  if (extracted.length === 0 && (0, llm_enhancer_1.isLLMAvailable)() && text.length > 50) {
@@ -1791,62 +1515,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1791
1515
  };
1792
1516
  }
1793
1517
  // Store each extracted memory + feed session tracker
1794
- // --- Helper: Independent importance scoring ---
1795
- function calculateImportance(item) {
1796
- // Base importance by type (corrections/bugs are more important than insights)
1797
- const TYPE_IMPORTANCE = {
1798
- CORRECTION: 0.85, BUG_FIX: 0.85, CONVENTION: 0.80,
1799
- DECISION: 0.75, GOTCHA: 0.75, BUSINESS_RULE: 0.70,
1800
- FAILED_ATTEMPT: 0.65, CURRENT_TASK: 0.60, INSIGHT: 0.55,
1801
- };
1802
- let importance = TYPE_IMPORTANCE[item.type] || 0.60;
1803
- // Boost for content signals that indicate higher value
1804
- if (/\b(always|never|must|critical|important|breaking)\b/i.test(item.content))
1805
- importance += 0.08;
1806
- if (/\b(error|bug|crash|fail|exception)\b/i.test(item.content))
1807
- importance += 0.05;
1808
- if (/\.(ts|js|py|go|rs|java|tsx|jsx)\b/.test(item.content))
1809
- importance += 0.03; // file-specific
1810
- if (/v?\d+\.\d+/.test(item.content))
1811
- importance += 0.02; // version numbers
1812
- // Blend with regex confidence (40% type-based, 60% regex confidence)
1813
- importance = importance * 0.4 + item.confidence * 0.6;
1814
- return Math.min(importance, 1.0);
1815
- }
1816
- // --- Helper: Extract topic tags from content ---
1817
- function extractTopicTags(item) {
1818
- const tags = [item.type.toLowerCase()];
1819
- const content = item.content.toLowerCase();
1820
- // Extract technology/framework mentions
1821
- const techPatterns = /\b(react|vue|angular|next\.?js|node|express|typescript|javascript|python|rust|go|docker|kubernetes|postgres|mongodb|redis|graphql|rest|api|css|html|webpack|vite|eslint|git|npm|yarn)\b/gi;
1822
- const techMatches = item.content.match(techPatterns);
1823
- if (techMatches) {
1824
- for (const tech of new Set(techMatches.map(t => t.toLowerCase()))) {
1825
- tags.push(tech);
1826
- }
1827
- }
1828
- // Extract file extensions as topic hints
1829
- const fileExts = content.match(/\.(ts|js|py|go|rs|java|tsx|jsx|css|html|json|yaml|yml|md)\b/g);
1830
- if (fileExts) {
1831
- for (const ext of new Set(fileExts)) {
1832
- tags.push(ext.replace('.', ''));
1833
- }
1834
- }
1835
- // Extract key action verbs as context
1836
- if (/\b(migrat|switch|chang|replac|upgrad|delet|remov)\w*/i.test(content))
1837
- tags.push('migration');
1838
- if (/\b(test|spec|assert|expect|mock)\b/i.test(content))
1839
- tags.push('testing');
1840
- if (/\b(deploy|ci|cd|pipeline|build|release)\b/i.test(content))
1841
- tags.push('devops');
1842
- if (/\b(auth|login|token|session|permission|role)\b/i.test(content))
1843
- tags.push('auth');
1844
- if (/\b(database|query|schema|table|index|sql)\b/i.test(content))
1845
- tags.push('database');
1846
- if (/\b(performance|speed|slow|fast|optimize|cache)\b/i.test(content))
1847
- tags.push('performance');
1848
- return [...new Set(tags)].slice(0, 8); // Cap at 8 tags
1849
- }
1850
1518
  const stored = [];
1851
1519
  const skipped = [];
1852
1520
  for (const item of extracted) {
@@ -1872,16 +1540,30 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1872
1540
  (0, session_tracker_1.feedSession)({ topic: item.content.slice(0, 60) });
1873
1541
  break;
1874
1542
  }
1543
+ // Map auto_learn string types to MemoryType enum values
1544
+ // (auto_learner returns uppercase like "DECISION", but MemoryType enum uses lowercase like "decision")
1545
+ const autoLearnTypeMap = {
1546
+ 'DECISION': types_1.MemoryType.DECISION || 'decision',
1547
+ 'CORRECTION': types_1.MemoryType.CORRECTION || 'correction',
1548
+ 'BUG_FIX': types_1.MemoryType.BUG_FIX || 'bug_fix',
1549
+ 'CONVENTION': types_1.MemoryType.CONVENTION || 'convention',
1550
+ 'INSIGHT': types_1.MemoryType.INSIGHT || 'insight',
1551
+ 'FAILED_ATTEMPT': types_1.MemoryType.FAILED_SUGGESTION || 'failed_suggestion',
1552
+ 'BUSINESS_RULE': types_1.MemoryType.DECISION || 'decision',
1553
+ 'GOTCHA': types_1.MemoryType.CORRECTION || 'correction',
1554
+ 'CURRENT_TASK': types_1.MemoryType.INSIGHT || 'insight',
1555
+ };
1556
+ const mappedType = autoLearnTypeMap[item.type] || item.type.toLowerCase();
1875
1557
  const result = (0, memory_quality_1.storeWithQuality)(memoryStore, {
1876
- type: item.type,
1558
+ type: mappedType,
1877
1559
  intent: item.content,
1878
- action: `auto_learn:${item.type.toLowerCase()}`,
1560
+ action: item.content,
1879
1561
  reason: item.reason,
1880
1562
  confidence: item.confidence,
1881
- importance: calculateImportance(item),
1882
- tags: extractTopicTags(item),
1563
+ importance: item.confidence,
1564
+ tags: [item.type.toLowerCase()],
1883
1565
  });
1884
- if (result.stored) {
1566
+ if (result) {
1885
1567
  stored.push(`[${item.type}] ${item.content.slice(0, 60)}${item.content.length > 60 ? '…' : ''}`);
1886
1568
  }
1887
1569
  else {
@@ -1894,137 +1576,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1894
1576
  }
1895
1577
  if (stored.length > 0) {
1896
1578
  (0, memory_cache_1.invalidateCache)();
1897
- for (let i = 0; i < stored.length; i++)
1898
- (0, usage_stats_1.trackStore)();
1899
- }
1900
- // ─── Auto-correction capture ─────────────────────────────────────
1901
- // Scan AI text for self-corrections ("I apologize", "you're right")
1902
- const aiCorrections = (0, correction_detector_1.detectAIAcknowledgments)(text);
1903
- for (const corr of aiCorrections) {
1904
- try {
1905
- const corrResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
1906
- type: 'CORRECTION',
1907
- intent: `[AUTO-DETECTED] ${corr.fullContext}`,
1908
- action: corr.fullContext,
1909
- reason: `AI self-correction detected (confidence: ${corr.confidence})`,
1910
- confidence: corr.confidence,
1911
- importance: 0.90, // High importance — corrections prevent repeats
1912
- tags: ['auto-correction', 'ai-acknowledgment'],
1913
- });
1914
- if (corrResult.stored) {
1915
- stored.push(`[CORRECTION] ${corr.fullContext.slice(0, 60)}…`);
1916
- }
1917
- }
1918
- catch { /* skip */ }
1919
- }
1920
- // Scan user context for direct corrections ("no, use X not Y")
1921
- const userContext = args.context;
1922
- if (userContext && userContext.length > 10) {
1923
- const userCorrections = (0, correction_detector_1.detectUserCorrections)(userContext);
1924
- for (const corr of userCorrections) {
1925
- try {
1926
- const content = corr.corrected
1927
- ? `User correction: use "${corr.corrected}"${corr.original ? ` instead of "${corr.original}"` : ''}`
1928
- : `User correction: ${corr.fullContext}`;
1929
- const corrResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
1930
- type: 'CORRECTION',
1931
- intent: content,
1932
- action: corr.fullContext,
1933
- reason: `User correction detected (confidence: ${corr.confidence})`,
1934
- confidence: corr.confidence,
1935
- importance: 0.95, // Very high — user corrections are gospel
1936
- tags: ['auto-correction', 'user-correction'],
1937
- });
1938
- if (corrResult.stored) {
1939
- stored.push(`[USER CORRECTION] ${content.slice(0, 60)}…`);
1940
- (0, memory_cache_1.invalidateCache)();
1941
- }
1942
- }
1943
- catch { /* skip */ }
1944
- }
1945
- // ─── Preference learning ─────────────────────────────────
1946
- const prefs = (0, preference_learner_1.detectPreferences)(userContext);
1947
- for (const pref of prefs) {
1948
- try {
1949
- const prefResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
1950
- type: 'CONVENTION',
1951
- intent: pref.preference,
1952
- action: `Detected from: "${pref.evidence}"`,
1953
- reason: `User preference auto-detected (${pref.category})`,
1954
- confidence: pref.confidence,
1955
- importance: 0.85,
1956
- tags: ['preference', pref.category],
1957
- });
1958
- if (prefResult.stored) {
1959
- stored.push(`[PREFERENCE] ${pref.preference.slice(0, 60)}…`);
1960
- (0, memory_cache_1.invalidateCache)();
1961
- }
1962
- }
1963
- catch { /* skip */ }
1964
- }
1965
- }
1966
- // ─── Build error learning ──────────────────────────────────
1967
- // Scan for TS errors, test failures in AI text
1968
- if ((0, error_learner_1.containsErrors)(text)) {
1969
- const errorPatterns = (0, error_learner_1.extractErrorPatterns)(text);
1970
- // Extract verification steps for regression prevention
1971
- const verifySteps = (0, regression_guard_1.extractVerificationSteps)(text);
1972
- for (const ep of errorPatterns) {
1973
- try {
1974
- const baseAction = `Auto-captured from build/test output`;
1975
- const actionWithVerify = (0, regression_guard_1.attachVerification)(baseAction, verifySteps);
1976
- const errResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
1977
- type: 'BUG_FIX',
1978
- intent: `[ERROR PATTERN] ${ep.errorType}: ${ep.message}`,
1979
- action: actionWithVerify,
1980
- reason: `Error pattern auto-detected — avoid this in future`,
1981
- confidence: ep.confidence,
1982
- importance: 0.90,
1983
- tags: ['error-pattern', ep.errorType.toLowerCase(), 'auto-detected'],
1984
- });
1985
- if (errResult.stored) {
1986
- stored.push(`[ERROR LEARNED] ${ep.errorType}: ${ep.message.slice(0, 50)}…`);
1987
- (0, memory_cache_1.invalidateCache)();
1988
- }
1989
- }
1990
- catch { /* skip */ }
1991
- }
1992
- }
1993
- // ─── Success reinforcement ─────────────────────────────────
1994
- // Scan user context for praise/success signals
1995
- if (userContext && userContext.length > 5) {
1996
- const successSignals = (0, success_tracker_1.detectSuccess)(userContext);
1997
- for (const signal of successSignals) {
1998
- try {
1999
- const mem = (0, success_tracker_1.buildSuccessMemory)(signal, text);
2000
- const successResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
2001
- type: 'INSIGHT',
2002
- intent: mem.intent,
2003
- action: text.slice(0, 200),
2004
- reason: mem.reason,
2005
- confidence: signal.confidence,
2006
- importance: 0.80,
2007
- tags: mem.tags,
2008
- });
2009
- if (successResult.stored) {
2010
- stored.push(`[SUCCESS] Proven approach stored from: "${signal.trigger}"`);
2011
- (0, memory_cache_1.invalidateCache)();
2012
- }
2013
- }
2014
- catch { /* skip */ }
2015
- }
2016
- }
2017
- // ─── File relationship tracking ────────────────────────────
2018
- // Track files mentioned in context for co-edit detection
2019
- if (userContext) {
2020
- const filePatterns = userContext.match(/[\w-]+\.\w{1,5}/g) || [];
2021
- const codeFileExts = new Set(['ts', 'tsx', 'js', 'jsx', 'css', 'py', 'go', 'rs', 'java']);
2022
- for (const f of filePatterns) {
2023
- const ext = f.split('.').pop()?.toLowerCase() || '';
2024
- if (codeFileExts.has(ext)) {
2025
- (0, file_relationships_1.recordFileEdit)(f);
2026
- }
2027
- }
2028
1579
  }
2029
1580
  const lines = ['**Auto-Learn Results:**'];
2030
1581
  if (stored.length > 0) {
@@ -2107,12 +1658,14 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
2107
1658
  '# Cortex Health Check\n',
2108
1659
  `| Metric | Value |`,
2109
1660
  `|--------|-------|`,
1661
+ `| Plan | **Full Brain — 100% Free** |`,
2110
1662
  `| Active Memories | ${activeCount} |`,
2111
- `| Session Store Count | ${stats.storeCount}/100 |`,
2112
- `| Session Auto-Learn Count | ${stats.autoLearnCount}/500 |`,
2113
- `| Session Total Calls | ${stats.totalCalls}/2000 |`,
1663
+ `| Brain Layers | All 12 Active |`,
1664
+ `| Session Store Count | ${stats.storeCount}/500 |`,
1665
+ `| Session Auto-Learn Count | ${stats.autoLearnCount}/1000 |`,
1666
+ `| Session Total Calls | ${stats.totalCalls}/5000 |`,
2114
1667
  `| Uptime | ${Math.floor(stats.uptime / 60)}m ${stats.uptime % 60}s |`,
2115
- `| Status | Healthy |`,
1668
+ `| Status | Healthy |`,
2116
1669
  ];
2117
1670
  return {
2118
1671
  jsonrpc: '2.0', id,
@@ -2126,101 +1679,428 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
2126
1679
  };
2127
1680
  }
2128
1681
  }
2129
- // ─── Pre-Flight Check Handler ───────────────────────────────────────────────
2130
- function handlePreCheck(id, args) {
1682
+ // ═══ NEW TOOLS: search_by_file, pin, unpin, undo, clear, timeline, thumbs ═══
1683
+ function handleSearchByFile(id, args) {
2131
1684
  try {
2132
- // Track file for relationship mapping
2133
- if (args.filename)
2134
- (0, file_relationships_1.recordFileEdit)(args.filename);
2135
- const result = (0, pre_flight_1.preFlightCheck)(memoryStore, args.filename, args.task);
2136
- let text = (0, pre_flight_1.formatPreFlight)(result);
2137
- // Add file relationship warnings
2138
- if (args.filename) {
2139
- const warnings = (0, file_relationships_1.checkMissingRelated)(args.filename, memoryStore);
2140
- if (warnings.length > 0) {
2141
- text += '\n\n## 🔗 File Relationships\n';
2142
- warnings.forEach(w => text += w + '\n');
2143
- }
1685
+ const filePath = args.filePath;
1686
+ const limit = args.limit || 20;
1687
+ if (!filePath) {
1688
+ return {
1689
+ jsonrpc: '2.0', id,
1690
+ result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true },
1691
+ };
2144
1692
  }
2145
- // Add architecture context show file's role in the system
2146
- if (args.filename && workspaceRoot) {
1693
+ // Use the existing getByFile method + FTS fallback
1694
+ let memories = memoryStore.getByFile(filePath, limit);
1695
+ // Also do FTS search for the filename
1696
+ if (memories.length < limit) {
1697
+ const basename = filePath.split(/[\\/]/).pop() || filePath;
2147
1698
  try {
2148
- const archGraph = (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot);
2149
- const basename = args.filename.replace(/\\/g, '/').split('/').pop() || args.filename;
2150
- // nodes is Map<string, ArchNode> — find by key ending with basename
2151
- let deps = null;
2152
- for (const [key, node] of archGraph.nodes) {
2153
- if (key.endsWith(basename)) {
2154
- deps = node;
2155
- break;
2156
- }
2157
- }
2158
- if (deps && (deps.imports?.length > 0 || deps.importedBy?.length > 0)) {
2159
- text += '\n\n## \ud83c\udfd7\ufe0f Architecture Context';
2160
- if (deps.imports?.length > 0) {
2161
- text += `\n**Imports from:** ${deps.imports.slice(0, 10).join(', ')}`;
2162
- }
2163
- if (deps.importedBy?.length > 0) {
2164
- text += `\n**Imported by:** ${deps.importedBy.slice(0, 10).join(', ')}`;
1699
+ const ftsResults = memoryStore.searchFTS(basename, limit - memories.length);
1700
+ const existingIds = new Set(memories.map(m => m.id));
1701
+ for (const r of ftsResults) {
1702
+ if (!existingIds.has(r.memory.id)) {
1703
+ memories.push(r.memory);
2165
1704
  }
2166
1705
  }
2167
- }
2168
- catch { /* non-fatal */ }
1706
+ } catch { /* FTS may fail on special characters */ }
1707
+ }
1708
+ if (memories.length === 0) {
1709
+ return {
1710
+ jsonrpc: '2.0', id,
1711
+ result: { content: [{ type: 'text', text: `No memories found for file: "${filePath}"` }] },
1712
+ };
1713
+ }
1714
+ const lines = [`# Memories related to "${filePath}"\n`];
1715
+ for (const m of memories) {
1716
+ const age = Math.floor((Date.now() - m.createdAt) / 86400000);
1717
+ lines.push(`- **[${m.type}]** ${m.intent}`);
1718
+ if (m.reason) lines.push(` _${m.reason}_`);
1719
+ lines.push(` \`id: ${m.id}\` · ${age}d old`);
2169
1720
  }
1721
+ lines.push(`\n_Total: ${memories.length} memories for this file._`);
1722
+ return {
1723
+ jsonrpc: '2.0', id,
1724
+ result: { content: [{ type: 'text', text: lines.join('\n') }] },
1725
+ };
1726
+ } catch (err) {
2170
1727
  return {
2171
1728
  jsonrpc: '2.0', id,
2172
- result: { content: [{ type: 'text', text }] },
1729
+ result: { content: [{ type: 'text', text: `search_by_file error: ${err.message}` }], isError: true },
2173
1730
  };
2174
1731
  }
2175
- catch (err) {
1732
+ }
1733
+ function handlePinMemory(id, args) {
1734
+ try {
1735
+ const memoryId = args.id;
1736
+ if (!memoryId) {
1737
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
1738
+ }
1739
+ const existing = memoryStore.get(memoryId);
1740
+ if (!existing) {
1741
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
1742
+ }
1743
+ const tags = existing.tags || [];
1744
+ if (!tags.includes('pinned')) tags.push('pinned');
1745
+ memoryStore.update(memoryId, { importance: 1.0, tags });
1746
+ (0, memory_cache_1.invalidateCache)();
2176
1747
  return {
2177
1748
  jsonrpc: '2.0', id,
2178
- result: { content: [{ type: 'text', text: `Pre-check error: ${err.message}` }], isError: true },
1749
+ result: { content: [{ type: 'text', text: `📌 Memory PINNED: "${existing.intent}"\n\nThis memory will ALWAYS be included in context, regardless of topic.` }] },
2179
1750
  };
1751
+ } catch (err) {
1752
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `pin error: ${err.message}` }], isError: true } };
2180
1753
  }
2181
1754
  }
2182
- // ─── Impact Analysis Handler ───────────────────────────────────────────────
2183
- function handleCheckImpact(id, args) {
1755
+ function handleUnpinMemory(id, args) {
2184
1756
  try {
2185
- const file = args.file;
2186
- if (!file) {
1757
+ const memoryId = args.id;
1758
+ if (!memoryId) {
1759
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
1760
+ }
1761
+ const existing = memoryStore.get(memoryId);
1762
+ if (!existing) {
1763
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
1764
+ }
1765
+ const tags = (existing.tags || []).filter(t => t !== 'pinned');
1766
+ memoryStore.update(memoryId, { importance: 0.7, tags });
1767
+ (0, memory_cache_1.invalidateCache)();
1768
+ return {
1769
+ jsonrpc: '2.0', id,
1770
+ result: { content: [{ type: 'text', text: `📌 Memory UNPINNED: "${existing.intent}"\n\nThis memory will now use normal priority ranking.` }] },
1771
+ };
1772
+ } catch (err) {
1773
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `unpin error: ${err.message}` }], isError: true } };
1774
+ }
1775
+ }
1776
+ function handleUndoLast(id, args) {
1777
+ try {
1778
+ const count = Math.min(Math.max(args.count || 1, 1), 10);
1779
+ // Get the N most recently CREATED active memories (order by created_at, not timestamp)
1780
+ const db = memoryStore.db;
1781
+ const recentRows = db.prepare('SELECT * FROM memory_units WHERE is_active = 1 ORDER BY created_at DESC LIMIT ?').all(count);
1782
+ const recent = recentRows.map(r => memoryStore.get(r.id)).filter(Boolean);
1783
+ if (recent.length === 0) {
2187
1784
  return {
2188
1785
  jsonrpc: '2.0', id,
2189
- result: { content: [{ type: 'text', text: 'Error: file parameter is required' }], isError: true },
1786
+ result: { content: [{ type: 'text', text: 'No memories to undo.' }] },
2190
1787
  };
2191
1788
  }
2192
- const wsRoot = args.workspaceRoot || process.cwd();
2193
- const result = (0, impact_analyzer_1.analyzeImpact)(file, wsRoot);
2194
- const text = (0, impact_analyzer_1.formatImpact)(result);
1789
+ const toUndo = recent;
1790
+ const undone = [];
1791
+ for (const m of toUndo) {
1792
+ memoryStore.deactivate(m.id);
1793
+ undone.push(`- [${m.type}] "${m.intent.slice(0, 80)}"`);
1794
+ }
1795
+ (0, memory_cache_1.invalidateCache)();
2195
1796
  return {
2196
1797
  jsonrpc: '2.0', id,
2197
- result: { content: [{ type: 'text', text }] },
1798
+ result: { content: [{ type: 'text', text: `⏪ Undone ${undone.length} memory(ies):\n\n${undone.join('\n')}` }] },
2198
1799
  };
1800
+ } catch (err) {
1801
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `undo error: ${err.message}` }], isError: true } };
2199
1802
  }
2200
- catch (err) {
1803
+ }
1804
+ function handleClearMemories(id, args) {
1805
+ try {
1806
+ const type = args.type;
1807
+ const olderThanDays = args.olderThanDays;
1808
+ if (!type) {
1809
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: type is required' }], isError: true } };
1810
+ }
1811
+ let query = 'SELECT * FROM memory_units WHERE is_active = 1';
1812
+ const params = [];
1813
+ if (type !== 'ALL') {
1814
+ query += ' AND type = ?';
1815
+ params.push(type);
1816
+ }
1817
+ if (olderThanDays) {
1818
+ const cutoff = Date.now() - (olderThanDays * 86400000);
1819
+ query += ' AND created_at < ?';
1820
+ params.push(cutoff);
1821
+ }
1822
+ const rows = memoryStore.db ? memoryStore.db.prepare(query).all.apply(memoryStore.db.prepare(query), params) : [];
1823
+ let cleared = 0;
1824
+ for (const row of rows) {
1825
+ memoryStore.deactivate(row.id);
1826
+ cleared++;
1827
+ }
1828
+ (0, memory_cache_1.invalidateCache)();
1829
+ const ageMsg = olderThanDays ? ` older than ${olderThanDays} days` : '';
2201
1830
  return {
2202
1831
  jsonrpc: '2.0', id,
2203
- result: { content: [{ type: 'text', text: `Impact analysis error: ${err.message}` }], isError: true },
1832
+ result: { content: [{ type: 'text', text: `🗑️ Cleared ${cleared} ${type === 'ALL' ? '' : type + ' '}memories${ageMsg}.` }] },
2204
1833
  };
1834
+ } catch (err) {
1835
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `clear error: ${err.message}` }], isError: true } };
2205
1836
  }
2206
1837
  }
2207
- // ─── Resume Work Handler ───────────────────────────────────────────────────
2208
- function handleResumeWork(id) {
1838
+ function parseRelativeDate(str) {
1839
+ if (!str) return Date.now();
1840
+ const lower = str.toLowerCase().trim();
1841
+ if (lower === 'now' || lower === 'today') return Date.now();
1842
+ if (lower === 'yesterday') return Date.now() - 86400000;
1843
+ // "N days ago", "N hours ago", "N weeks ago"
1844
+ const relMatch = lower.match(/^(\d+)\s+(day|hour|week|month)s?\s+ago$/);
1845
+ if (relMatch) {
1846
+ const n = parseInt(relMatch[1], 10);
1847
+ const unit = relMatch[2];
1848
+ const multipliers = { hour: 3600000, day: 86400000, week: 604800000, month: 2592000000 };
1849
+ return Date.now() - (n * (multipliers[unit] || 86400000));
1850
+ }
1851
+ // Try ISO date
1852
+ const parsed = new Date(str).getTime();
1853
+ return isNaN(parsed) ? Date.now() - 604800000 : parsed; // default to 7 days ago
1854
+ }
1855
+ function handleSearchTimeline(id, args) {
2209
1856
  try {
2210
- const ctx = (0, resume_work_1.buildResumeContext)(memoryStore);
2211
- const text = (0, resume_work_1.formatResumeContext)(ctx);
1857
+ const fromTs = parseRelativeDate(args.from);
1858
+ const toTs = parseRelativeDate(args.to || 'now');
1859
+ const type = args.type;
1860
+ let query = 'SELECT * FROM memory_units WHERE is_active = 1 AND created_at >= ? AND created_at <= ?';
1861
+ const params = [fromTs, toTs];
1862
+ if (type && type !== 'ALL') {
1863
+ query += ' AND type = ?';
1864
+ params.push(type);
1865
+ }
1866
+ query += ' ORDER BY created_at DESC LIMIT 50';
1867
+ const db = memoryStore.db || memoryStore.connection;
1868
+ const stmt = db.prepare(query);
1869
+ const rows = stmt.all.apply(stmt, params);
1870
+ if (rows.length === 0) {
1871
+ return {
1872
+ jsonrpc: '2.0', id,
1873
+ result: { content: [{ type: 'text', text: `No memories found between ${new Date(fromTs).toLocaleDateString()} and ${new Date(toTs).toLocaleDateString()}.` }] },
1874
+ };
1875
+ }
1876
+ const lines = [`# Timeline: ${new Date(fromTs).toLocaleDateString()} → ${new Date(toTs).toLocaleDateString()}\n`];
1877
+ let lastDate = '';
1878
+ for (const row of rows) {
1879
+ const dateStr = new Date(row.created_at).toLocaleDateString();
1880
+ if (dateStr !== lastDate) {
1881
+ lines.push(`\n## ${dateStr}`);
1882
+ lastDate = dateStr;
1883
+ }
1884
+ lines.push(`- **[${row.type}]** ${row.intent}`);
1885
+ if (row.reason) lines.push(` _${row.reason}_`);
1886
+ }
1887
+ lines.push(`\n_Total: ${rows.length} memories in this period._`);
2212
1888
  return {
2213
1889
  jsonrpc: '2.0', id,
2214
- result: { content: [{ type: 'text', text }] },
1890
+ result: { content: [{ type: 'text', text: lines.join('\n') }] },
2215
1891
  };
1892
+ } catch (err) {
1893
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `timeline error: ${err.message}` }], isError: true } };
2216
1894
  }
2217
- catch (err) {
1895
+ }
1896
+ function handleThumbsUp(id, args) {
1897
+ try {
1898
+ const memoryId = args.id;
1899
+ if (!memoryId) {
1900
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
1901
+ }
1902
+ const existing = memoryStore.get(memoryId);
1903
+ if (!existing) {
1904
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
1905
+ }
1906
+ // Boost importance (cap at 1.0)
1907
+ const newImportance = Math.min(1.0, (existing.importance || 0.5) + 0.1);
1908
+ memoryStore.update(memoryId, { importance: newImportance });
1909
+ // Record in user_signals table
1910
+ try {
1911
+ const db = memoryStore.db || memoryStore.connection;
1912
+ db.prepare('INSERT INTO user_signals (memory_id, signal, timestamp) VALUES (?, ?, ?)')
1913
+ .run(memoryId, 'thumbs_up', Date.now());
1914
+ } catch { /* table might not exist */ }
1915
+ (0, memory_cache_1.invalidateCache)();
1916
+ return {
1917
+ jsonrpc: '2.0', id,
1918
+ result: { content: [{ type: 'text', text: `👍 Upvoted: "${existing.intent.slice(0, 80)}"\nImportance: ${(newImportance * 100).toFixed(0)}%` }] },
1919
+ };
1920
+ } catch (err) {
1921
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `thumbs_up error: ${err.message}` }], isError: true } };
1922
+ }
1923
+ }
1924
+ function handleThumbsDown(id, args) {
1925
+ try {
1926
+ const memoryId = args.id;
1927
+ const reason = args.reason || 'Not useful';
1928
+ if (!memoryId) {
1929
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
1930
+ }
1931
+ const existing = memoryStore.get(memoryId);
1932
+ if (!existing) {
1933
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
1934
+ }
1935
+ // Demote importance
1936
+ const newImportance = Math.max(0.0, (existing.importance || 0.5) - 0.15);
1937
+ memoryStore.update(memoryId, { importance: newImportance });
1938
+ // Record in user_signals table
1939
+ let downvoteCount = 0;
1940
+ try {
1941
+ const db = memoryStore.db || memoryStore.connection;
1942
+ db.prepare('INSERT INTO user_signals (memory_id, signal, correction, timestamp) VALUES (?, ?, ?, ?)')
1943
+ .run(memoryId, 'thumbs_down', reason, Date.now());
1944
+ // Count total downvotes for this memory
1945
+ const row = db.prepare('SELECT COUNT(*) as cnt FROM user_signals WHERE memory_id = ? AND signal = ?')
1946
+ .get(memoryId, 'thumbs_down');
1947
+ downvoteCount = row ? row.cnt : 0;
1948
+ } catch { /* table might not exist */ }
1949
+ // Auto-deactivate if 3+ downvotes
1950
+ let deactivated = false;
1951
+ if (downvoteCount >= 3) {
1952
+ memoryStore.deactivate(memoryId);
1953
+ deactivated = true;
1954
+ }
1955
+ (0, memory_cache_1.invalidateCache)();
1956
+ let msg = `👎 Downvoted: "${existing.intent.slice(0, 80)}"\nImportance: ${(newImportance * 100).toFixed(0)}%\nReason: ${reason}`;
1957
+ if (deactivated) {
1958
+ msg += `\n\n⚠️ Memory auto-deactivated (received ${downvoteCount} downvotes).`;
1959
+ }
1960
+ return {
1961
+ jsonrpc: '2.0', id,
1962
+ result: { content: [{ type: 'text', text: msg }] },
1963
+ };
1964
+ } catch (err) {
1965
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `thumbs_down error: ${err.message}` }], isError: true } };
1966
+ }
1967
+ }
1968
+ function handleBackupBrain(id) {
1969
+ try {
1970
+ const fs = require('fs');
1971
+ const path = require('path');
1972
+ const dbPath = memoryStore.db?.name || memoryStore._dbPath;
1973
+ if (!dbPath || !fs.existsSync(dbPath)) {
1974
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: Cannot locate database file.' }], isError: true } };
1975
+ }
1976
+ const backupDate = new Date().toISOString().replace(/[:.]/g, '-');
1977
+ const backupPath = path.join(path.dirname(dbPath), `cognitive-backup-${backupDate}.db`);
1978
+ // Close active WAL before backup
1979
+ memoryStore.checkpoint?.();
1980
+ fs.copyFileSync(dbPath, backupPath);
2218
1981
  return {
2219
1982
  jsonrpc: '2.0', id,
2220
- result: { content: [{ type: 'text', text: `Resume work error: ${err.message}` }], isError: true },
1983
+ result: { content: [{ type: 'text', text: `✅ Database backed up successfully!\n\nSaved to: ${backupPath}\nSize: ${(fs.statSync(backupPath).size / 1024 / 1024).toFixed(2)} MB` }] },
2221
1984
  };
1985
+ } catch (err) {
1986
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `backup error: ${err.message}` }], isError: true } };
1987
+ }
1988
+ }
1989
+ function handleGetDailySummary(id, args) {
1990
+ try {
1991
+ const dateStr = args.date || new Date().toISOString().split('T')[0];
1992
+ const db = memoryStore.db || memoryStore.connection;
1993
+ // First check if it exists
1994
+ const row = db.prepare('SELECT summary FROM daily_summaries WHERE date = ?').get(dateStr);
1995
+ if (row) {
1996
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `# Summary for ${dateStr}\n\n${row.summary}` }] } };
1997
+ }
1998
+ // If checking today and no summary exists, let's create a dynamic one
1999
+ const isToday = dateStr === new Date().toISOString().split('T')[0];
2000
+ if (!isToday) {
2001
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `No summary found for ${dateStr}.` }] } };
2002
+ }
2003
+ // Generate dynamic summary for today
2004
+ const startOfDay = new Date(dateStr).setHours(0,0,0,0);
2005
+ const todayMemories = db.prepare('SELECT * FROM memory_units WHERE is_active = 1 AND created_at >= ? ORDER BY created_at ASC').all(startOfDay);
2006
+ if (todayMemories.length === 0) {
2007
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `No memories recorded yet today (${dateStr}).` }] } };
2008
+ }
2009
+ const lines = [];
2010
+ let types = { DECISION: 0, BUG_FIX: 0, CORRECTION: 0, CONVENTION: 0 };
2011
+ for (const m of todayMemories) {
2012
+ if (types[m.type] !== undefined) types[m.type]++;
2013
+ lines.push(`- **[${m.type}]** ${m.intent}`);
2014
+ }
2015
+ const summaryText = `Today we recorded ${todayMemories.length} memories (${types.DECISION} decisions, ${types.BUG_FIX} fixes, ${types.CORRECTION} corrections, ${types.CONVENTION} conventions).\n\nKey updates:\n${lines.join('\n')}`;
2016
+ // Save it
2017
+ db.prepare('INSERT OR REPLACE INTO daily_summaries (date, summary, created_at) VALUES (?, ?, ?)')
2018
+ .run(dateStr, summaryText, Date.now());
2019
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `# Summary for Today (${dateStr})\n\n${summaryText}` }] } };
2020
+ } catch (err) {
2021
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `summary error: ${err.message}` }], isError: true } };
2022
+ }
2023
+ }
2024
+ function handleAnalytics(id) {
2025
+ try {
2026
+ const db = memoryStore.db || memoryStore.connection;
2027
+ const stats = [];
2028
+ // Basic counts
2029
+ const total = db.prepare('SELECT COUNT(*) as c FROM memory_units').get().c;
2030
+ const active = db.prepare('SELECT COUNT(*) as c FROM memory_units WHERE is_active = 1').get().c;
2031
+ const pinned = db.prepare("SELECT COUNT(*) as c FROM memory_units WHERE is_active = 1 AND tags LIKE '%pinned%'").get().c;
2032
+ stats.push(`## 📊 Brain Health\n- Total Memories: ${total}\n- Active Memories: ${active}\n- Pinned Rules: ${pinned}`);
2033
+ // By Type
2034
+ stats.push('\n## 🧩 Distribution by Type');
2035
+ const types = db.prepare('SELECT type, COUNT(*) as c FROM memory_units WHERE is_active = 1 GROUP BY type ORDER BY c DESC').all();
2036
+ for (const t of types) stats.push(`- **${t.type}**: ${t.c}`);
2037
+ // Most Accessed
2038
+ stats.push('\n## 🔥 Most Accessed Memories (Top 5)');
2039
+ const hot = db.prepare('SELECT id, type, intent, access_count FROM memory_units WHERE is_active = 1 AND access_count > 0 ORDER BY access_count DESC LIMIT 5').all();
2040
+ if (hot.length) {
2041
+ for (const h of hot) stats.push(`- [${h.access_count}x] **${h.type}**: ${h.intent} (id: ${h.id})`);
2042
+ } else {
2043
+ stats.push('- No memory accesses recorded yet.');
2044
+ }
2045
+ // Feedback stats
2046
+ try {
2047
+ const up = db.prepare("SELECT COUNT(*) as c FROM user_signals WHERE signal = 'thumbs_up'").get().c;
2048
+ const down = db.prepare("SELECT COUNT(*) as c FROM user_signals WHERE signal = 'thumbs_down'").get().c;
2049
+ if (up > 0 || down > 0) {
2050
+ stats.push(`\n## 👍 User Feedback\n- Upvotes: ${up}\n- Downvotes: ${down}`);
2051
+ }
2052
+ } catch { /* ignore if table missing */ }
2053
+ return {
2054
+ jsonrpc: '2.0', id,
2055
+ result: { content: [{ type: 'text', text: stats.join('\n') }] },
2056
+ };
2057
+ } catch (err) {
2058
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `analytics error: ${err.message}` }], isError: true } };
2222
2059
  }
2223
2060
  }
2224
2061
  return { handleMCPRequest };
2225
2062
  }
2063
+ // ─── NEW: Export/Import/Graph Handlers ─────────────────────────────────────
2064
+ async function handleExportMemoriesFn(memoryStore, id, args) {
2065
+ try {
2066
+ const { filePath } = args;
2067
+ if (!filePath) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true } };
2068
+ const result = (0, export_import_1.exportToFile)(memoryStore, filePath);
2069
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: '✅ Exported ' + result.count + ' memories to:\n' + result.path + '\n\nShare this file with teammates or use import_memories to restore.' }] } };
2070
+ } catch (err) {
2071
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'export error: ' + err.message }], isError: true } };
2072
+ }
2073
+ }
2074
+ async function handleImportMemoriesFn(memoryStore, memory_cache_1, id, args) {
2075
+ try {
2076
+ const { filePath } = args;
2077
+ if (!filePath) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true } };
2078
+ const result = (0, export_import_1.importFromFile)(memoryStore, filePath);
2079
+ if (result.imported > 0) (0, memory_cache_1.invalidateCache)();
2080
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: '✅ Import complete!\n\n- Imported: ' + result.imported + ' memories\n- Skipped (duplicates): ' + result.skipped + '\n- Errors: ' + result.errors }] } };
2081
+ } catch (err) {
2082
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'import error: ' + err.message }], isError: true } };
2083
+ }
2084
+ }
2085
+ async function handleGetRelatedMemoriesFn(memoryStore, id, args) {
2086
+ try {
2087
+ const memoryId = args.id;
2088
+ const maxHops = args.maxHops || 2;
2089
+ if (!memoryId) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
2090
+ const hops = Math.min(4, Math.max(1, maxHops));
2091
+ const starting = memoryStore.get(memoryId);
2092
+ if (!starting) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'No memory found with id: ' + memoryId }] } };
2093
+ const related = memoryStore.getRelated(memoryId, hops, 20);
2094
+ if (related.length === 0) {
2095
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Memory: [' + starting.type + '] ' + starting.intent + '\n\nNo related memories found yet (no graph edges). Use store_memory to build connections.' }] } };
2096
+ }
2097
+ const lines = ['## Starting Memory', '[' + starting.type + '] ' + starting.intent + ' (id: ' + starting.id + ')', '', '## Related Memories (via graph)'];
2098
+ for (const r of related) {
2099
+ lines.push('- [' + r.memory.type + '] ' + r.memory.intent + ' (id: ' + r.memory.id + ') (' + (r.score * 100).toFixed(0) + '% relevance)');
2100
+ }
2101
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: lines.join('\n') }] } };
2102
+ } catch (err) {
2103
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'get_related error: ' + err.message }], isError: true } };
2104
+ }
2105
+ }
2226
2106
  //# sourceMappingURL=mcp-handler.js.map