cortex-mcp 2.8.0 → 2.9.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 (202) hide show
  1. package/CHANGELOG.md +305 -58
  2. package/README.md +394 -227
  3. package/dist/cli/setup.js +58 -60
  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 +8 -0
  8. package/dist/db/database.d.ts.map +1 -1
  9. package/dist/db/database.js +35 -54
  10. package/dist/db/database.js.map +1 -1
  11. package/dist/db/event-log.d.ts +4 -0
  12. package/dist/db/event-log.d.ts.map +1 -1
  13. package/dist/db/event-log.js +14 -10
  14. package/dist/db/event-log.js.map +1 -1
  15. package/dist/db/memory-store.d.ts +30 -3
  16. package/dist/db/memory-store.d.ts.map +1 -1
  17. package/dist/db/memory-store.js +153 -55
  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 +5 -2
  23. package/dist/mcp-stdio.js +39 -5
  24. package/dist/mcp-stdio.js.map +1 -1
  25. package/dist/memory/access-pattern-tracker.d.ts +51 -0
  26. package/dist/memory/access-pattern-tracker.d.ts.map +1 -0
  27. package/dist/memory/access-pattern-tracker.js +92 -0
  28. package/dist/memory/access-pattern-tracker.js.map +1 -0
  29. package/dist/memory/anticipation-engine.d.ts.map +1 -1
  30. package/dist/memory/anticipation-engine.js +18 -10
  31. package/dist/memory/anticipation-engine.js.map +1 -1
  32. package/dist/memory/auto-learner.d.ts.map +1 -1
  33. package/dist/memory/auto-learner.js +192 -45
  34. package/dist/memory/auto-learner.js.map +1 -1
  35. package/dist/memory/completion-resolver.d.ts +38 -0
  36. package/dist/memory/completion-resolver.d.ts.map +1 -0
  37. package/dist/memory/completion-resolver.js +127 -0
  38. package/dist/memory/completion-resolver.js.map +1 -0
  39. package/dist/memory/confidence-decay.d.ts.map +1 -1
  40. package/dist/memory/confidence-decay.js +13 -9
  41. package/dist/memory/confidence-decay.js.map +1 -1
  42. package/dist/memory/convention-detector.d.ts +11 -0
  43. package/dist/memory/convention-detector.d.ts.map +1 -0
  44. package/dist/memory/convention-detector.js +294 -0
  45. package/dist/memory/convention-detector.js.map +1 -0
  46. package/dist/memory/correction-detector.d.ts +33 -0
  47. package/dist/memory/correction-detector.d.ts.map +1 -0
  48. package/dist/memory/correction-detector.js +129 -0
  49. package/dist/memory/correction-detector.js.map +1 -0
  50. package/dist/memory/cross-memory-linker.d.ts +18 -0
  51. package/dist/memory/cross-memory-linker.d.ts.map +1 -0
  52. package/dist/memory/cross-memory-linker.js +115 -0
  53. package/dist/memory/cross-memory-linker.js.map +1 -0
  54. package/dist/memory/daily-diary.d.ts +30 -0
  55. package/dist/memory/daily-diary.d.ts.map +1 -0
  56. package/dist/memory/daily-diary.js +159 -0
  57. package/dist/memory/daily-diary.js.map +1 -0
  58. package/dist/memory/embedding-cache.d.ts +32 -0
  59. package/dist/memory/embedding-cache.d.ts.map +1 -0
  60. package/dist/memory/embedding-cache.js +76 -0
  61. package/dist/memory/embedding-cache.js.map +1 -0
  62. package/dist/memory/embedding-manager.d.ts.map +1 -1
  63. package/dist/memory/embedding-manager.js +6 -4
  64. package/dist/memory/embedding-manager.js.map +1 -1
  65. package/dist/memory/error-learner.d.ts +26 -0
  66. package/dist/memory/error-learner.d.ts.map +1 -0
  67. package/dist/memory/error-learner.js +145 -0
  68. package/dist/memory/error-learner.js.map +1 -0
  69. package/dist/memory/export-import.js +2 -2
  70. package/dist/memory/export-import.js.map +1 -1
  71. package/dist/memory/file-relationships.d.ts +47 -0
  72. package/dist/memory/file-relationships.d.ts.map +1 -0
  73. package/dist/memory/file-relationships.js +130 -0
  74. package/dist/memory/file-relationships.js.map +1 -0
  75. package/dist/memory/git-memory.d.ts.map +1 -1
  76. package/dist/memory/git-memory.js +20 -26
  77. package/dist/memory/git-memory.js.map +1 -1
  78. package/dist/memory/impact-analyzer.d.ts +16 -0
  79. package/dist/memory/impact-analyzer.d.ts.map +1 -0
  80. package/dist/memory/impact-analyzer.js +189 -0
  81. package/dist/memory/impact-analyzer.js.map +1 -0
  82. package/dist/memory/instructions-generator.d.ts +30 -0
  83. package/dist/memory/instructions-generator.d.ts.map +1 -0
  84. package/dist/memory/instructions-generator.js +117 -0
  85. package/dist/memory/instructions-generator.js.map +1 -0
  86. package/dist/memory/learning-rate.js +8 -7
  87. package/dist/memory/learning-rate.js.map +1 -1
  88. package/dist/memory/llm-enhancer.d.ts +2 -14
  89. package/dist/memory/llm-enhancer.d.ts.map +1 -1
  90. package/dist/memory/llm-enhancer.js +66 -46
  91. package/dist/memory/llm-enhancer.js.map +1 -1
  92. package/dist/memory/memory-cache.d.ts.map +1 -1
  93. package/dist/memory/memory-cache.js +10 -0
  94. package/dist/memory/memory-cache.js.map +1 -1
  95. package/dist/memory/memory-consolidator.d.ts.map +1 -1
  96. package/dist/memory/memory-consolidator.js +20 -14
  97. package/dist/memory/memory-consolidator.js.map +1 -1
  98. package/dist/memory/memory-decay.d.ts.map +1 -1
  99. package/dist/memory/memory-decay.js +82 -52
  100. package/dist/memory/memory-decay.js.map +1 -1
  101. package/dist/memory/memory-export-md.d.ts +12 -0
  102. package/dist/memory/memory-export-md.d.ts.map +1 -0
  103. package/dist/memory/memory-export-md.js +188 -0
  104. package/dist/memory/memory-export-md.js.map +1 -0
  105. package/dist/memory/memory-quality.d.ts +1 -1
  106. package/dist/memory/memory-quality.d.ts.map +1 -1
  107. package/dist/memory/memory-quality.js +9 -6
  108. package/dist/memory/memory-quality.js.map +1 -1
  109. package/dist/memory/memory-ranker.d.ts.map +1 -1
  110. package/dist/memory/memory-ranker.js +12 -3
  111. package/dist/memory/memory-ranker.js.map +1 -1
  112. package/dist/memory/meta-memory.js +3 -3
  113. package/dist/memory/meta-memory.js.map +1 -1
  114. package/dist/memory/mmr-reranker.d.ts +39 -0
  115. package/dist/memory/mmr-reranker.d.ts.map +1 -0
  116. package/dist/memory/mmr-reranker.js +115 -0
  117. package/dist/memory/mmr-reranker.js.map +1 -0
  118. package/dist/memory/pre-flight.d.ts +24 -0
  119. package/dist/memory/pre-flight.d.ts.map +1 -0
  120. package/dist/memory/pre-flight.js +121 -0
  121. package/dist/memory/pre-flight.js.map +1 -0
  122. package/dist/memory/preference-learner.d.ts +28 -0
  123. package/dist/memory/preference-learner.d.ts.map +1 -0
  124. package/dist/memory/preference-learner.js +144 -0
  125. package/dist/memory/preference-learner.js.map +1 -0
  126. package/dist/memory/query-expansion.d.ts +28 -0
  127. package/dist/memory/query-expansion.d.ts.map +1 -0
  128. package/dist/memory/query-expansion.js +140 -0
  129. package/dist/memory/query-expansion.js.map +1 -0
  130. package/dist/memory/regression-guard.d.ts +35 -0
  131. package/dist/memory/regression-guard.d.ts.map +1 -0
  132. package/dist/memory/regression-guard.js +90 -0
  133. package/dist/memory/regression-guard.js.map +1 -0
  134. package/dist/memory/resume-work.d.ts +37 -0
  135. package/dist/memory/resume-work.d.ts.map +1 -0
  136. package/dist/memory/resume-work.js +141 -0
  137. package/dist/memory/resume-work.js.map +1 -0
  138. package/dist/memory/session-tracker.d.ts +2 -0
  139. package/dist/memory/session-tracker.d.ts.map +1 -1
  140. package/dist/memory/session-tracker.js +26 -8
  141. package/dist/memory/session-tracker.js.map +1 -1
  142. package/dist/memory/soul-manager.d.ts +30 -0
  143. package/dist/memory/soul-manager.d.ts.map +1 -0
  144. package/dist/memory/soul-manager.js +171 -0
  145. package/dist/memory/soul-manager.js.map +1 -0
  146. package/dist/memory/success-tracker.d.ts +33 -0
  147. package/dist/memory/success-tracker.d.ts.map +1 -0
  148. package/dist/memory/success-tracker.js +75 -0
  149. package/dist/memory/success-tracker.js.map +1 -0
  150. package/dist/memory/temporal-engine.d.ts.map +1 -1
  151. package/dist/memory/temporal-engine.js +9 -13
  152. package/dist/memory/temporal-engine.js.map +1 -1
  153. package/dist/memory/tool-recommender.d.ts +29 -0
  154. package/dist/memory/tool-recommender.d.ts.map +1 -0
  155. package/dist/memory/tool-recommender.js +117 -0
  156. package/dist/memory/tool-recommender.js.map +1 -0
  157. package/dist/memory/usage-stats.d.ts +98 -0
  158. package/dist/memory/usage-stats.d.ts.map +1 -0
  159. package/dist/memory/usage-stats.js +345 -0
  160. package/dist/memory/usage-stats.js.map +1 -0
  161. package/dist/retrieval/hybrid-retriever.d.ts +0 -2
  162. package/dist/retrieval/hybrid-retriever.d.ts.map +1 -1
  163. package/dist/retrieval/hybrid-retriever.js +3 -13
  164. package/dist/retrieval/hybrid-retriever.js.map +1 -1
  165. package/dist/scanners/architecture-graph.js +2 -2
  166. package/dist/scanners/architecture-graph.js.map +1 -1
  167. package/dist/scanners/code-verifier.d.ts +1 -0
  168. package/dist/scanners/code-verifier.d.ts.map +1 -1
  169. package/dist/scanners/code-verifier.js +14 -14
  170. package/dist/scanners/code-verifier.js.map +1 -1
  171. package/dist/scanners/context-builder.d.ts.map +1 -1
  172. package/dist/scanners/context-builder.js +33 -45
  173. package/dist/scanners/context-builder.js.map +1 -1
  174. package/dist/scanners/export-map.js +2 -2
  175. package/dist/scanners/export-map.js.map +1 -1
  176. package/dist/scanners/project-scanner.js +2 -2
  177. package/dist/scanners/project-scanner.js.map +1 -1
  178. package/dist/security/encryption.js +1 -1
  179. package/dist/security/encryption.js.map +1 -1
  180. package/dist/security/feature-gate.d.ts.map +1 -1
  181. package/dist/security/feature-gate.js +62 -20
  182. package/dist/security/feature-gate.js.map +1 -1
  183. package/dist/security/license.js +282 -35
  184. package/dist/security/license.js.map +1 -1
  185. package/dist/security/rate-limiter.d.ts +4 -3
  186. package/dist/security/rate-limiter.d.ts.map +1 -1
  187. package/dist/security/rate-limiter.js +11 -29
  188. package/dist/security/rate-limiter.js.map +1 -1
  189. package/dist/server/dashboard.js +166 -327
  190. package/dist/server/dashboard.js.map +1 -1
  191. package/dist/server/mcp-handler.d.ts.map +1 -1
  192. package/dist/server/mcp-handler.js +1010 -1015
  193. package/dist/server/mcp-handler.js.map +1 -1
  194. package/dist/utils/extract-tags.d.ts +16 -0
  195. package/dist/utils/extract-tags.d.ts.map +1 -0
  196. package/dist/utils/extract-tags.js +40 -0
  197. package/dist/utils/extract-tags.js.map +1 -0
  198. package/package.json +18 -8
  199. package/dist/core/event-bus.d.ts +0 -19
  200. package/dist/core/event-bus.d.ts.map +0 -1
  201. package/dist/core/event-bus.js +0 -51
  202. package/dist/core/event-bus.js.map +0 -1
@@ -23,39 +23,35 @@ 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");
27
26
  const feature_gate_1 = require("../security/feature-gate");
28
27
  const export_import_1 = require("../memory/export-import");
29
28
  const llm_enhancer_1 = require("../memory/llm-enhancer");
30
- // --- Query Expansion (Synonym Map) ---
31
- const SYNONYMS = {
32
- auth: ['authentication', 'login', 'signin', 'sign-in', 'credentials'],
33
- login: ['auth', 'authentication', 'signin', 'sign-in'],
34
- db: ['database', 'sql', 'postgresql', 'postgres', 'mongodb', 'sqlite'],
35
- database: ['db', 'sql', 'postgresql', 'postgres', 'mongodb', 'sqlite'],
36
- api: ['endpoint', 'route', 'rest', 'graphql', 'http'],
37
- error: ['bug', 'fix', 'issue', 'problem', 'crash', 'fail'],
38
- bug: ['error', 'fix', 'issue', 'problem', 'crash'],
39
- style: ['css', 'design', 'theme', 'color', 'font', 'layout'],
40
- test: ['testing', 'jest', 'vitest', 'spec', 'unittest'],
41
- deploy: ['deployment', 'ci', 'cd', 'pipeline', 'docker', 'build'],
42
- };
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 cross_memory_linker_1 = require("../memory/cross-memory-linker");
37
+ const access_pattern_tracker_1 = require("../memory/access-pattern-tracker");
38
+ const soul_manager_1 = require("../memory/soul-manager");
39
+ const daily_diary_1 = require("../memory/daily-diary");
40
+ const memory_export_md_1 = require("../memory/memory-export-md");
41
+ const mmr_reranker_1 = require("../memory/mmr-reranker");
42
+ const embedding_cache_1 = require("../memory/embedding-cache");
43
+ const query_expansion_1 = require("../memory/query-expansion");
44
+ const resume_work_1 = require("../memory/resume-work");
45
+ const preference_learner_1 = require("../memory/preference-learner");
46
+ const convention_detector_1 = require("../memory/convention-detector");
47
+ const export_map_2 = require("../scanners/export-map");
48
+ const file_relationships_1 = require("../memory/file-relationships");
49
+ const instructions_generator_1 = require("../memory/instructions-generator");
50
+ const tool_recommender_1 = require("../memory/tool-recommender");
51
+ const regression_guard_1 = require("../memory/regression-guard");
52
+ // --- OpenClaw-style Query Expansion (replaced basic synonym map) ---
43
53
  function expandQuery(query) {
44
- const words = query.toLowerCase().split(/\s+/).filter(w => w.length > 2);
45
- const expanded = new Set(words);
46
- for (const word of words) {
47
- if (expanded.size >= 8)
48
- break; // Cap expansion to prevent FTS hang
49
- const syns = SYNONYMS[word];
50
- if (syns) {
51
- for (const s of syns.slice(0, 2)) {
52
- if (expanded.size >= 8)
53
- break;
54
- expanded.add(s);
55
- }
56
- }
57
- }
58
- return Array.from(expanded).join(' OR ');
54
+ return (0, query_expansion_1.expandQuery)(query);
59
55
  }
60
56
  // --- MCP Tool Definitions ---
61
57
  const MCP_TOOLS = [
@@ -143,14 +139,14 @@ const MCP_TOOLS = [
143
139
  },
144
140
  {
145
141
  name: 'force_recall',
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.',
142
+ 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.',
147
143
  inputSchema: {
148
144
  type: 'object',
149
145
  properties: {
150
146
  topic: { type: 'string', description: 'What the user is asking about (used to search for relevant memories)' },
151
147
  currentFile: { type: 'string', description: 'Currently active file path (optional)' },
152
148
  },
153
- required: [],
149
+ required: ['topic'],
154
150
  },
155
151
  },
156
152
  {
@@ -159,9 +155,9 @@ const MCP_TOOLS = [
159
155
  inputSchema: {
160
156
  type: 'object',
161
157
  properties: {
162
- content: { type: 'string', description: 'One sentence describing the decision, correction, convention, or bug fix' },
158
+ memory: { type: 'string', description: 'One sentence describing the decision, correction, convention, or bug fix' },
163
159
  },
164
- required: ['content'],
160
+ required: ['memory'],
165
161
  },
166
162
  },
167
163
  {
@@ -202,7 +198,7 @@ const MCP_TOOLS = [
202
198
  },
203
199
  {
204
200
  name: 'auto_learn',
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.',
201
+ 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.',
206
202
  inputSchema: {
207
203
  type: 'object',
208
204
  properties: {
@@ -240,149 +236,47 @@ const MCP_TOOLS = [
240
236
  },
241
237
  },
242
238
  {
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.',
245
- inputSchema: {
246
- type: 'object',
247
- properties: {
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)' },
283
- },
284
- },
285
- },
286
- {
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.',
289
- inputSchema: {
290
- type: 'object',
291
- properties: {
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)' },
294
- },
295
- required: ['type'],
296
- },
297
- },
298
- {
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?".',
239
+ name: 'review_code',
240
+ 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.',
301
241
  inputSchema: {
302
242
  type: 'object',
303
243
  properties: {
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)' },
244
+ code: { type: 'string', description: 'The code to review' },
245
+ filename: { type: 'string', description: 'Optional filename for context-aware review' },
307
246
  },
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'],
247
+ required: ['code'],
320
248
  },
321
249
  },
322
250
  {
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.',
251
+ name: 'pre_check',
252
+ 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.',
325
253
  inputSchema: {
326
254
  type: 'object',
327
255
  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)' },
256
+ filename: { type: 'string', description: 'The file you are about to edit' },
257
+ task: { type: 'string', description: 'What you plan to do (helps find relevant past failures)' },
330
258
  },
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.',
337
- inputSchema: {
338
- type: 'object',
339
- properties: {},
340
259
  },
341
260
  },
342
261
  {
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.',
262
+ name: 'check_impact',
263
+ 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.',
345
264
  inputSchema: {
346
265
  type: 'object',
347
266
  properties: {
348
- date: { type: 'string', description: 'Date in YYYY-MM-DD format (optional, defaults to today)' },
267
+ file: { type: 'string', description: 'The file you plan to modify' },
349
268
  },
269
+ required: ['file'],
350
270
  },
351
271
  },
352
272
  {
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: 'repair_brain',
362
- description: 'Repair and optimize the memory database. Rebuilds the full-text search index, checks database integrity, removes orphaned vectors, and runs VACUUM. Use this if search stops working or the brain feels slow.',
363
- inputSchema: {
364
- type: 'object',
365
- properties: {},
366
- },
367
- },
368
- {
369
- name: 'detect_conflicts',
370
- description: 'Scan all active memories for contradictions. Finds pairs of memories that say opposite things (e.g. "use PostgreSQL" vs "use MongoDB"). Returns conflicts so the user can resolve which is correct.',
273
+ name: 'resume_work',
274
+ 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.',
371
275
  inputSchema: {
372
276
  type: 'object',
373
277
  properties: {},
374
278
  },
375
279
  },
376
- {
377
- name: 'suggest_cleanup',
378
- description: 'Identify stale, low-value memories that should be deleted. Finds memories older than 30 days with low confidence, zero access count, and no pins. Returns a list the user can review and bulk-delete.',
379
- inputSchema: {
380
- type: 'object',
381
- properties: {
382
- olderThanDays: { type: 'number', description: 'Only suggest memories older than N days (default 30)' },
383
- },
384
- },
385
- },
386
280
  ];
387
281
  // --- Dynamic Context via ContextBuilder ---
388
282
  let cachedContextBuilder = null;
@@ -404,68 +298,12 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
404
298
  result: {
405
299
  protocolVersion: '2024-11-05',
406
300
  capabilities: { tools: {}, resources: {}, prompts: {} },
407
- serverInfo: { name: 'cortex', version: '2.7.0' },
301
+ serverInfo: { name: 'Cortex', version: require('../../package.json').version },
408
302
  },
409
303
  };
410
304
  case 'notifications/initialized':
305
+ case 'notifications/cancelled':
411
306
  return null;
412
- case 'prompts/list':
413
- return {
414
- jsonrpc: '2.0',
415
- id,
416
- result: {
417
- prompts: [
418
- {
419
- name: 'cortex_system_context',
420
- 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.',
421
- arguments: [
422
- {
423
- name: 'currentFile',
424
- description: 'The currently active file path (optional, improves relevance)',
425
- required: false,
426
- },
427
- {
428
- name: 'topic',
429
- description: 'What the user is asking about (optional, improves topic search)',
430
- required: false,
431
- },
432
- ],
433
- },
434
- ],
435
- },
436
- };
437
- case 'prompts/get': {
438
- const promptName = rpc.params?.name;
439
- const promptArgs = rpc.params?.arguments || {};
440
- if (promptName === 'cortex_system_context') {
441
- const builder = getContextBuilder(memoryStore);
442
- const memoryPayload = builder.build({
443
- currentFile: promptArgs.currentFile,
444
- maxChars: 6000,
445
- });
446
- return {
447
- jsonrpc: '2.0',
448
- id,
449
- result: {
450
- description: 'Cortex Auto-Injected System Context',
451
- messages: [
452
- {
453
- role: 'user',
454
- content: {
455
- type: 'text',
456
- 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}`,
457
- },
458
- },
459
- ],
460
- },
461
- };
462
- }
463
- return {
464
- jsonrpc: '2.0',
465
- id,
466
- error: { code: -32602, message: `Unknown prompt: ${promptName}` },
467
- };
468
- }
469
307
  case 'tools/list':
470
308
  return {
471
309
  jsonrpc: '2.0',
@@ -479,7 +317,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
479
317
  result: {
480
318
  resources: [{
481
319
  uri: 'memory://brain/context',
482
- name: 'Brain Context',
320
+ name: 'Cortex MCP Context',
483
321
  description: 'Top memories — corrections, decisions, conventions. Read this before every response.',
484
322
  mimeType: 'text/plain',
485
323
  }],
@@ -506,6 +344,97 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
506
344
  error: { code: -32602, message: `Unknown resource: ${uri}` },
507
345
  };
508
346
  }
347
+ case 'prompts/list':
348
+ return {
349
+ jsonrpc: '2.0', id,
350
+ result: {
351
+ prompts: [
352
+ {
353
+ name: 'cortex-review',
354
+ description: 'Review code against stored conventions, past bugs, and project decisions. Returns specific violations.',
355
+ arguments: [
356
+ { name: 'code', description: 'The code to review', required: true },
357
+ { name: 'filename', description: 'Filename for context-aware review', required: false },
358
+ ],
359
+ },
360
+ {
361
+ name: 'cortex-debug',
362
+ description: 'Debug an issue using Cortex memory. Checks for similar past bugs, failed attempts, and gotchas.',
363
+ arguments: [
364
+ { name: 'error', description: 'The error message or issue description', required: true },
365
+ { name: 'file', description: 'The file where the error occurs', required: false },
366
+ ],
367
+ },
368
+ {
369
+ name: 'cortex-new-feature',
370
+ description: 'Pre-flight checklist before building a new feature. Gets conventions, gotchas, and architecture context.',
371
+ arguments: [
372
+ { name: 'feature', description: 'What feature you plan to build', required: true },
373
+ { name: 'files', description: 'Files you plan to modify', required: false },
374
+ ],
375
+ },
376
+ ],
377
+ },
378
+ };
379
+ case 'prompts/get': {
380
+ const promptName = rpc.params?.name;
381
+ const promptArgs = rpc.params?.arguments || {};
382
+ if (promptName === 'cortex-review') {
383
+ return {
384
+ jsonrpc: '2.0', id,
385
+ result: {
386
+ description: 'Code review against Cortex memory',
387
+ messages: [
388
+ {
389
+ role: 'user',
390
+ content: {
391
+ type: 'text',
392
+ 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.`,
393
+ },
394
+ },
395
+ ],
396
+ },
397
+ };
398
+ }
399
+ if (promptName === 'cortex-debug') {
400
+ return {
401
+ jsonrpc: '2.0', id,
402
+ result: {
403
+ description: 'Debug with Cortex memory context',
404
+ messages: [
405
+ {
406
+ role: 'user',
407
+ content: {
408
+ type: 'text',
409
+ 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.`,
410
+ },
411
+ },
412
+ ],
413
+ },
414
+ };
415
+ }
416
+ if (promptName === 'cortex-new-feature') {
417
+ return {
418
+ jsonrpc: '2.0', id,
419
+ result: {
420
+ description: 'New feature pre-flight checklist',
421
+ messages: [
422
+ {
423
+ role: 'user',
424
+ content: {
425
+ type: 'text',
426
+ 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.`,
427
+ },
428
+ },
429
+ ],
430
+ },
431
+ };
432
+ }
433
+ return {
434
+ jsonrpc: '2.0', id,
435
+ error: { code: -32602, message: `Unknown prompt: ${promptName}` },
436
+ };
437
+ }
509
438
  case 'tools/call': {
510
439
  const toolName = rpc.params?.name;
511
440
  const args = rpc.params?.arguments || {};
@@ -516,10 +445,10 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
516
445
  result: { content: [{ type: 'text', text: 'Error: query too long (max 1000 chars)' }], isError: true },
517
446
  };
518
447
  }
519
- if (args.content && typeof args.content === 'string' && args.content.length > 5000) {
448
+ if (args.content && typeof args.content === 'string' && args.content.length > 50000) {
520
449
  return {
521
450
  jsonrpc: '2.0', id,
522
- result: { content: [{ type: 'text', text: 'Error: content too long (max 5000 chars)' }], isError: true },
451
+ result: { content: [{ type: 'text', text: 'Error: content too long (max 50000 chars)' }], isError: true },
523
452
  };
524
453
  }
525
454
  if (toolName === 'recall_memory') {
@@ -562,58 +491,25 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
562
491
  return handleAutoLearn(id, args);
563
492
  }
564
493
  else if (toolName === 'export_memories') {
565
- return handleExportMemoriesFn(memoryStore, id, args);
494
+ return handleExportMemories(id);
566
495
  }
567
496
  else if (toolName === 'import_memories') {
568
- return handleImportMemoriesFn(memoryStore, memory_cache_1, id, args);
569
- }
570
- else if (toolName === 'get_related_memories') {
571
- return handleGetRelatedMemoriesFn(memoryStore, id, args);
497
+ return handleImportMemories(id, args);
572
498
  }
573
499
  else if (toolName === 'health_check') {
574
500
  return handleHealthCheck(id);
575
501
  }
576
- else if (toolName === 'search_by_file') {
577
- return handleSearchByFile(id, args);
578
- }
579
- else if (toolName === 'pin_memory') {
580
- return handlePinMemory(id, args);
581
- }
582
- else if (toolName === 'unpin_memory') {
583
- return handleUnpinMemory(id, args);
584
- }
585
- else if (toolName === 'undo_last') {
586
- return handleUndoLast(id, args);
587
- }
588
- else if (toolName === 'clear_memories') {
589
- return handleClearMemories(id, args);
590
- }
591
- else if (toolName === 'search_timeline') {
592
- return handleSearchTimeline(id, args);
593
- }
594
- else if (toolName === 'thumbs_up') {
595
- return handleThumbsUp(id, args);
502
+ else if (toolName === 'review_code') {
503
+ return handleReviewCode(id, args);
596
504
  }
597
- else if (toolName === 'thumbs_down') {
598
- return handleThumbsDown(id, args);
505
+ else if (toolName === 'pre_check') {
506
+ return handlePreCheck(id, args);
599
507
  }
600
- else if (toolName === 'backup_brain') {
601
- return handleBackupBrain(id);
508
+ else if (toolName === 'check_impact') {
509
+ return handleCheckImpact(id, args);
602
510
  }
603
- else if (toolName === 'get_daily_summary') {
604
- return handleGetDailySummary(id, args);
605
- }
606
- else if (toolName === 'analytics') {
607
- return handleAnalytics(id);
608
- }
609
- else if (toolName === 'repair_brain') {
610
- return handleRepairBrain(id);
611
- }
612
- else if (toolName === 'detect_conflicts') {
613
- return handleDetectConflicts(id);
614
- }
615
- else if (toolName === 'suggest_cleanup') {
616
- return handleSuggestCleanup(id, args);
511
+ else if (toolName === 'resume_work') {
512
+ return handleResumeWork(id);
617
513
  }
618
514
  else {
619
515
  return {
@@ -666,27 +562,65 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
666
562
  if (ftsResults.length === 0) {
667
563
  ftsResults = memoryStore.searchFTS(queryText, maxResults * 2);
668
564
  }
565
+ // OpenClaw: Try alternative queries if still 0 results
566
+ if (ftsResults.length === 0) {
567
+ const altQueries = (0, query_expansion_1.generateAlternativeQueries)(queryText);
568
+ for (const alt of altQueries.slice(1)) { // skip first (original)
569
+ ftsResults = memoryStore.searchFTS(alt, maxResults * 2);
570
+ if (ftsResults.length > 0)
571
+ break;
572
+ }
573
+ }
669
574
  console.log(` [FTS] ${ftsResults.length} results`);
670
- // 2. Vector Search (if worker ready)
575
+ // 2. Vector Search (if worker ready) — with OpenClaw-style embedding cache
671
576
  let vectorResults = [];
672
577
  if ((0, embedding_manager_1.isWorkerReady)()) {
673
- const embedding = await (0, embedding_manager_1.embedText)(queryText);
674
- vectorResults = memoryStore.searchVector(new Float32Array(embedding), maxResults * 2);
578
+ // Check cache first (OpenClaw pattern)
579
+ let embeddingArr = (0, embedding_cache_1.getCachedEmbedding)(queryText);
580
+ if (!embeddingArr) {
581
+ embeddingArr = await (0, embedding_manager_1.embedText)(queryText);
582
+ (0, embedding_cache_1.cacheEmbedding)(queryText, embeddingArr);
583
+ }
584
+ vectorResults = memoryStore.searchVector(new Float32Array(embeddingArr), maxResults * 2);
675
585
  if (vectorResults.length > 0) {
676
586
  console.log(` [VECTOR] ${vectorResults.length} results`);
677
587
  }
678
588
  }
679
589
  // 3. Hybrid Ranking
680
590
  const rawRanked = (0, memory_ranker_1.rankResults)(ftsResults, vectorResults, maxResults * 2, currentFile);
681
- // Map RankedResult to ScoredMemory
682
- ranked = rawRanked.map(r => ({
591
+ // 3a. Apply MMR re-ranking (OpenClaw-style diversity)
592
+ const mmrRanked = (0, mmr_reranker_1.applyMMR)(rawRanked.map(r => ({
683
593
  ...r,
684
594
  matchMethod: 'hybrid'
685
- }));
686
- // 3b. Context-based boost (if current file provided)
687
- if (args.currentFile) {
688
- // Boost memories related to this file or its directory
689
- // Implementation in memory-ranker.ts (pending)
595
+ })));
596
+ // Map to ScoredMemory
597
+ ranked = mmrRanked;
598
+ // 3b. Project-aware boost (memories from current project rank higher)
599
+ if (workspaceRoot) {
600
+ try {
601
+ const pkgPath = require('path').join(workspaceRoot, 'package.json');
602
+ let projectTag = '';
603
+ if (require('fs').existsSync(pkgPath)) {
604
+ const pkg = JSON.parse(require('fs').readFileSync(pkgPath, 'utf-8'));
605
+ projectTag = (pkg.name || '').toLowerCase();
606
+ }
607
+ if (!projectTag)
608
+ projectTag = require('path').basename(workspaceRoot).toLowerCase();
609
+ if (projectTag) {
610
+ ranked = ranked.map(r => {
611
+ const tags = r.memory.tags || [];
612
+ const hasProjectTag = tags.some(t => t.toLowerCase().includes(projectTag));
613
+ const hasDiffProject = tags.some(t => t.startsWith('project:') && !t.toLowerCase().includes(projectTag));
614
+ if (hasProjectTag)
615
+ return { ...r, score: r.score * 1.3 };
616
+ if (hasDiffProject)
617
+ return { ...r, score: r.score * 0.7 };
618
+ return r;
619
+ });
620
+ ranked.sort((a, b) => b.score - a.score);
621
+ }
622
+ }
623
+ catch { /* project detection failed — skip boost */ }
690
624
  }
691
625
  // 3c. Apply attention-based re-ranking (debugging→bugs, coding→conventions)
692
626
  const recallContext = (0, attention_ranker_1.detectActionContext)(queryText, currentFile);
@@ -719,14 +653,16 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
719
653
  ranked = ranked.slice(0, maxResults);
720
654
  // 4. Touch for access tracking (reinforcement — used memories get stronger)
721
655
  if (ranked.length > 0) {
722
- await Promise.all(ranked.map(m => {
723
- try {
724
- return memoryStore.touch(m.memory.id);
725
- }
726
- catch {
727
- return Promise.resolve();
656
+ memoryStore.runTransaction(() => {
657
+ for (const m of ranked) {
658
+ try {
659
+ memoryStore.touch(m.memory.id);
660
+ }
661
+ catch { /* non-fatal */ }
728
662
  }
729
- }));
663
+ });
664
+ // NEW: Record access patterns for personalized boosting
665
+ (0, access_pattern_tracker_1.recordBatchAccess)(ranked.map(r => ({ type: r.memory.type })));
730
666
  (0, confidence_decay_1.runDecayMaintenance)(memoryStore); // Opportunistic decay
731
667
  }
732
668
  (0, memory_cache_1.setCache)(cacheKey, ranked);
@@ -779,7 +715,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
779
715
  }
780
716
  try {
781
717
  // License check — gate memory storage
782
- const activeCount = memoryStore.getActive(9999).length;
718
+ const activeCount = memoryStore.activeCount();
783
719
  const storeCheck = (0, feature_gate_1.canStoreMemory)(activeCount);
784
720
  if (!storeCheck.allowed) {
785
721
  return {
@@ -836,25 +772,24 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
836
772
  }
837
773
  }
838
774
  catch { /* non-fatal */ }
839
- // Auto-edge creationlink to recent memories of same type/files
775
+ // Smart cross-memory linkingconnects by files, tags, AND word overlap
840
776
  try {
841
- const recent = memoryStore.getByType(memType, 5);
842
- for (const r of recent) {
843
- if (r.id !== memory.id) {
844
- memoryStore.addEdge({
845
- sourceId: memory.id,
846
- targetId: r.id,
847
- relation: 'related_to',
848
- weight: 0.5,
849
- timestamp: Date.now(),
850
- });
851
- break; // Link to most recent only
852
- }
853
- }
777
+ const linksCreated = (0, cross_memory_linker_1.autoLinkMemory)(memoryStore, memory);
778
+ if (linksCreated > 0)
779
+ console.log(` [LINK] Auto-linked ${linksCreated} related memories`);
854
780
  }
855
781
  catch { /* non-fatal */ }
856
782
  // Feed session tracker
857
783
  (0, session_tracker_1.feedSession)({ decision: `[${type}] ${sanitized.slice(0, 60)}` });
784
+ // OpenClaw: Log to daily diary (store_memory path)
785
+ try {
786
+ (0, daily_diary_1.appendDiaryEntry)({
787
+ type: type.toLowerCase().replace('_', '_'),
788
+ content: sanitized.slice(0, 100),
789
+ file: files?.[0],
790
+ });
791
+ }
792
+ catch { /* diary is non-critical */ }
858
793
  // Queue background embedding
859
794
  if ((0, embedding_manager_1.isWorkerReady)()) {
860
795
  const embedText_ = [sanitized, reason || ''].join(' ').trim();
@@ -877,6 +812,15 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
877
812
  }
878
813
  }
879
814
  function handleGetStats(id) {
815
+ const health = (0, usage_stats_1.calculateBrainHealth)(memoryStore);
816
+ const lifetime = (0, usage_stats_1.getLifetimeStats)();
817
+ const streak = (0, usage_stats_1.getStreakDisplay)();
818
+ let llmProvider = 'none';
819
+ try {
820
+ if ((0, llm_enhancer_1.isLLMAvailable)())
821
+ llmProvider = (0, llm_enhancer_1.getLLMProvider)();
822
+ }
823
+ catch { /* */ }
880
824
  return {
881
825
  jsonrpc: '2.0', id,
882
826
  result: {
@@ -888,6 +832,16 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
888
832
  totalEvents: eventLog.count(),
889
833
  vectorSearchReady: (0, embedding_manager_1.isWorkerReady)(),
890
834
  cacheSize: (0, memory_cache_1.cacheSize)(),
835
+ brainHealth: { score: health.score, grade: health.grade, tips: health.tips },
836
+ savedYouCount: lifetime.savedYouCount,
837
+ totalSessions: lifetime.totalSessions,
838
+ timeSaved: lifetime.totalMemoriesServed * 15 + lifetime.totalHallucationsCaught * 300,
839
+ streak: streak || 'Day 1',
840
+ longestStreak: lifetime.longestStreak || 0,
841
+ totalAutoLearns: lifetime.totalAutoLearns,
842
+ successPatternsLearned: lifetime.totalSuccessPatterns || 0,
843
+ errorsLearned: lifetime.totalErrorsLearned || 0,
844
+ llmProvider,
891
845
  }, null, 2),
892
846
  }],
893
847
  },
@@ -917,16 +871,46 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
917
871
  extraMemories += (0, architecture_graph_1.storeArchitectureGraph)(memoryStore, archGraph);
918
872
  }
919
873
  catch { /* non-fatal */ }
874
+ // Convention auto-detection — analyze actual code patterns
875
+ try {
876
+ const conventions = (0, convention_detector_1.detectConventions)(root);
877
+ for (const conv of conventions) {
878
+ try {
879
+ (0, memory_quality_1.storeWithQuality)(memoryStore, {
880
+ type: 'CONVENTION',
881
+ intent: conv.pattern,
882
+ action: conv.evidence,
883
+ reason: `Auto-detected from code (${conv.category}, confidence: ${conv.confidence})`,
884
+ confidence: conv.confidence,
885
+ importance: conv.confidence,
886
+ tags: ['convention', 'auto-detected', conv.category],
887
+ });
888
+ extraMemories++;
889
+ }
890
+ catch { /* skip duplicates */ }
891
+ }
892
+ }
893
+ catch { /* non-fatal */ }
920
894
  (0, memory_cache_1.invalidateCache)();
895
+ (0, usage_stats_1.trackScan)();
921
896
  const total = count + extraMemories;
897
+ // Report knowledge gaps after scan
898
+ let gapReport = '';
899
+ try {
900
+ const gaps = (0, meta_memory_1.detectKnowledgeGaps)(memoryStore, root);
901
+ if (gaps.length > 0) {
902
+ gapReport = `\n\n${(0, meta_memory_1.formatKnowledgeGaps)(gaps)}`;
903
+ }
904
+ }
905
+ catch { /* non-fatal */ }
922
906
  return {
923
907
  jsonrpc: '2.0', id,
924
908
  result: {
925
909
  content: [{
926
910
  type: 'text',
927
911
  text: total > 0
928
- ? `Project scanned successfully. ${total} memories created (stack, structure, config, git history, export map, architecture graph).`
929
- : 'Project was already scanned. No new memories created.',
912
+ ? `Project scanned successfully. ${total} memories created (stack, structure, config, git history, export map, architecture graph, coding conventions).${gapReport}`
913
+ : `Project was already scanned. No new memories created.${gapReport}`,
930
914
  }],
931
915
  },
932
916
  };
@@ -1002,6 +986,9 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1002
986
  };
1003
987
  }
1004
988
  try {
989
+ // Track file for relationship mapping
990
+ if (args.filename)
991
+ (0, file_relationships_1.recordFileEdit)(args.filename);
1005
992
  const result = (0, code_verifier_1.verifyCode)(args.code, root);
1006
993
  const lines = [];
1007
994
  // Imports
@@ -1028,6 +1015,20 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1028
1015
  for (const [file, available] of Object.entries(result.exports.available)) {
1029
1016
  lines.push(` ${file} exports: ${available.join(', ')}`);
1030
1017
  }
1018
+ // Smart fix suggestions — find closest real exports
1019
+ try {
1020
+ const wsRoot = args.workspaceRoot || workspaceRoot;
1021
+ if (wsRoot) {
1022
+ const exportMap = (0, export_map_1.buildExportMap)(wsRoot);
1023
+ for (const invalid of result.exports.invalid) {
1024
+ const suggestions = (0, export_map_2.suggestRealExport)(exportMap, invalid);
1025
+ if (suggestions.length > 0) {
1026
+ lines.push(` 💡 Did you mean: ${suggestions.slice(0, 3).join(', ')}?`);
1027
+ }
1028
+ }
1029
+ }
1030
+ }
1031
+ catch { /* non-fatal */ }
1031
1032
  }
1032
1033
  }
1033
1034
  // Env vars
@@ -1046,6 +1047,12 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1046
1047
  if (lines.length === 0) {
1047
1048
  lines.push('No imports, exports, or env vars detected in the code.');
1048
1049
  }
1050
+ // Track hallucination catches for usage stats
1051
+ const catchCount = result.imports.invalid.length + result.exports.invalid.length + result.envVars.invalid.length;
1052
+ if (catchCount > 0) {
1053
+ for (let i = 0; i < catchCount; i++)
1054
+ (0, usage_stats_1.trackCatch)();
1055
+ }
1049
1056
  return {
1050
1057
  jsonrpc: '2.0', id,
1051
1058
  result: { content: [{ type: 'text', text: lines.join('\n') }] },
@@ -1067,7 +1074,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1067
1074
  result: { content: [{ type: 'text', text: `[WARN] Rate limited: ${rateCheck.reason}` }], isError: true },
1068
1075
  };
1069
1076
  }
1070
- const text = (args.content || args.memory)?.trim();
1077
+ const text = args.memory?.trim();
1071
1078
  if (!text || text.length < 5) {
1072
1079
  return {
1073
1080
  jsonrpc: '2.0', id,
@@ -1129,35 +1136,108 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1129
1136
  }
1130
1137
  async function handleForceRecall(id, args) {
1131
1138
  try {
1139
+ // Fix #17: Cache force_recall with short TTL to avoid redundant rebuilds
1140
+ const cacheKey = `force_recall:${args.topic || ''}:${args.currentFile || ''}`;
1141
+ const cached = (0, memory_cache_1.getCached)(cacheKey);
1142
+ if (cached) {
1143
+ console.log(` [CACHE] force_recall hit`);
1144
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: cached }] } };
1145
+ }
1132
1146
  const parts = [];
1133
1147
  // ─── BRAIN LAYER 0: End previous session + start new one ─────────
1134
1148
  (0, session_tracker_1.endSession)(memoryStore); // Save previous session summary
1149
+ (0, file_relationships_1.storeRelationships)(memoryStore); // Persist file co-edit relationships
1135
1150
  (0, session_tracker_1.startSession)();
1136
- const topic = args.topic || args.query || '';
1137
- if (topic)
1138
- (0, session_tracker_1.feedSession)({ topic });
1139
- // ─── BRAIN LAYER 1: Maintenance (runs in background) ─────────────
1140
- try {
1141
- // Decay old unused memories
1142
- (0, confidence_decay_1.runDecayMaintenance)(memoryStore);
1143
- // Boost frequently corrected topics
1144
- (0, learning_rate_1.boostFrequentCorrections)(memoryStore);
1145
- // Consolidate similar memories if needed
1146
- if ((0, memory_consolidator_1.shouldConsolidate)(memoryStore)) {
1147
- (0, memory_consolidator_1.consolidateMemories)(memoryStore);
1151
+ (0, usage_stats_1.resetSessionStats)();
1152
+ if (args.topic)
1153
+ (0, session_tracker_1.feedSession)({ topic: args.topic });
1154
+ // ─── SOUL LAYER (OpenClaw-style identity) ────────────────────────
1155
+ if (workspaceRoot) {
1156
+ try {
1157
+ (0, soul_manager_1.initSoul)(workspaceRoot);
1158
+ const soulContext = (0, soul_manager_1.formatSoul)(workspaceRoot);
1159
+ if (soulContext)
1160
+ parts.push(soulContext);
1148
1161
  }
1162
+ catch { /* soul is non-critical */ }
1149
1163
  }
1150
- catch { /* maintenance errors are non-fatal */ }
1151
- // ─── BRAIN LAYER 1.5: PINNED MEMORIES (Critical Rules) ───────────
1152
- // Scan ALL memories for pinned — critical rules must never be missed
1153
- const pinnedMemories = memoryStore.getActive(10000).filter(m => (m.tags || []).includes('pinned'));
1154
- if (pinnedMemories.length > 0) {
1155
- parts.push('\n## 📌 PINNED CRITICAL RULES (Must Follow)');
1156
- for (const p of pinnedMemories) {
1157
- parts.push(`- **[${p.type}]** ${p.intent}`);
1164
+ // ─── Project Detection (for project isolation) ───────────────────
1165
+ let projectName = '';
1166
+ if (workspaceRoot) {
1167
+ try {
1168
+ const pkgPath = require('path').join(workspaceRoot, 'package.json');
1169
+ if (require('fs').existsSync(pkgPath)) {
1170
+ const pkg = JSON.parse(require('fs').readFileSync(pkgPath, 'utf-8'));
1171
+ projectName = pkg.name || require('path').basename(workspaceRoot);
1172
+ }
1173
+ else {
1174
+ projectName = require('path').basename(workspaceRoot);
1175
+ }
1176
+ }
1177
+ catch {
1178
+ projectName = require('path').basename(workspaceRoot || '');
1179
+ }
1180
+ if (projectName) {
1181
+ (0, session_tracker_1.feedSession)({ project: projectName });
1158
1182
  }
1159
1183
  }
1160
- // ─── BRAIN LAYER 2: Attention Context ────────────────────────────
1184
+ // ─── BRAIN LAYER 0.5: Auto-scan on first run (Day 1 Empty Brain fix) ───
1185
+ if (memoryStore.activeCount() === 0 && workspaceRoot) {
1186
+ try {
1187
+ console.log(` [AUTO-SCAN] First run detected — scanning project...`);
1188
+ const scanner = new project_scanner_1.ProjectScanner(memoryStore, workspaceRoot);
1189
+ const scanCount = await scanner.scan();
1190
+ let extraMemories = 0;
1191
+ try {
1192
+ extraMemories += (0, export_map_1.storeExportMap)(memoryStore, (0, export_map_1.buildExportMap)(workspaceRoot));
1193
+ }
1194
+ catch { }
1195
+ try {
1196
+ extraMemories += (0, architecture_graph_1.storeArchitectureGraph)(memoryStore, (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot));
1197
+ }
1198
+ catch { }
1199
+ const total = scanCount + extraMemories;
1200
+ if (total > 0) {
1201
+ 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.`);
1202
+ (0, memory_cache_1.invalidateCache)();
1203
+ (0, usage_stats_1.trackScan)();
1204
+ }
1205
+ }
1206
+ catch (scanErr) {
1207
+ console.log(` [AUTO-SCAN] Failed: ${scanErr.message}`);
1208
+ }
1209
+ }
1210
+ // ─── BRAIN LAYER 1: Maintenance (runs in background) ─────────────
1211
+ try {
1212
+ // Decay old unused memories
1213
+ (0, confidence_decay_1.runDecayMaintenance)(memoryStore);
1214
+ // Boost frequently corrected topics
1215
+ (0, learning_rate_1.boostFrequentCorrections)(memoryStore);
1216
+ // Consolidate similar memories if needed
1217
+ if ((0, memory_consolidator_1.shouldConsolidate)(memoryStore)) {
1218
+ (0, memory_consolidator_1.consolidateMemories)(memoryStore);
1219
+ }
1220
+ // OpenClaw: Auto-learn soul from high-access memories
1221
+ if (workspaceRoot) {
1222
+ const topMemories = memoryStore.getActive(50).map(m => ({
1223
+ type: m.type, intent: m.intent, accessCount: m.accessCount
1224
+ }));
1225
+ (0, soul_manager_1.autoLearnSoul)(workspaceRoot, topMemories);
1226
+ }
1227
+ // OpenClaw: Load access profile for personalized ranking
1228
+ (0, access_pattern_tracker_1.loadAccessProfile)(memoryStore);
1229
+ // OpenClaw: Auto-import user edits from MEMORY.md
1230
+ if (workspaceRoot) {
1231
+ try {
1232
+ const imported = (0, memory_export_md_1.importMemoryMd)(workspaceRoot, memoryStore);
1233
+ if (imported > 0)
1234
+ console.log(` [MEMORY.md] Imported ${imported} user-edited memories`);
1235
+ }
1236
+ catch { /* non-fatal */ }
1237
+ }
1238
+ }
1239
+ catch { /* maintenance errors are non-fatal */ }
1240
+ // ─── BRAIN LAYER 2: Attention Context ────────────────────────────
1161
1241
  const actionContext = (0, feature_gate_1.isFeatureAllowed)('attentionRanking') ? (0, attention_ranker_1.detectActionContext)(args.topic, args.currentFile) : {};
1162
1242
  const attentionLabel = (0, feature_gate_1.isFeatureAllowed)('attentionRanking') ? (0, attention_ranker_1.formatAttentionContext)(actionContext) : '';
1163
1243
  if (attentionLabel)
@@ -1169,28 +1249,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1169
1249
  for (const s of sessions)
1170
1250
  parts.push(s);
1171
1251
  }
1172
- // ─── BRAIN LAYER 3.5: What's New Since Last Session ──────────────
1173
- try {
1174
- const db = memoryStore.db || memoryStore.connection;
1175
- const oneDayAgo = Date.now() - 86400000;
1176
- const recentNew = db.prepare(
1177
- 'SELECT type, intent FROM memory_units WHERE is_active = 1 AND created_at > ? ORDER BY created_at DESC LIMIT 10'
1178
- ).all(oneDayAgo);
1179
- if (recentNew.length > 0) {
1180
- const typeCounts = {};
1181
- for (const m of recentNew) {
1182
- typeCounts[m.type] = (typeCounts[m.type] || 0) + 1;
1183
- }
1184
- const summary = Object.entries(typeCounts).map(([t, c]) => `${c} ${t.toLowerCase()}${c > 1 ? 's' : ''}`).join(', ');
1185
- parts.push(`\n## 🆕 Since Last Session\n_${recentNew.length} new memories (${summary})_`);
1186
- for (const m of recentNew.slice(0, 5)) {
1187
- parts.push(`- [${m.type}] ${m.intent}`);
1188
- }
1189
- if (recentNew.length > 5) {
1190
- parts.push(`_...and ${recentNew.length - 5} more_`);
1191
- }
1192
- }
1193
- } catch { /* non-fatal */ }
1194
1252
  // ─── BRAIN LAYER 4: Hot Corrections (learning rate) ──────────────
1195
1253
  const hotCorrections = (0, learning_rate_1.formatHotCorrections)(memoryStore);
1196
1254
  if (hotCorrections)
@@ -1202,149 +1260,335 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1202
1260
  maxChars: 8000, // leave room for brain layers
1203
1261
  });
1204
1262
  parts.push(fullContext);
1205
- // ─── BRAIN LAYER 6: Anticipation (file-aware proactive recall) ───
1206
- if (args.currentFile && (0, feature_gate_1.isFeatureAllowed)('anticipation')) {
1207
- const anticipated = (0, anticipation_engine_1.formatAnticipation)((0, anticipation_engine_1.anticipate)(memoryStore, args.currentFile));
1208
- if (anticipated)
1209
- parts.push('\n' + anticipated);
1210
- }
1211
- // ─── BRAIN LAYER 7: Temporal Context (what changed recently) ─────
1212
- if ((0, feature_gate_1.isFeatureAllowed)('temporalContext')) {
1213
- const temporal = (0, temporal_engine_1.formatTemporalContext)(memoryStore);
1214
- if (temporal)
1215
- parts.push('\n' + temporal);
1216
- }
1217
- // ─── BRAIN LAYER 8: Workspace State (git changes) ────────────────
1218
- try {
1219
- const workspace = (0, temporal_engine_1.getWorkspaceDiff)(workspaceRoot || '');
1220
- if (workspace)
1221
- parts.push('\n' + workspace);
1222
- }
1223
- catch { /* git not available */ }
1224
- // ─── BRAIN LAYER 8.5: Git Memory (commit capture + file changes) ───
1225
- if ((0, feature_gate_1.isFeatureAllowed)('gitMemory')) {
1226
- try {
1227
- // Capture recent commits as memories
1228
- const commitsCaptured = (0, git_memory_1.captureGitCommits)(memoryStore, workspaceRoot || '', 5);
1229
- if (commitsCaptured > 0) {
1230
- parts.push(`\n> Captured ${commitsCaptured} new git commit(s) as memories`);
1263
+ // ─── BRAIN LAYERS 6-12: Run in parallel (independent of each other) ───
1264
+ const parallelResults = await Promise.allSettled([
1265
+ // Layer 6: Anticipation
1266
+ (async () => {
1267
+ if (args.currentFile && (0, feature_gate_1.isFeatureAllowed)('anticipation')) {
1268
+ return (0, anticipation_engine_1.formatAnticipation)((0, anticipation_engine_1.anticipate)(memoryStore, args.currentFile));
1231
1269
  }
1232
- // Show uncommitted file changes
1233
- const fileChanges = (0, git_memory_1.detectFileChanges)(workspaceRoot || '');
1234
- const fileChangeText = (0, git_memory_1.formatFileChanges)(fileChanges);
1235
- if (fileChangeText)
1236
- parts.push('\n' + fileChangeText);
1237
- }
1238
- catch { /* git not available */ }
1239
- } // end isPro() for git memory
1240
- // ─── BRAIN LAYER 9: Topic-Specific Search ────────────────────────
1241
- if (topic) {
1242
- try {
1243
- let ftsResults = memoryStore.searchFTS(topic, 15);
1244
- // Apply confidence decay + attention ranking
1245
- ftsResults = (0, confidence_decay_1.applyConfidenceDecay)(ftsResults);
1246
- ftsResults = (0, attention_ranker_1.rankByAttention)(ftsResults, actionContext);
1247
- // Causal chain: follow graph edges for top results
1248
- const seen = new Set();
1249
- const enriched = [];
1250
- for (const r of ftsResults) {
1251
- if (seen.has(r.memory.id))
1252
- continue;
1253
- seen.add(r.memory.id);
1254
- enriched.push(r);
1255
- // Follow causal links (1 hop)
1256
- try {
1257
- const related = memoryStore.getRelated(r.memory.id, 1, 3);
1258
- for (const rel of related) {
1259
- if (!seen.has(rel.memory.id)) {
1260
- seen.add(rel.memory.id);
1261
- enriched.push({ ...rel, score: rel.score * 0.7 });
1270
+ return '';
1271
+ })(),
1272
+ // Layer 6.5: Proactive Warnings — ⚠️ for files with past bugs/corrections
1273
+ (async () => {
1274
+ if (!args.currentFile)
1275
+ return '';
1276
+ try {
1277
+ const fileMemories = memoryStore.getByFile(args.currentFile, 50);
1278
+ const warnings = fileMemories.filter((m) => (m.type === 'CORRECTION' || m.type === 'BUG_FIX') && m.is_active);
1279
+ if (warnings.length === 0)
1280
+ return '';
1281
+ const lines = warnings.slice(0, 5).map((m) => `⚠️ **${m.type}**: ${m.intent}${m.reason && !m.reason.startsWith('Auto-detected') ? ` — _${m.reason}_` : ''}`);
1282
+ return `\n## ⚠️ Watch Out (past issues with this file)\n${lines.join('\n')}`;
1283
+ }
1284
+ catch {
1285
+ return '';
1286
+ }
1287
+ })(),
1288
+ // Layer 7: Temporal Context
1289
+ (async () => {
1290
+ if ((0, feature_gate_1.isFeatureAllowed)('temporalContext')) {
1291
+ return (0, temporal_engine_1.formatTemporalContext)(memoryStore);
1292
+ }
1293
+ return '';
1294
+ })(),
1295
+ // Layer 7.5: Daily Diary (OpenClaw-style)
1296
+ (async () => {
1297
+ try {
1298
+ return (0, daily_diary_1.formatDiaryContext)();
1299
+ }
1300
+ catch {
1301
+ return '';
1302
+ }
1303
+ })(),
1304
+ // Layer 8: Workspace State (git)
1305
+ (async () => {
1306
+ try {
1307
+ return (0, temporal_engine_1.getWorkspaceDiff)(workspaceRoot || '');
1308
+ }
1309
+ catch {
1310
+ return '';
1311
+ }
1312
+ })(),
1313
+ // Layer 8.5: Git Memory
1314
+ (async () => {
1315
+ if (!(0, feature_gate_1.isFeatureAllowed)('gitMemory'))
1316
+ return '';
1317
+ try {
1318
+ const commitsCaptured = (0, git_memory_1.captureGitCommits)(memoryStore, workspaceRoot || '', 5);
1319
+ const commitText = commitsCaptured > 0
1320
+ ? `\n> Captured ${commitsCaptured} new git commit(s) as memories`
1321
+ : '';
1322
+ const fileChanges = (0, git_memory_1.detectFileChanges)(workspaceRoot || '');
1323
+ const fileChangeText = (0, git_memory_1.formatFileChanges)(fileChanges);
1324
+ return [commitText, fileChangeText].filter(Boolean).join('\n');
1325
+ }
1326
+ catch {
1327
+ return '';
1328
+ }
1329
+ })(),
1330
+ // Layer 9: Topic Search — Hybrid (FTS + Vector) for deeper relevance
1331
+ (async () => {
1332
+ if (!args.topic)
1333
+ return '';
1334
+ try {
1335
+ // FTS search
1336
+ const ftsResults = memoryStore.searchFTS(args.topic, 15);
1337
+ // Vector search (semantic — catches what FTS misses)
1338
+ let vectorResults = [];
1339
+ if ((0, embedding_manager_1.isWorkerReady)()) {
1340
+ try {
1341
+ const topicEmbedding = await (0, embedding_manager_1.embedText)(args.topic);
1342
+ vectorResults = memoryStore.searchVector(new Float32Array(topicEmbedding), 10);
1343
+ }
1344
+ catch { /* vector search failure is non-fatal */ }
1345
+ }
1346
+ // Merge FTS + Vector, deduplicate by ID
1347
+ const merged = (0, memory_ranker_1.rankResults)(ftsResults, vectorResults, 20, args.currentFile);
1348
+ let ranked = merged.map(r => ({ ...r, matchMethod: 'hybrid' }));
1349
+ ranked = (0, confidence_decay_1.applyConfidenceDecay)(ranked);
1350
+ ranked = (0, attention_ranker_1.rankByAttention)(ranked, actionContext);
1351
+ const seen = new Set();
1352
+ const enriched = [];
1353
+ for (const r of ranked) {
1354
+ if (seen.has(r.memory.id))
1355
+ continue;
1356
+ seen.add(r.memory.id);
1357
+ enriched.push(r);
1358
+ try {
1359
+ const related = memoryStore.getRelated(r.memory.id, 1, 3);
1360
+ for (const rel of related) {
1361
+ if (!seen.has(rel.memory.id)) {
1362
+ seen.add(rel.memory.id);
1363
+ enriched.push({ ...rel, score: rel.score * 0.7 });
1364
+ }
1262
1365
  }
1263
1366
  }
1367
+ catch { }
1264
1368
  }
1265
- catch { }
1266
- }
1267
- if (enriched.length > 0) {
1268
- parts.push('\n## Topic: "' + topic + '"');
1269
- for (const m of enriched.slice(0, 15)) {
1270
- parts.push(`- [${m.memory.type}] ${m.memory.intent}${m.memory.reason ? ` — ${m.memory.reason}` : ''}`);
1369
+ if (enriched.length > 0) {
1370
+ const lines = ['\n## Topic: "' + args.topic + '"'];
1371
+ for (const m of enriched.slice(0, 15)) {
1372
+ lines.push(`- [${m.memory.type}] ${m.memory.intent}${m.memory.reason ? ` ${m.memory.reason}` : ''}`);
1373
+ }
1374
+ return lines.join('\n');
1271
1375
  }
1376
+ return '';
1377
+ }
1378
+ catch {
1379
+ return '\n> Note: Topic search unavailable (FTS index needs rebuild).';
1272
1380
  }
1381
+ })(),
1382
+ // Layer 10: Knowledge Gaps (skip when topic is specific — saves tokens)
1383
+ (async () => {
1384
+ if (args.topic)
1385
+ return ''; // Skip heavy layer for focused queries
1386
+ try {
1387
+ const gaps = (0, meta_memory_1.detectKnowledgeGaps)(memoryStore, workspaceRoot || '');
1388
+ return (0, meta_memory_1.formatKnowledgeGaps)(gaps);
1389
+ }
1390
+ catch {
1391
+ return '';
1392
+ }
1393
+ })(),
1394
+ // Layer 11: Export Map (skip when topic is specific — saves tokens)
1395
+ (async () => {
1396
+ if (args.topic)
1397
+ return ''; // Skip heavy layer for focused queries
1398
+ if (!workspaceRoot || !(0, feature_gate_1.isFeatureAllowed)('exportMap'))
1399
+ return '';
1400
+ try {
1401
+ const exportMap = (0, export_map_1.buildExportMap)(workspaceRoot);
1402
+ return exportMap.totalExports > 0 ? (0, export_map_1.formatExportMap)(exportMap) : '';
1403
+ }
1404
+ catch {
1405
+ return '';
1406
+ }
1407
+ })(),
1408
+ // Layer 12: Architecture Graph (skip when topic is specific — saves tokens)
1409
+ (async () => {
1410
+ if (args.topic)
1411
+ return ''; // Skip heavy layer for focused queries
1412
+ if (!workspaceRoot || !(0, feature_gate_1.isFeatureAllowed)('architectureGraph'))
1413
+ return '';
1414
+ try {
1415
+ const archGraph = (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot);
1416
+ return archGraph.totalFiles > 0 ? (0, architecture_graph_1.formatArchitectureGraph)(archGraph) : '';
1417
+ }
1418
+ catch {
1419
+ return '';
1420
+ }
1421
+ })(),
1422
+ ]);
1423
+ // Collect results (in order) — only push non-empty strings
1424
+ for (const result of parallelResults) {
1425
+ if (result.status === 'fulfilled' && result.value) {
1426
+ parts.push('\n' + result.value);
1273
1427
  }
1274
- catch {
1275
- parts.push('\n> Note: Topic search unavailable (FTS index needs rebuild).');
1428
+ }
1429
+ // ─── SMART CONTEXT SELECTION: Priority-based trimming ────────────
1430
+ // Instead of dumb slicing, trim lowest-priority sections first
1431
+ const MAX_CHARS = 12000; // ~3000 tokens — fits any model
1432
+ let output = parts.join('\n');
1433
+ if (output.length > MAX_CHARS) {
1434
+ // Priority: highest first (kept), lowest first (trimmed)
1435
+ // Layers 0-5 are high priority (welcome, sessions, corrections, core context)
1436
+ // Layers 6-12 are lower priority (anticipation, temporal, git, topic, gaps, exports, arch)
1437
+ // Trim from end (lowest priority) working backwards
1438
+ while (output.length > MAX_CHARS && parts.length > 4) {
1439
+ parts.pop(); // Remove lowest-priority section
1440
+ output = parts.join('\n');
1276
1441
  }
1442
+ if (output.length > MAX_CHARS) {
1443
+ output = output.slice(0, MAX_CHARS);
1444
+ }
1445
+ output += '\n\n> (Some context trimmed to fit token budget. Use `recall_memory` for specific queries.)';
1277
1446
  }
1278
- // ─── BRAIN LAYER 10: Knowledge Gaps (meta-memory) ────────────────
1447
+ // Track usage stats
1448
+ const memoriesInOutput = (output.match(/\[(?:CORRECTION|DECISION|CONVENTION|BUG_FIX|INSIGHT)\]/g) || []).length;
1449
+ (0, usage_stats_1.trackRecall)(memoriesInOutput);
1450
+ // Inject contextual instructions (DO/DON'T/WATCH-OUT)
1279
1451
  try {
1280
- const gaps = (0, meta_memory_1.detectKnowledgeGaps)(memoryStore, workspaceRoot || '');
1281
- const gapText = (0, meta_memory_1.formatKnowledgeGaps)(gaps);
1282
- if (gapText)
1283
- parts.push('\n' + gapText);
1452
+ const instructions = (0, instructions_generator_1.generateInstructions)(memoryStore);
1453
+ const instructionText = (0, instructions_generator_1.formatInstructions)(instructions);
1454
+ if (instructionText)
1455
+ output += '\n\n' + instructionText;
1284
1456
  }
1285
1457
  catch { /* non-fatal */ }
1286
- // ─── BRAIN LAYER 11: Export Map (anti-hallucination) ──────────────
1287
- if (workspaceRoot && (0, feature_gate_1.isFeatureAllowed)('exportMap')) {
1288
- try {
1289
- const exportMap = (0, export_map_1.buildExportMap)(workspaceRoot);
1290
- if (exportMap.totalExports > 0) {
1291
- const exportText = (0, export_map_1.formatExportMap)(exportMap);
1292
- if (exportText)
1293
- parts.push('\n' + exportText);
1458
+ // Inject tool recommendations (what to use based on context)
1459
+ try {
1460
+ const recs = (0, tool_recommender_1.recommendTools)({
1461
+ topic: args.topic,
1462
+ currentFile: args.currentFile,
1463
+ isNewConversation: true,
1464
+ });
1465
+ const recText = (0, tool_recommender_1.formatRecommendations)(recs);
1466
+ if (recText)
1467
+ output += '\n\n' + recText;
1468
+ }
1469
+ catch { /* non-fatal */ }
1470
+ // Inject user preferences (adapts AI behavior)
1471
+ try {
1472
+ const prefText = (0, preference_learner_1.getStoredPreferences)(memoryStore);
1473
+ if (prefText)
1474
+ output += '\n\n' + prefText;
1475
+ }
1476
+ catch { /* non-fatal */ }
1477
+ // Append stats footer (makes value visible — THE KEY TO ADDICTION)
1478
+ const statsFooter = (0, usage_stats_1.formatStatsFooter)(memoryStore);
1479
+ if (statsFooter)
1480
+ output += statsFooter;
1481
+ // Count corrections recalled as "saved you" moments
1482
+ const correctionsRecalled = (output.match(/\[CORRECTION\]/g) || []).length;
1483
+ for (let i = 0; i < correctionsRecalled; i++)
1484
+ (0, usage_stats_1.trackSaved)();
1485
+ // Cache the result for short-lived reuse
1486
+ (0, memory_cache_1.setCache)(cacheKey, output);
1487
+ return {
1488
+ jsonrpc: '2.0', id,
1489
+ result: { content: [{ type: 'text', text: output }] },
1490
+ };
1491
+ }
1492
+ catch (err) {
1493
+ return {
1494
+ jsonrpc: '2.0', id,
1495
+ result: { content: [{ type: 'text', text: `Force recall error: ${err.message}` }], isError: true },
1496
+ };
1497
+ }
1498
+ }
1499
+ // ─── REVIEW CODE: Check against conventions + past bugs ─────────────
1500
+ function handleReviewCode(id, args) {
1501
+ try {
1502
+ const code = args.code;
1503
+ const filename = args.filename || '';
1504
+ if (!code || code.length < 10) {
1505
+ return {
1506
+ jsonrpc: '2.0', id,
1507
+ result: { content: [{ type: 'text', text: 'Error: provide code to review (min 10 chars)' }], isError: true },
1508
+ };
1509
+ }
1510
+ const violations = [];
1511
+ const suggestions = [];
1512
+ const codeLower = code.toLowerCase();
1513
+ // Check against stored CONVENTIONS
1514
+ const conventions = memoryStore.getByType('CONVENTION', 100);
1515
+ for (const conv of conventions) {
1516
+ const intentLower = conv.intent.toLowerCase();
1517
+ // Check for common pattern violations
1518
+ if (intentLower.includes('never use') || intentLower.includes("don't use") || intentLower.includes('avoid')) {
1519
+ // Extract the forbidden thing
1520
+ const match = intentLower.match(/(?:never use|don't use|avoid)\s+(\w+(?:\s+\w+)?)/i);
1521
+ if (match) {
1522
+ const forbidden = match[1].toLowerCase();
1523
+ if (codeLower.includes(forbidden)) {
1524
+ violations.push(`⚠️ **Convention Violation** \`id:${conv.id}\`: "${conv.intent}" — Found \`${forbidden}\` in your code`);
1525
+ }
1294
1526
  }
1295
1527
  }
1296
- catch { /* non-fatal */ }
1297
- }
1298
- // ─── BRAIN LAYER 12: Architecture Graph (deep understanding) ──────
1299
- if (workspaceRoot && (0, feature_gate_1.isFeatureAllowed)('architectureGraph')) {
1300
- try {
1301
- const archGraph = (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot);
1302
- if (archGraph.totalFiles > 0) {
1303
- const archText = (0, architecture_graph_1.formatArchitectureGraph)(archGraph);
1304
- if (archText)
1305
- parts.push('\n' + archText);
1528
+ if (intentLower.includes('always use') || intentLower.includes('must use')) {
1529
+ const match = intentLower.match(/(?:always use|must use)\s+(\w+(?:\s+\w+)?)/i);
1530
+ if (match) {
1531
+ const required = match[1].toLowerCase();
1532
+ if (!codeLower.includes(required) && code.length > 50) {
1533
+ suggestions.push(`💡 **Convention Suggestion** \`id:${conv.id}\`: "${conv.intent}" — Consider using \`${required}\``);
1534
+ }
1306
1535
  }
1307
1536
  }
1308
- catch { /* non-fatal */ }
1309
1537
  }
1310
- // ─── SMART CONTEXT SELECTION: Trim to token budget ───────────────
1311
- // ─── SMART CONTEXT TRIMMING: Remove low-priority sections first ─
1312
- const MAX_CHARS = 12000; // ~3000 tokens — fits any model
1313
- let output;
1314
- if (parts.join('\n').length > MAX_CHARS) {
1315
- // Priority order: keep critical layers, trim optional ones from the end
1316
- // Parts are added in priority order (sessions, corrections, core context first)
1317
- // So we trim from the END (architecture graph, export map, knowledge gaps)
1318
- let trimmedParts = [...parts];
1319
- let totalLen = trimmedParts.join('\n').length;
1320
- while (totalLen > MAX_CHARS && trimmedParts.length > 3) {
1321
- trimmedParts.pop(); // Remove lowest priority section
1322
- totalLen = trimmedParts.join('\n').length;
1323
- }
1324
- output = trimmedParts.join('\n');
1325
- if (output.length > MAX_CHARS) {
1326
- output = output.slice(0, MAX_CHARS);
1538
+ // Check against stored BUG_FIX patterns
1539
+ const bugFixes = memoryStore.getByType('BUG_FIX', 50);
1540
+ for (const bug of bugFixes) {
1541
+ const bugLower = bug.intent.toLowerCase();
1542
+ // Extract key terms from bug description
1543
+ const bugTerms = bugLower.split(/\s+/).filter(w => w.length > 4);
1544
+ const matchCount = bugTerms.filter(t => codeLower.includes(t)).length;
1545
+ if (matchCount >= 3) {
1546
+ violations.push(`🐛 **Similar Bug Pattern** \`id:${bug.id}\`: "${bug.intent}" — This code has similarities to a past bug`);
1327
1547
  }
1328
- output += '\n\n> (Lower-priority context removed to fit token budget. Use `recall_memory` for specific queries.)';
1329
- } else {
1330
- output = parts.join('\n');
1331
1548
  }
1332
- // ─── DAY 1 EMPTY BRAIN: Guide new users ──────────────────────────
1333
- if (memoryStore.activeCount() === 0) {
1334
- return {
1335
- jsonrpc: '2.0', id,
1336
- 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.' }] },
1337
- };
1549
+ // Check for CORRECTION patterns
1550
+ const corrections = memoryStore.getByType('CORRECTION', 50);
1551
+ for (const corr of corrections) {
1552
+ const corrLower = corr.intent.toLowerCase();
1553
+ const corrTerms = corrLower.split(/\s+/).filter(w => w.length > 4);
1554
+ const matchCount = corrTerms.filter(t => codeLower.includes(t)).length;
1555
+ if (matchCount >= 3) {
1556
+ violations.push(`🔄 **Past Correction Applies** \`id:${corr.id}\`: "${corr.intent}"`);
1557
+ }
1558
+ }
1559
+ // File-specific memories
1560
+ if (filename) {
1561
+ const fileMemories = memoryStore.getByFile(filename, 10);
1562
+ for (const fm of fileMemories) {
1563
+ suggestions.push(`📄 **File Note** \`id:${fm.id}\`: "${fm.intent}"`);
1564
+ }
1565
+ }
1566
+ (0, usage_stats_1.trackReview)();
1567
+ const lines = ['# Cortex Code Review\n'];
1568
+ if (violations.length > 0) {
1569
+ lines.push(`## ⚠️ ${violations.length} Issue${violations.length > 1 ? 's' : ''} Found\n`);
1570
+ violations.forEach(v => lines.push(v));
1571
+ }
1572
+ if (suggestions.length > 0) {
1573
+ lines.push(`\n## 💡 ${suggestions.length} Suggestion${suggestions.length > 1 ? 's' : ''}\n`);
1574
+ suggestions.forEach(s => lines.push(s));
1575
+ }
1576
+ if (violations.length === 0 && suggestions.length === 0) {
1577
+ lines.push('✅ **No issues found.** Code looks clean against your stored conventions and past bugs.');
1578
+ lines.push(`\n_Checked against ${conventions.length} conventions, ${bugFixes.length} bug fixes, ${corrections.length} corrections._`);
1579
+ }
1580
+ else {
1581
+ lines.push(`\n_Reviewed against ${conventions.length} conventions, ${bugFixes.length} bug fixes, ${corrections.length} corrections._`);
1338
1582
  }
1339
1583
  return {
1340
1584
  jsonrpc: '2.0', id,
1341
- result: { content: [{ type: 'text', text: output }] },
1585
+ result: { content: [{ type: 'text', text: lines.join('\n') }] },
1342
1586
  };
1343
1587
  }
1344
1588
  catch (err) {
1345
1589
  return {
1346
1590
  jsonrpc: '2.0', id,
1347
- result: { content: [{ type: 'text', text: `Force recall error: ${err.message}` }], isError: true },
1591
+ result: { content: [{ type: 'text', text: `Review error: ${err.message}` }], isError: true },
1348
1592
  };
1349
1593
  }
1350
1594
  }
@@ -1488,7 +1732,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1488
1732
  }
1489
1733
  async function handleAutoLearn(id, args) {
1490
1734
  try {
1491
- // Feature gate check (auto_learn is now free!)
1735
+ // Feature gate (launch mode: all features unlocked)
1492
1736
  if (!(0, feature_gate_1.isFeatureAllowed)('autoLearn')) {
1493
1737
  return {
1494
1738
  jsonrpc: '2.0', id,
@@ -1512,6 +1756,50 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1512
1756
  }
1513
1757
  // Extract memory-worthy patterns (regex-based)
1514
1758
  const extracted = (0, auto_learner_1.extractMemories)(text);
1759
+ (0, usage_stats_1.trackAutoLearn)(); // Track for Brain Health Score + Streak
1760
+ // SUCCESS DETECTION: capture proven approaches
1761
+ try {
1762
+ const successSignals = (0, success_tracker_1.detectSuccess)(text);
1763
+ for (const signal of successSignals) {
1764
+ const successMemory = (0, success_tracker_1.buildSuccessMemory)(signal, text);
1765
+ extracted.push({
1766
+ type: 'INSIGHT',
1767
+ content: successMemory.intent,
1768
+ confidence: signal.confidence,
1769
+ reason: successMemory.reason,
1770
+ });
1771
+ (0, usage_stats_1.trackSuccess)();
1772
+ }
1773
+ }
1774
+ catch { /* success detection failed — non-fatal */ }
1775
+ // ERROR FINGERPRINT: capture error patterns for instant recall
1776
+ try {
1777
+ if ((0, error_learner_1.containsErrors)(text)) {
1778
+ const errorPatterns = (0, error_learner_1.extractErrorPatterns)(text);
1779
+ for (const ep of errorPatterns.slice(0, 3)) {
1780
+ extracted.push({
1781
+ type: 'BUG_FIX',
1782
+ content: ep.message,
1783
+ confidence: ep.confidence,
1784
+ reason: `Error pattern: ${ep.errorType} — auto-captured for instant fix recall`,
1785
+ });
1786
+ (0, usage_stats_1.trackErrorLearned)();
1787
+ }
1788
+ }
1789
+ }
1790
+ catch { /* error learning failed — non-fatal */ }
1791
+ // COMPLETION DETECTION: demote old memories about completed topics
1792
+ try {
1793
+ const completionSignals = (0, completion_resolver_1.detectCompletion)(text);
1794
+ for (const signal of completionSignals) {
1795
+ const resolved = (0, completion_resolver_1.resolveRelatedMemories)(memoryStore, signal.topic, signal.confidence);
1796
+ if (resolved > 0) {
1797
+ console.log(` 🏁 Completion: "${signal.topic}" — demoted ${resolved} old memories`);
1798
+ (0, memory_cache_1.invalidateCache)();
1799
+ }
1800
+ }
1801
+ }
1802
+ catch { /* completion detection failed — non-fatal */ }
1515
1803
  // LLM enhancement: when API key is available and regex found nothing,
1516
1804
  // use LLM to catch implicit patterns that keywords miss
1517
1805
  if (extracted.length === 0 && (0, llm_enhancer_1.isLLMAvailable)() && text.length > 50) {
@@ -1539,6 +1827,62 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1539
1827
  };
1540
1828
  }
1541
1829
  // Store each extracted memory + feed session tracker
1830
+ // --- Helper: Independent importance scoring ---
1831
+ function calculateImportance(item) {
1832
+ // Base importance by type (corrections/bugs are more important than insights)
1833
+ const TYPE_IMPORTANCE = {
1834
+ CORRECTION: 0.85, BUG_FIX: 0.85, CONVENTION: 0.80,
1835
+ DECISION: 0.75, GOTCHA: 0.75, BUSINESS_RULE: 0.70,
1836
+ FAILED_ATTEMPT: 0.65, CURRENT_TASK: 0.60, INSIGHT: 0.55,
1837
+ };
1838
+ let importance = TYPE_IMPORTANCE[item.type] || 0.60;
1839
+ // Boost for content signals that indicate higher value
1840
+ if (/\b(always|never|must|critical|important|breaking)\b/i.test(item.content))
1841
+ importance += 0.08;
1842
+ if (/\b(error|bug|crash|fail|exception)\b/i.test(item.content))
1843
+ importance += 0.05;
1844
+ if (/\.(ts|js|py|go|rs|java|tsx|jsx)\b/.test(item.content))
1845
+ importance += 0.03; // file-specific
1846
+ if (/v?\d+\.\d+/.test(item.content))
1847
+ importance += 0.02; // version numbers
1848
+ // Blend with regex confidence (40% type-based, 60% regex confidence)
1849
+ importance = importance * 0.4 + item.confidence * 0.6;
1850
+ return Math.min(importance, 1.0);
1851
+ }
1852
+ // --- Helper: Extract topic tags from content ---
1853
+ function extractTopicTags(item) {
1854
+ const tags = [item.type.toLowerCase()];
1855
+ const content = item.content.toLowerCase();
1856
+ // Extract technology/framework mentions
1857
+ 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;
1858
+ const techMatches = item.content.match(techPatterns);
1859
+ if (techMatches) {
1860
+ for (const tech of new Set(techMatches.map(t => t.toLowerCase()))) {
1861
+ tags.push(tech);
1862
+ }
1863
+ }
1864
+ // Extract file extensions as topic hints
1865
+ const fileExts = content.match(/\.(ts|js|py|go|rs|java|tsx|jsx|css|html|json|yaml|yml|md)\b/g);
1866
+ if (fileExts) {
1867
+ for (const ext of new Set(fileExts)) {
1868
+ tags.push(ext.replace('.', ''));
1869
+ }
1870
+ }
1871
+ // Extract key action verbs as context
1872
+ if (/\b(migrat|switch|chang|replac|upgrad|delet|remov)\w*/i.test(content))
1873
+ tags.push('migration');
1874
+ if (/\b(test|spec|assert|expect|mock)\b/i.test(content))
1875
+ tags.push('testing');
1876
+ if (/\b(deploy|ci|cd|pipeline|build|release)\b/i.test(content))
1877
+ tags.push('devops');
1878
+ if (/\b(auth|login|token|session|permission|role)\b/i.test(content))
1879
+ tags.push('auth');
1880
+ if (/\b(database|query|schema|table|index|sql)\b/i.test(content))
1881
+ tags.push('database');
1882
+ if (/\b(performance|speed|slow|fast|optimize|cache)\b/i.test(content))
1883
+ tags.push('performance');
1884
+ return [...new Set(tags)].slice(0, 8); // Cap at 8 tags
1885
+ }
1542
1886
  const stored = [];
1543
1887
  const skipped = [];
1544
1888
  for (const item of extracted) {
@@ -1564,31 +1908,25 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1564
1908
  (0, session_tracker_1.feedSession)({ topic: item.content.slice(0, 60) });
1565
1909
  break;
1566
1910
  }
1567
- // Map auto_learn string types to MemoryType enum values
1568
- // (auto_learner returns uppercase like "DECISION", but MemoryType enum uses lowercase like "decision")
1569
- const autoLearnTypeMap = {
1570
- 'DECISION': types_1.MemoryType.DECISION || 'decision',
1571
- 'CORRECTION': types_1.MemoryType.CORRECTION || 'correction',
1572
- 'BUG_FIX': types_1.MemoryType.BUG_FIX || 'bug_fix',
1573
- 'CONVENTION': types_1.MemoryType.CONVENTION || 'convention',
1574
- 'INSIGHT': types_1.MemoryType.INSIGHT || 'insight',
1575
- 'FAILED_ATTEMPT': types_1.MemoryType.FAILED_SUGGESTION || 'failed_suggestion',
1576
- 'BUSINESS_RULE': types_1.MemoryType.DECISION || 'decision',
1577
- 'GOTCHA': types_1.MemoryType.CORRECTION || 'correction',
1578
- 'CURRENT_TASK': types_1.MemoryType.INSIGHT || 'insight',
1579
- };
1580
- const mappedType = autoLearnTypeMap[item.type] || item.type.toLowerCase();
1581
1911
  const result = (0, memory_quality_1.storeWithQuality)(memoryStore, {
1582
- type: mappedType,
1912
+ type: item.type,
1583
1913
  intent: item.content,
1584
- action: item.content,
1914
+ action: `auto_learn:${item.type.toLowerCase()}`,
1585
1915
  reason: item.reason,
1586
1916
  confidence: item.confidence,
1587
- importance: item.confidence,
1588
- tags: [item.type.toLowerCase()],
1917
+ importance: calculateImportance(item),
1918
+ tags: extractTopicTags(item),
1589
1919
  });
1590
- if (result) {
1920
+ if (result.stored) {
1591
1921
  stored.push(`[${item.type}] ${item.content.slice(0, 60)}${item.content.length > 60 ? '…' : ''}`);
1922
+ // OpenClaw-style diary logging
1923
+ try {
1924
+ (0, daily_diary_1.appendDiaryEntry)({
1925
+ type: item.type.toLowerCase().replace('_', '_'),
1926
+ content: item.content.slice(0, 100),
1927
+ });
1928
+ }
1929
+ catch { /* diary is non-critical */ }
1592
1930
  }
1593
1931
  else {
1594
1932
  skipped.push(`[${item.type}] ${item.content.slice(0, 40)}… (duplicate)`);
@@ -1600,6 +1938,144 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1600
1938
  }
1601
1939
  if (stored.length > 0) {
1602
1940
  (0, memory_cache_1.invalidateCache)();
1941
+ for (let i = 0; i < stored.length; i++)
1942
+ (0, usage_stats_1.trackStore)();
1943
+ // OpenClaw: Auto-export curated MEMORY.md after storing new memories
1944
+ if (workspaceRoot && stored.length >= 2) {
1945
+ try {
1946
+ (0, memory_export_md_1.generateMemoryMd)(memoryStore, workspaceRoot);
1947
+ }
1948
+ catch { /* non-fatal */ }
1949
+ }
1950
+ }
1951
+ // ─── Auto-correction capture ─────────────────────────────────────
1952
+ // Scan AI text for self-corrections ("I apologize", "you're right")
1953
+ const aiCorrections = (0, correction_detector_1.detectAIAcknowledgments)(text);
1954
+ for (const corr of aiCorrections) {
1955
+ try {
1956
+ const corrResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
1957
+ type: 'CORRECTION',
1958
+ intent: `[AUTO-DETECTED] ${corr.fullContext}`,
1959
+ action: corr.fullContext,
1960
+ reason: `AI self-correction detected (confidence: ${corr.confidence})`,
1961
+ confidence: corr.confidence,
1962
+ importance: 0.90, // High importance — corrections prevent repeats
1963
+ tags: ['auto-correction', 'ai-acknowledgment'],
1964
+ });
1965
+ if (corrResult.stored) {
1966
+ stored.push(`[CORRECTION] ${corr.fullContext.slice(0, 60)}…`);
1967
+ }
1968
+ }
1969
+ catch { /* skip */ }
1970
+ }
1971
+ // Scan user context for direct corrections ("no, use X not Y")
1972
+ const userContext = args.context;
1973
+ if (userContext && userContext.length > 10) {
1974
+ const userCorrections = (0, correction_detector_1.detectUserCorrections)(userContext);
1975
+ for (const corr of userCorrections) {
1976
+ try {
1977
+ const content = corr.corrected
1978
+ ? `User correction: use "${corr.corrected}"${corr.original ? ` instead of "${corr.original}"` : ''}`
1979
+ : `User correction: ${corr.fullContext}`;
1980
+ const corrResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
1981
+ type: 'CORRECTION',
1982
+ intent: content,
1983
+ action: corr.fullContext,
1984
+ reason: `User correction detected (confidence: ${corr.confidence})`,
1985
+ confidence: corr.confidence,
1986
+ importance: 0.95, // Very high — user corrections are gospel
1987
+ tags: ['auto-correction', 'user-correction'],
1988
+ });
1989
+ if (corrResult.stored) {
1990
+ stored.push(`[USER CORRECTION] ${content.slice(0, 60)}…`);
1991
+ (0, memory_cache_1.invalidateCache)();
1992
+ }
1993
+ }
1994
+ catch { /* skip */ }
1995
+ }
1996
+ // ─── Preference learning ─────────────────────────────────
1997
+ const prefs = (0, preference_learner_1.detectPreferences)(userContext);
1998
+ for (const pref of prefs) {
1999
+ try {
2000
+ const prefResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
2001
+ type: 'CONVENTION',
2002
+ intent: pref.preference,
2003
+ action: `Detected from: "${pref.evidence}"`,
2004
+ reason: `User preference auto-detected (${pref.category})`,
2005
+ confidence: pref.confidence,
2006
+ importance: 0.85,
2007
+ tags: ['preference', pref.category],
2008
+ });
2009
+ if (prefResult.stored) {
2010
+ stored.push(`[PREFERENCE] ${pref.preference.slice(0, 60)}…`);
2011
+ (0, memory_cache_1.invalidateCache)();
2012
+ }
2013
+ }
2014
+ catch { /* skip */ }
2015
+ }
2016
+ }
2017
+ // ─── Build error learning ──────────────────────────────────
2018
+ // Scan for TS errors, test failures in AI text
2019
+ if ((0, error_learner_1.containsErrors)(text)) {
2020
+ const errorPatterns = (0, error_learner_1.extractErrorPatterns)(text);
2021
+ // Extract verification steps for regression prevention
2022
+ const verifySteps = (0, regression_guard_1.extractVerificationSteps)(text);
2023
+ for (const ep of errorPatterns) {
2024
+ try {
2025
+ const baseAction = `Auto-captured from build/test output`;
2026
+ const actionWithVerify = (0, regression_guard_1.attachVerification)(baseAction, verifySteps);
2027
+ const errResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
2028
+ type: 'BUG_FIX',
2029
+ intent: `[ERROR PATTERN] ${ep.errorType}: ${ep.message}`,
2030
+ action: actionWithVerify,
2031
+ reason: `Error pattern auto-detected — avoid this in future`,
2032
+ confidence: ep.confidence,
2033
+ importance: 0.90,
2034
+ tags: ['error-pattern', ep.errorType.toLowerCase(), 'auto-detected'],
2035
+ });
2036
+ if (errResult.stored) {
2037
+ stored.push(`[ERROR LEARNED] ${ep.errorType}: ${ep.message.slice(0, 50)}…`);
2038
+ (0, memory_cache_1.invalidateCache)();
2039
+ }
2040
+ }
2041
+ catch { /* skip */ }
2042
+ }
2043
+ }
2044
+ // ─── Success reinforcement ─────────────────────────────────
2045
+ // Scan user context for praise/success signals
2046
+ if (userContext && userContext.length > 5) {
2047
+ const successSignals = (0, success_tracker_1.detectSuccess)(userContext);
2048
+ for (const signal of successSignals) {
2049
+ try {
2050
+ const mem = (0, success_tracker_1.buildSuccessMemory)(signal, text);
2051
+ const successResult = (0, memory_quality_1.storeWithQuality)(memoryStore, {
2052
+ type: 'INSIGHT',
2053
+ intent: mem.intent,
2054
+ action: text.slice(0, 200),
2055
+ reason: mem.reason,
2056
+ confidence: signal.confidence,
2057
+ importance: 0.80,
2058
+ tags: mem.tags,
2059
+ });
2060
+ if (successResult.stored) {
2061
+ stored.push(`[SUCCESS] Proven approach stored from: "${signal.trigger}"`);
2062
+ (0, memory_cache_1.invalidateCache)();
2063
+ }
2064
+ }
2065
+ catch { /* skip */ }
2066
+ }
2067
+ }
2068
+ // ─── File relationship tracking ────────────────────────────
2069
+ // Track files mentioned in context for co-edit detection
2070
+ if (userContext) {
2071
+ const filePatterns = userContext.match(/[\w-]+\.\w{1,5}/g) || [];
2072
+ const codeFileExts = new Set(['ts', 'tsx', 'js', 'jsx', 'css', 'py', 'go', 'rs', 'java']);
2073
+ for (const f of filePatterns) {
2074
+ const ext = f.split('.').pop()?.toLowerCase() || '';
2075
+ if (codeFileExts.has(ext)) {
2076
+ (0, file_relationships_1.recordFileEdit)(f);
2077
+ }
2078
+ }
1603
2079
  }
1604
2080
  const lines = ['**Auto-Learn Results:**'];
1605
2081
  if (stored.length > 0) {
@@ -1682,14 +2158,16 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1682
2158
  '# Cortex Health Check\n',
1683
2159
  `| Metric | Value |`,
1684
2160
  `|--------|-------|`,
1685
- `| Plan | **Full Brain — 100% Free** |`,
1686
2161
  `| Active Memories | ${activeCount} |`,
1687
- `| Brain Layers | All 12 Active |`,
1688
- `| Session Store Count | ${stats.storeCount}/500 |`,
1689
- `| Session Auto-Learn Count | ${stats.autoLearnCount}/1000 |`,
1690
- `| Session Total Calls | ${stats.totalCalls}/5000 |`,
2162
+ `| Session Store Count | ${stats.storeCount}/100 |`,
2163
+ `| Session Auto-Learn Count | ${stats.autoLearnCount}/500 |`,
2164
+ `| Session Total Calls | ${stats.totalCalls}/2000 |`,
1691
2165
  `| Uptime | ${Math.floor(stats.uptime / 60)}m ${stats.uptime % 60}s |`,
1692
- `| Status | Healthy |`,
2166
+ `| Diary Days | ${(0, daily_diary_1.getDiaryStats)().totalDays} |`,
2167
+ `| Today's Diary Entries | ${(0, daily_diary_1.getDiaryStats)().todayEntries} |`,
2168
+ `| Top Memory Types | ${(0, access_pattern_tracker_1.getTopTypes)(3).map(t => `${t.type}(${t.count})`).join(', ') || 'none yet'} |`,
2169
+ `| Access Profile | ${(0, access_pattern_tracker_1.getAccessProfile)() ? 'loaded' : 'empty'} |`,
2170
+ `| Status | Healthy |`,
1693
2171
  ];
1694
2172
  return {
1695
2173
  jsonrpc: '2.0', id,
@@ -1703,584 +2181,101 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
1703
2181
  };
1704
2182
  }
1705
2183
  }
1706
- // ═══ NEW TOOLS: search_by_file, pin, unpin, undo, clear, timeline, thumbs ═══
1707
- function handleSearchByFile(id, args) {
2184
+ // ─── Pre-Flight Check Handler ───────────────────────────────────────────────
2185
+ function handlePreCheck(id, args) {
1708
2186
  try {
1709
- const filePath = args.filePath;
1710
- const limit = args.limit || 20;
1711
- if (!filePath) {
1712
- return {
1713
- jsonrpc: '2.0', id,
1714
- result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true },
1715
- };
1716
- }
1717
- // Use the existing getByFile method + FTS fallback
1718
- let memories = memoryStore.getByFile(filePath, limit);
1719
- // Also do FTS search for the filename
1720
- if (memories.length < limit) {
1721
- const basename = filePath.split(/[\\/]/).pop() || filePath;
2187
+ // Track file for relationship mapping
2188
+ if (args.filename)
2189
+ (0, file_relationships_1.recordFileEdit)(args.filename);
2190
+ const result = (0, pre_flight_1.preFlightCheck)(memoryStore, args.filename, args.task);
2191
+ let text = (0, pre_flight_1.formatPreFlight)(result);
2192
+ // Add file relationship warnings
2193
+ if (args.filename) {
2194
+ const warnings = (0, file_relationships_1.checkMissingRelated)(args.filename, memoryStore);
2195
+ if (warnings.length > 0) {
2196
+ text += '\n\n## 🔗 File Relationships\n';
2197
+ warnings.forEach(w => text += w + '\n');
2198
+ }
2199
+ }
2200
+ // Add architecture context — show file's role in the system
2201
+ if (args.filename && workspaceRoot) {
1722
2202
  try {
1723
- const ftsResults = memoryStore.searchFTS(basename, limit - memories.length);
1724
- const existingIds = new Set(memories.map(m => m.id));
1725
- for (const r of ftsResults) {
1726
- if (!existingIds.has(r.memory.id)) {
1727
- memories.push(r.memory);
2203
+ const archGraph = (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot);
2204
+ const basename = args.filename.replace(/\\/g, '/').split('/').pop() || args.filename;
2205
+ // nodes is Map<string, ArchNode> — find by key ending with basename
2206
+ let deps = null;
2207
+ for (const [key, node] of archGraph.nodes) {
2208
+ if (key.endsWith(basename)) {
2209
+ deps = node;
2210
+ break;
1728
2211
  }
1729
2212
  }
1730
- } catch { /* FTS may fail on special characters */ }
1731
- }
1732
- if (memories.length === 0) {
1733
- return {
1734
- jsonrpc: '2.0', id,
1735
- result: { content: [{ type: 'text', text: `No memories found for file: "${filePath}"` }] },
1736
- };
1737
- }
1738
- const lines = [`# Memories related to "${filePath}"\n`];
1739
- for (const m of memories) {
1740
- const age = Math.floor((Date.now() - m.createdAt) / 86400000);
1741
- lines.push(`- **[${m.type}]** ${m.intent}`);
1742
- if (m.reason) lines.push(` _${m.reason}_`);
1743
- lines.push(` \`id: ${m.id}\` · ${age}d old`);
1744
- }
1745
- lines.push(`\n_Total: ${memories.length} memories for this file._`);
1746
- return {
1747
- jsonrpc: '2.0', id,
1748
- result: { content: [{ type: 'text', text: lines.join('\n') }] },
1749
- };
1750
- } catch (err) {
1751
- return {
1752
- jsonrpc: '2.0', id,
1753
- result: { content: [{ type: 'text', text: `search_by_file error: ${err.message}` }], isError: true },
1754
- };
1755
- }
1756
- }
1757
- function handlePinMemory(id, args) {
1758
- try {
1759
- const memoryId = args.id;
1760
- if (!memoryId) {
1761
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
1762
- }
1763
- const existing = memoryStore.get(memoryId);
1764
- if (!existing) {
1765
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
1766
- }
1767
- const tags = existing.tags || [];
1768
- if (!tags.includes('pinned')) tags.push('pinned');
1769
- memoryStore.update(memoryId, { importance: 1.0, tags });
1770
- (0, memory_cache_1.invalidateCache)();
1771
- return {
1772
- jsonrpc: '2.0', id,
1773
- result: { content: [{ type: 'text', text: `📌 Memory PINNED: "${existing.intent}"\n\nThis memory will ALWAYS be included in context, regardless of topic.` }] },
1774
- };
1775
- } catch (err) {
1776
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `pin error: ${err.message}` }], isError: true } };
1777
- }
1778
- }
1779
- function handleUnpinMemory(id, args) {
1780
- try {
1781
- const memoryId = args.id;
1782
- if (!memoryId) {
1783
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
1784
- }
1785
- const existing = memoryStore.get(memoryId);
1786
- if (!existing) {
1787
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
1788
- }
1789
- const tags = (existing.tags || []).filter(t => t !== 'pinned');
1790
- memoryStore.update(memoryId, { importance: 0.7, tags });
1791
- (0, memory_cache_1.invalidateCache)();
1792
- return {
1793
- jsonrpc: '2.0', id,
1794
- result: { content: [{ type: 'text', text: `📌 Memory UNPINNED: "${existing.intent}"\n\nThis memory will now use normal priority ranking.` }] },
1795
- };
1796
- } catch (err) {
1797
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `unpin error: ${err.message}` }], isError: true } };
1798
- }
1799
- }
1800
- function handleUndoLast(id, args) {
1801
- try {
1802
- const count = Math.min(Math.max(args.count || 1, 1), 10);
1803
- // Get the N most recently CREATED active memories (order by created_at, not timestamp)
1804
- const db = memoryStore.db;
1805
- const recentRows = db.prepare('SELECT * FROM memory_units WHERE is_active = 1 ORDER BY created_at DESC LIMIT ?').all(count);
1806
- const recent = recentRows.map(r => memoryStore.get(r.id)).filter(Boolean);
1807
- if (recent.length === 0) {
1808
- return {
1809
- jsonrpc: '2.0', id,
1810
- result: { content: [{ type: 'text', text: 'No memories to undo.' }] },
1811
- };
1812
- }
1813
- const toUndo = recent;
1814
- const undone = [];
1815
- for (const m of toUndo) {
1816
- memoryStore.deactivate(m.id);
1817
- undone.push(`- [${m.type}] "${m.intent.slice(0, 80)}"`);
2213
+ if (deps && (deps.imports?.length > 0 || deps.importedBy?.length > 0)) {
2214
+ text += '\n\n## \ud83c\udfd7\ufe0f Architecture Context';
2215
+ if (deps.imports?.length > 0) {
2216
+ text += `\n**Imports from:** ${deps.imports.slice(0, 10).join(', ')}`;
2217
+ }
2218
+ if (deps.importedBy?.length > 0) {
2219
+ text += `\n**Imported by:** ${deps.importedBy.slice(0, 10).join(', ')}`;
2220
+ }
2221
+ }
2222
+ }
2223
+ catch { /* non-fatal */ }
1818
2224
  }
1819
- (0, memory_cache_1.invalidateCache)();
1820
2225
  return {
1821
2226
  jsonrpc: '2.0', id,
1822
- result: { content: [{ type: 'text', text: `⏪ Undone ${undone.length} memory(ies):\n\n${undone.join('\n')}` }] },
2227
+ result: { content: [{ type: 'text', text }] },
1823
2228
  };
1824
- } catch (err) {
1825
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `undo error: ${err.message}` }], isError: true } };
1826
2229
  }
1827
- }
1828
- function handleClearMemories(id, args) {
1829
- try {
1830
- const type = args.type;
1831
- const olderThanDays = args.olderThanDays;
1832
- if (!type) {
1833
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: type is required' }], isError: true } };
1834
- }
1835
- let query = 'SELECT * FROM memory_units WHERE is_active = 1';
1836
- const params = [];
1837
- if (type !== 'ALL') {
1838
- query += ' AND type = ?';
1839
- params.push(type);
1840
- }
1841
- if (olderThanDays) {
1842
- const cutoff = Date.now() - (olderThanDays * 86400000);
1843
- query += ' AND created_at < ?';
1844
- params.push(cutoff);
1845
- }
1846
- const rows = memoryStore.db ? memoryStore.db.prepare(query).all.apply(memoryStore.db.prepare(query), params) : [];
1847
- let cleared = 0;
1848
- for (const row of rows) {
1849
- memoryStore.deactivate(row.id);
1850
- cleared++;
1851
- }
1852
- (0, memory_cache_1.invalidateCache)();
1853
- const ageMsg = olderThanDays ? ` older than ${olderThanDays} days` : '';
2230
+ catch (err) {
1854
2231
  return {
1855
2232
  jsonrpc: '2.0', id,
1856
- result: { content: [{ type: 'text', text: `🗑️ Cleared ${cleared} ${type === 'ALL' ? '' : type + ' '}memories${ageMsg}.` }] },
2233
+ result: { content: [{ type: 'text', text: `Pre-check error: ${err.message}` }], isError: true },
1857
2234
  };
1858
- } catch (err) {
1859
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `clear error: ${err.message}` }], isError: true } };
1860
2235
  }
1861
2236
  }
1862
- function parseRelativeDate(str) {
1863
- if (!str) return Date.now();
1864
- const lower = str.toLowerCase().trim();
1865
- if (lower === 'now' || lower === 'today') return Date.now();
1866
- if (lower === 'yesterday') return Date.now() - 86400000;
1867
- // "N days ago", "N hours ago", "N weeks ago"
1868
- const relMatch = lower.match(/^(\d+)\s+(day|hour|week|month)s?\s+ago$/);
1869
- if (relMatch) {
1870
- const n = parseInt(relMatch[1], 10);
1871
- const unit = relMatch[2];
1872
- const multipliers = { hour: 3600000, day: 86400000, week: 604800000, month: 2592000000 };
1873
- return Date.now() - (n * (multipliers[unit] || 86400000));
1874
- }
1875
- // Try ISO date
1876
- const parsed = new Date(str).getTime();
1877
- return isNaN(parsed) ? Date.now() - 604800000 : parsed; // default to 7 days ago
1878
- }
1879
- function handleSearchTimeline(id, args) {
2237
+ // ─── Impact Analysis Handler ───────────────────────────────────────────────
2238
+ function handleCheckImpact(id, args) {
1880
2239
  try {
1881
- const fromTs = parseRelativeDate(args.from);
1882
- const toTs = parseRelativeDate(args.to || 'now');
1883
- const type = args.type;
1884
- let query = 'SELECT * FROM memory_units WHERE is_active = 1 AND created_at >= ? AND created_at <= ?';
1885
- const params = [fromTs, toTs];
1886
- if (type && type !== 'ALL') {
1887
- query += ' AND type = ?';
1888
- params.push(type);
1889
- }
1890
- query += ' ORDER BY created_at DESC LIMIT 50';
1891
- const db = memoryStore.db || memoryStore.connection;
1892
- const stmt = db.prepare(query);
1893
- const rows = stmt.all.apply(stmt, params);
1894
- if (rows.length === 0) {
2240
+ const file = args.file;
2241
+ if (!file) {
1895
2242
  return {
1896
2243
  jsonrpc: '2.0', id,
1897
- result: { content: [{ type: 'text', text: `No memories found between ${new Date(fromTs).toLocaleDateString()} and ${new Date(toTs).toLocaleDateString()}.` }] },
2244
+ result: { content: [{ type: 'text', text: 'Error: file parameter is required' }], isError: true },
1898
2245
  };
1899
2246
  }
1900
- const lines = [`# Timeline: ${new Date(fromTs).toLocaleDateString()} ${new Date(toTs).toLocaleDateString()}\n`];
1901
- let lastDate = '';
1902
- for (const row of rows) {
1903
- const dateStr = new Date(row.created_at).toLocaleDateString();
1904
- if (dateStr !== lastDate) {
1905
- lines.push(`\n## ${dateStr}`);
1906
- lastDate = dateStr;
1907
- }
1908
- lines.push(`- **[${row.type}]** ${row.intent}`);
1909
- if (row.reason) lines.push(` _${row.reason}_`);
1910
- }
1911
- lines.push(`\n_Total: ${rows.length} memories in this period._`);
1912
- return {
1913
- jsonrpc: '2.0', id,
1914
- result: { content: [{ type: 'text', text: lines.join('\n') }] },
1915
- };
1916
- } catch (err) {
1917
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `timeline error: ${err.message}` }], isError: true } };
1918
- }
1919
- }
1920
- function handleThumbsUp(id, args) {
1921
- try {
1922
- const memoryId = args.id;
1923
- if (!memoryId) {
1924
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
1925
- }
1926
- const existing = memoryStore.get(memoryId);
1927
- if (!existing) {
1928
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
1929
- }
1930
- // Boost importance (cap at 1.0)
1931
- const newImportance = Math.min(1.0, (existing.importance || 0.5) + 0.1);
1932
- memoryStore.update(memoryId, { importance: newImportance });
1933
- // Record in user_signals table
1934
- try {
1935
- const db = memoryStore.db || memoryStore.connection;
1936
- db.prepare('INSERT INTO user_signals (memory_id, signal, timestamp) VALUES (?, ?, ?)')
1937
- .run(memoryId, 'thumbs_up', Date.now());
1938
- } catch { /* table might not exist */ }
1939
- (0, memory_cache_1.invalidateCache)();
1940
- return {
1941
- jsonrpc: '2.0', id,
1942
- result: { content: [{ type: 'text', text: `👍 Upvoted: "${existing.intent.slice(0, 80)}"\nImportance: ${(newImportance * 100).toFixed(0)}%` }] },
1943
- };
1944
- } catch (err) {
1945
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `thumbs_up error: ${err.message}` }], isError: true } };
1946
- }
1947
- }
1948
- function handleThumbsDown(id, args) {
1949
- try {
1950
- const memoryId = args.id;
1951
- const reason = args.reason || 'Not useful';
1952
- if (!memoryId) {
1953
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
1954
- }
1955
- const existing = memoryStore.get(memoryId);
1956
- if (!existing) {
1957
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
1958
- }
1959
- // Demote importance
1960
- const newImportance = Math.max(0.0, (existing.importance || 0.5) - 0.15);
1961
- memoryStore.update(memoryId, { importance: newImportance });
1962
- // Record in user_signals table
1963
- let downvoteCount = 0;
1964
- try {
1965
- const db = memoryStore.db || memoryStore.connection;
1966
- db.prepare('INSERT INTO user_signals (memory_id, signal, correction, timestamp) VALUES (?, ?, ?, ?)')
1967
- .run(memoryId, 'thumbs_down', reason, Date.now());
1968
- // Count total downvotes for this memory
1969
- const row = db.prepare('SELECT COUNT(*) as cnt FROM user_signals WHERE memory_id = ? AND signal = ?')
1970
- .get(memoryId, 'thumbs_down');
1971
- downvoteCount = row ? row.cnt : 0;
1972
- } catch { /* table might not exist */ }
1973
- // Auto-deactivate if 3+ downvotes
1974
- let deactivated = false;
1975
- if (downvoteCount >= 3) {
1976
- memoryStore.deactivate(memoryId);
1977
- deactivated = true;
1978
- }
1979
- (0, memory_cache_1.invalidateCache)();
1980
- let msg = `👎 Downvoted: "${existing.intent.slice(0, 80)}"\nImportance: ${(newImportance * 100).toFixed(0)}%\nReason: ${reason}`;
1981
- if (deactivated) {
1982
- msg += `\n\n⚠️ Memory auto-deactivated (received ${downvoteCount} downvotes).`;
1983
- }
1984
- return {
1985
- jsonrpc: '2.0', id,
1986
- result: { content: [{ type: 'text', text: msg }] },
1987
- };
1988
- } catch (err) {
1989
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `thumbs_down error: ${err.message}` }], isError: true } };
1990
- }
1991
- }
1992
- function handleBackupBrain(id) {
1993
- try {
1994
- const fs = require('fs');
1995
- const path = require('path');
1996
- const dbPath = memoryStore.db?.name || memoryStore._dbPath;
1997
- if (!dbPath || !fs.existsSync(dbPath)) {
1998
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: Cannot locate database file.' }], isError: true } };
1999
- }
2000
- const backupDate = new Date().toISOString().replace(/[:.]/g, '-');
2001
- const backupPath = path.join(path.dirname(dbPath), `cognitive-backup-${backupDate}.db`);
2002
- // Close active WAL before backup
2003
- memoryStore.checkpoint?.();
2004
- fs.copyFileSync(dbPath, backupPath);
2005
- return {
2006
- jsonrpc: '2.0', id,
2007
- result: { content: [{ type: 'text', text: `✅ Database backed up successfully!\n\nSaved to: ${backupPath}\nSize: ${(fs.statSync(backupPath).size / 1024 / 1024).toFixed(2)} MB` }] },
2008
- };
2009
- } catch (err) {
2010
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `backup error: ${err.message}` }], isError: true } };
2011
- }
2012
- }
2013
- function handleGetDailySummary(id, args) {
2014
- try {
2015
- const dateStr = args.date || new Date().toISOString().split('T')[0];
2016
- const db = memoryStore.db || memoryStore.connection;
2017
- // First check if it exists
2018
- const row = db.prepare('SELECT summary FROM daily_summaries WHERE date = ?').get(dateStr);
2019
- if (row) {
2020
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `# Summary for ${dateStr}\n\n${row.summary}` }] } };
2021
- }
2022
- // If checking today and no summary exists, let's create a dynamic one
2023
- const isToday = dateStr === new Date().toISOString().split('T')[0];
2024
- if (!isToday) {
2025
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `No summary found for ${dateStr}.` }] } };
2026
- }
2027
- // Generate dynamic summary for today
2028
- const startOfDay = new Date(dateStr).setHours(0,0,0,0);
2029
- const todayMemories = db.prepare('SELECT * FROM memory_units WHERE is_active = 1 AND created_at >= ? ORDER BY created_at ASC').all(startOfDay);
2030
- if (todayMemories.length === 0) {
2031
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `No memories recorded yet today (${dateStr}).` }] } };
2032
- }
2033
- const lines = [];
2034
- let types = { DECISION: 0, BUG_FIX: 0, CORRECTION: 0, CONVENTION: 0 };
2035
- for (const m of todayMemories) {
2036
- if (types[m.type] !== undefined) types[m.type]++;
2037
- lines.push(`- **[${m.type}]** ${m.intent}`);
2038
- }
2039
- 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')}`;
2040
- // Save it
2041
- db.prepare('INSERT OR REPLACE INTO daily_summaries (date, summary, created_at) VALUES (?, ?, ?)')
2042
- .run(dateStr, summaryText, Date.now());
2043
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `# Summary for Today (${dateStr})\n\n${summaryText}` }] } };
2044
- } catch (err) {
2045
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `summary error: ${err.message}` }], isError: true } };
2046
- }
2047
- }
2048
- function handleAnalytics(id) {
2049
- try {
2050
- const db = memoryStore.db || memoryStore.connection;
2051
- const stats = [];
2052
- // Basic counts
2053
- const total = db.prepare('SELECT COUNT(*) as c FROM memory_units').get().c;
2054
- const active = db.prepare('SELECT COUNT(*) as c FROM memory_units WHERE is_active = 1').get().c;
2055
- const pinned = db.prepare("SELECT COUNT(*) as c FROM memory_units WHERE is_active = 1 AND tags LIKE '%pinned%'").get().c;
2056
- stats.push(`## 📊 Brain Health\n- Total Memories: ${total}\n- Active Memories: ${active}\n- Pinned Rules: ${pinned}`);
2057
- // By Type
2058
- stats.push('\n## 🧩 Distribution by Type');
2059
- const types = db.prepare('SELECT type, COUNT(*) as c FROM memory_units WHERE is_active = 1 GROUP BY type ORDER BY c DESC').all();
2060
- for (const t of types) stats.push(`- **${t.type}**: ${t.c}`);
2061
- // Most Accessed
2062
- stats.push('\n## 🔥 Most Accessed Memories (Top 5)');
2063
- 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();
2064
- if (hot.length) {
2065
- for (const h of hot) stats.push(`- [${h.access_count}x] **${h.type}**: ${h.intent} (id: ${h.id})`);
2066
- } else {
2067
- stats.push('- No memory accesses recorded yet.');
2068
- }
2069
- // Feedback stats
2070
- try {
2071
- const up = db.prepare("SELECT COUNT(*) as c FROM user_signals WHERE signal = 'thumbs_up'").get().c;
2072
- const down = db.prepare("SELECT COUNT(*) as c FROM user_signals WHERE signal = 'thumbs_down'").get().c;
2073
- if (up > 0 || down > 0) {
2074
- stats.push(`\n## 👍 User Feedback\n- Upvotes: ${up}\n- Downvotes: ${down}`);
2075
- }
2076
- } catch { /* ignore if table missing */ }
2247
+ const wsRoot = args.workspaceRoot || process.cwd();
2248
+ const result = (0, impact_analyzer_1.analyzeImpact)(file, wsRoot);
2249
+ const text = (0, impact_analyzer_1.formatImpact)(result);
2077
2250
  return {
2078
2251
  jsonrpc: '2.0', id,
2079
- result: { content: [{ type: 'text', text: stats.join('\n') }] },
2252
+ result: { content: [{ type: 'text', text }] },
2080
2253
  };
2081
- } catch (err) {
2082
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `analytics error: ${err.message}` }], isError: true } };
2083
2254
  }
2084
- }
2085
- // ═══ NEW CORE TOOLS: repair_brain, detect_conflicts, suggest_cleanup ═══
2086
- function handleRepairBrain(id) {
2087
- try {
2088
- const db = memoryStore.db || memoryStore.connection;
2089
- const repairs = [];
2090
- // 1. Integrity check
2091
- const integrityResult = db.pragma('integrity_check');
2092
- const integrityOk = integrityResult && integrityResult[0] && integrityResult[0].integrity_check === 'ok';
2093
- repairs.push(integrityOk ? '✅ Database integrity: OK' : '⚠️ Database integrity issues detected');
2094
- // 2. Rebuild FTS index
2095
- try {
2096
- db.exec("INSERT INTO memory_fts(memory_fts) VALUES('rebuild')");
2097
- repairs.push('✅ Full-text search index: Rebuilt');
2098
- } catch (e) {
2099
- repairs.push('⚠️ FTS rebuild failed: ' + e.message);
2100
- }
2101
- // 3. Fix orphaned vectors (vectors for deleted memories)
2102
- try {
2103
- const orphaned = db.prepare(
2104
- "DELETE FROM memory_vectors WHERE memory_id NOT IN (SELECT id FROM memory_units WHERE is_active = 1)"
2105
- ).run();
2106
- repairs.push(`✅ Orphaned vectors: Cleaned ${orphaned.changes} entries`);
2107
- } catch { repairs.push('✅ Orphaned vectors: None (table may not exist)'); }
2108
- // 4. Fix memories with missing created_at
2109
- try {
2110
- const fixed = db.prepare(
2111
- "UPDATE memory_units SET created_at = timestamp WHERE created_at = 0 OR created_at IS NULL"
2112
- ).run();
2113
- if (fixed.changes > 0) {
2114
- repairs.push(`✅ Fixed ${fixed.changes} memories with missing created_at`);
2115
- }
2116
- } catch { }
2117
- // 5. WAL checkpoint + VACUUM
2118
- try {
2119
- db.pragma('wal_checkpoint(TRUNCATE)');
2120
- db.exec('VACUUM');
2121
- repairs.push('✅ Database compacted (VACUUM)');
2122
- } catch (e) {
2123
- repairs.push('⚠️ VACUUM failed: ' + e.message);
2124
- }
2125
- // 6. Stats after repair
2126
- const activeCount = memoryStore.activeCount();
2127
- repairs.push(`\n📊 Active memories: ${activeCount}`);
2128
- (0, memory_cache_1.invalidateCache)();
2255
+ catch (err) {
2129
2256
  return {
2130
2257
  jsonrpc: '2.0', id,
2131
- result: { content: [{ type: 'text', text: `# 🔧 Brain Repair Complete\n\n${repairs.join('\n')}` }] },
2258
+ result: { content: [{ type: 'text', text: `Impact analysis error: ${err.message}` }], isError: true },
2132
2259
  };
2133
- } catch (err) {
2134
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Repair error: ${err.message}` }], isError: true } };
2135
2260
  }
2136
2261
  }
2137
- function handleDetectConflicts(id) {
2262
+ // ─── Resume Work Handler ───────────────────────────────────────────────────
2263
+ function handleResumeWork(id) {
2138
2264
  try {
2139
- const memories = memoryStore.getActive(500);
2140
- // Only look at decisions and conventions — these are the ones that conflict
2141
- const candidates = memories.filter(m =>
2142
- ['DECISION', 'CONVENTION', 'CORRECTION'].includes(m.type)
2143
- );
2144
- const conflicts = [];
2145
- // Compare each pair for contradictions
2146
- const OPPOSITE_PAIRS = [
2147
- ['always', 'never'], ['use', 'avoid'], ['enable', 'disable'],
2148
- ['allow', 'block'], ['include', 'exclude'], ['add', 'remove'],
2149
- ['yes', 'no'], ['true', 'false'], ['must', 'must not'],
2150
- ['do', "don't"], ['do', 'do not'], ['can', 'cannot'],
2151
- ];
2152
- for (let i = 0; i < candidates.length; i++) {
2153
- for (let j = i + 1; j < candidates.length; j++) {
2154
- const a = candidates[i];
2155
- const b = candidates[j];
2156
- const aText = (a.intent + ' ' + (a.action || '')).toLowerCase();
2157
- const bText = (b.intent + ' ' + (b.action || '')).toLowerCase();
2158
- // Check if they share a subject but have opposite verbs
2159
- let isConflict = false;
2160
- for (const [pos, neg] of OPPOSITE_PAIRS) {
2161
- if ((aText.includes(pos) && bText.includes(neg)) ||
2162
- (aText.includes(neg) && bText.includes(pos))) {
2163
- // Check they share at least one significant word (3+ chars)
2164
- const aWords = aText.split(/\s+/).filter(w => w.length > 3);
2165
- const bWords = new Set(bText.split(/\s+/).filter(w => w.length > 3));
2166
- const shared = aWords.filter(w => bWords.has(w));
2167
- if (shared.length >= 1) {
2168
- isConflict = true;
2169
- break;
2170
- }
2171
- }
2172
- }
2173
- if (isConflict) {
2174
- conflicts.push({
2175
- a: { id: a.id, type: a.type, intent: a.intent, age: Math.floor((Date.now() - a.createdAt) / 86400000) },
2176
- b: { id: b.id, type: b.type, intent: b.intent, age: Math.floor((Date.now() - b.createdAt) / 86400000) },
2177
- });
2178
- }
2179
- }
2180
- }
2181
- if (conflicts.length === 0) {
2182
- return {
2183
- jsonrpc: '2.0', id,
2184
- result: { content: [{ type: 'text', text: '✅ No conflicts detected! All memories are consistent.' }] },
2185
- };
2186
- }
2187
- const lines = [`# ⚠️ ${conflicts.length} Potential Conflict(s) Detected\n`];
2188
- for (let i = 0; i < conflicts.length; i++) {
2189
- const c = conflicts[i];
2190
- lines.push(`### Conflict ${i + 1}`);
2191
- lines.push(`**A:** [${c.a.type}] ${c.a.intent} _(${c.a.age}d old, id: ${c.a.id})_`);
2192
- lines.push(`**B:** [${c.b.type}] ${c.b.intent} _(${c.b.age}d old, id: ${c.b.id})_`);
2193
- lines.push(`→ Use \`delete_memory\` to remove the outdated one.\n`);
2194
- }
2265
+ const ctx = (0, resume_work_1.buildResumeContext)(memoryStore);
2266
+ const text = (0, resume_work_1.formatResumeContext)(ctx);
2195
2267
  return {
2196
2268
  jsonrpc: '2.0', id,
2197
- result: { content: [{ type: 'text', text: lines.join('\n') }] },
2269
+ result: { content: [{ type: 'text', text }] },
2198
2270
  };
2199
- } catch (err) {
2200
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `conflict detection error: ${err.message}` }], isError: true } };
2201
2271
  }
2202
- }
2203
- function handleSuggestCleanup(id, args) {
2204
- try {
2205
- const olderThanDays = args.olderThanDays || 30;
2206
- const cutoff = Date.now() - (olderThanDays * 86400000);
2207
- const db = memoryStore.db || memoryStore.connection;
2208
- // Find stale, low-value memories
2209
- const stale = db.prepare(`
2210
- SELECT * FROM memory_units
2211
- WHERE is_active = 1
2212
- AND created_at < ?
2213
- AND confidence < 0.4
2214
- AND access_count <= 1
2215
- AND (tags IS NULL OR tags NOT LIKE '%pinned%')
2216
- ORDER BY confidence ASC, access_count ASC
2217
- LIMIT 25
2218
- `).all(cutoff);
2219
- if (stale.length === 0) {
2220
- return {
2221
- jsonrpc: '2.0', id,
2222
- result: { content: [{ type: 'text', text: `✅ Brain is clean! No stale memories older than ${olderThanDays} days with low value found.` }] },
2223
- };
2224
- }
2225
- const lines = [`# 🧹 ${stale.length} Stale Memories (Cleanup Suggestions)\n`];
2226
- lines.push(`These memories are older than ${olderThanDays} days, have low confidence, and are rarely accessed:\n`);
2227
- for (const row of stale) {
2228
- const age = Math.floor((Date.now() - row.created_at) / 86400000);
2229
- lines.push(`- **[${row.type}]** ${row.intent}`);
2230
- lines.push(` _Confidence: ${(row.confidence * 100).toFixed(0)}% · Accessed: ${row.access_count}x · Age: ${age}d · id: ${row.id}_`);
2231
- }
2232
- lines.push(`\n→ Use \`delete_memory\` with each id to remove, or \`clear_memories\` for bulk cleanup.`);
2272
+ catch (err) {
2233
2273
  return {
2234
2274
  jsonrpc: '2.0', id,
2235
- result: { content: [{ type: 'text', text: lines.join('\n') }] },
2275
+ result: { content: [{ type: 'text', text: `Resume work error: ${err.message}` }], isError: true },
2236
2276
  };
2237
- } catch (err) {
2238
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `cleanup suggestion error: ${err.message}` }], isError: true } };
2239
2277
  }
2240
2278
  }
2241
2279
  return { handleMCPRequest };
2242
2280
  }
2243
- // ─── NEW: Export/Import/Graph Handlers ─────────────────────────────────────
2244
- async function handleExportMemoriesFn(memoryStore, id, args) {
2245
- try {
2246
- const { filePath } = args;
2247
- if (!filePath) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true } };
2248
- const result = (0, export_import_1.exportToFile)(memoryStore, filePath);
2249
- 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.' }] } };
2250
- } catch (err) {
2251
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'export error: ' + err.message }], isError: true } };
2252
- }
2253
- }
2254
- async function handleImportMemoriesFn(memoryStore, memory_cache_1, id, args) {
2255
- try {
2256
- const { filePath } = args;
2257
- if (!filePath) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true } };
2258
- const result = (0, export_import_1.importFromFile)(memoryStore, filePath);
2259
- if (result.imported > 0) (0, memory_cache_1.invalidateCache)();
2260
- 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 }] } };
2261
- } catch (err) {
2262
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'import error: ' + err.message }], isError: true } };
2263
- }
2264
- }
2265
- async function handleGetRelatedMemoriesFn(memoryStore, id, args) {
2266
- try {
2267
- const memoryId = args.id;
2268
- const maxHops = args.maxHops || 2;
2269
- if (!memoryId) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
2270
- const hops = Math.min(4, Math.max(1, maxHops));
2271
- const starting = memoryStore.get(memoryId);
2272
- if (!starting) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'No memory found with id: ' + memoryId }] } };
2273
- const related = memoryStore.getRelated(memoryId, hops, 20);
2274
- if (related.length === 0) {
2275
- 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.' }] } };
2276
- }
2277
- const lines = ['## Starting Memory', '[' + starting.type + '] ' + starting.intent + ' (id: ' + starting.id + ')', '', '## Related Memories (via graph)'];
2278
- for (const r of related) {
2279
- lines.push('- [' + r.memory.type + '] ' + r.memory.intent + ' (id: ' + r.memory.id + ') (' + (r.score * 100).toFixed(0) + '% relevance)');
2280
- }
2281
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: lines.join('\n') }] } };
2282
- } catch (err) {
2283
- return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'get_related error: ' + err.message }], isError: true } };
2284
- }
2285
- }
2286
2281
  //# sourceMappingURL=mcp-handler.js.map