cortex-mcp 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -279
- package/README.md +224 -393
- package/dist/cli/setup.js +60 -58
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/config.js +2 -2
- package/dist/config/config.js.map +1 -1
- package/dist/db/database.d.ts +0 -8
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/database.js +2 -34
- package/dist/db/database.js.map +1 -1
- package/dist/db/event-log.d.ts +0 -4
- package/dist/db/event-log.d.ts.map +1 -1
- package/dist/db/event-log.js +10 -14
- package/dist/db/event-log.js.map +1 -1
- package/dist/db/memory-store.d.ts +3 -30
- package/dist/db/memory-store.d.ts.map +1 -1
- package/dist/db/memory-store.js +44 -140
- package/dist/db/memory-store.js.map +1 -1
- package/dist/embedding-worker.js +1 -1
- package/dist/embedding-worker.js.map +1 -1
- package/dist/hooks/git-capture.js +3 -3
- package/dist/hooks/git-hooks.js +2 -5
- package/dist/mcp-stdio.js +3 -34
- package/dist/mcp-stdio.js.map +1 -1
- package/dist/memory/anticipation-engine.d.ts.map +1 -1
- package/dist/memory/anticipation-engine.js +10 -18
- package/dist/memory/anticipation-engine.js.map +1 -1
- package/dist/memory/auto-learner.d.ts.map +1 -1
- package/dist/memory/auto-learner.js +43 -190
- package/dist/memory/auto-learner.js.map +1 -1
- package/dist/memory/confidence-decay.d.ts.map +1 -1
- package/dist/memory/confidence-decay.js +9 -13
- package/dist/memory/confidence-decay.js.map +1 -1
- package/dist/memory/embedding-manager.d.ts.map +1 -1
- package/dist/memory/embedding-manager.js +4 -6
- package/dist/memory/embedding-manager.js.map +1 -1
- package/dist/memory/export-import.js +2 -2
- package/dist/memory/export-import.js.map +1 -1
- package/dist/memory/git-memory.d.ts.map +1 -1
- package/dist/memory/git-memory.js +26 -20
- package/dist/memory/git-memory.js.map +1 -1
- package/dist/memory/learning-rate.js +7 -8
- package/dist/memory/learning-rate.js.map +1 -1
- package/dist/memory/llm-enhancer.d.ts +14 -2
- package/dist/memory/llm-enhancer.d.ts.map +1 -1
- package/dist/memory/llm-enhancer.js +46 -66
- package/dist/memory/llm-enhancer.js.map +1 -1
- package/dist/memory/memory-cache.d.ts.map +1 -1
- package/dist/memory/memory-cache.js +0 -10
- package/dist/memory/memory-cache.js.map +1 -1
- package/dist/memory/memory-consolidator.d.ts.map +1 -1
- package/dist/memory/memory-consolidator.js +14 -20
- package/dist/memory/memory-consolidator.js.map +1 -1
- package/dist/memory/memory-decay.d.ts.map +1 -1
- package/dist/memory/memory-decay.js +52 -82
- package/dist/memory/memory-decay.js.map +1 -1
- package/dist/memory/memory-quality.d.ts +1 -1
- package/dist/memory/memory-quality.d.ts.map +1 -1
- package/dist/memory/memory-quality.js +6 -9
- package/dist/memory/memory-quality.js.map +1 -1
- package/dist/memory/memory-ranker.d.ts.map +1 -1
- package/dist/memory/memory-ranker.js +3 -12
- package/dist/memory/memory-ranker.js.map +1 -1
- package/dist/memory/meta-memory.js +3 -3
- package/dist/memory/meta-memory.js.map +1 -1
- package/dist/memory/session-tracker.d.ts +0 -2
- package/dist/memory/session-tracker.d.ts.map +1 -1
- package/dist/memory/session-tracker.js +8 -26
- package/dist/memory/session-tracker.js.map +1 -1
- package/dist/memory/temporal-engine.d.ts.map +1 -1
- package/dist/memory/temporal-engine.js +13 -9
- package/dist/memory/temporal-engine.js.map +1 -1
- package/dist/retrieval/hybrid-retriever.d.ts +2 -0
- package/dist/retrieval/hybrid-retriever.d.ts.map +1 -1
- package/dist/retrieval/hybrid-retriever.js +13 -3
- package/dist/retrieval/hybrid-retriever.js.map +1 -1
- package/dist/scanners/architecture-graph.js +2 -2
- package/dist/scanners/architecture-graph.js.map +1 -1
- package/dist/scanners/code-verifier.d.ts +0 -1
- package/dist/scanners/code-verifier.d.ts.map +1 -1
- package/dist/scanners/code-verifier.js +14 -14
- package/dist/scanners/code-verifier.js.map +1 -1
- package/dist/scanners/context-builder.d.ts.map +1 -1
- package/dist/scanners/context-builder.js +45 -33
- package/dist/scanners/context-builder.js.map +1 -1
- package/dist/scanners/export-map.js +2 -2
- package/dist/scanners/export-map.js.map +1 -1
- package/dist/scanners/project-scanner.js +2 -2
- package/dist/scanners/project-scanner.js.map +1 -1
- package/dist/security/encryption.js +1 -1
- package/dist/security/encryption.js.map +1 -1
- package/dist/security/feature-gate.d.ts.map +1 -1
- package/dist/security/feature-gate.js +20 -62
- package/dist/security/feature-gate.js.map +1 -1
- package/dist/security/license.js +35 -282
- package/dist/security/license.js.map +1 -1
- package/dist/security/rate-limiter.d.ts +3 -4
- package/dist/security/rate-limiter.d.ts.map +1 -1
- package/dist/security/rate-limiter.js +29 -11
- package/dist/security/rate-limiter.js.map +1 -1
- package/dist/server/dashboard.js +327 -166
- package/dist/server/dashboard.js.map +1 -1
- package/dist/server/mcp-handler.d.ts.map +1 -1
- package/dist/server/mcp-handler.js +827 -1002
- package/dist/server/mcp-handler.js.map +1 -1
- package/package.json +8 -18
- package/dist/memory/access-pattern-tracker.d.ts +0 -51
- package/dist/memory/access-pattern-tracker.d.ts.map +0 -1
- package/dist/memory/access-pattern-tracker.js +0 -92
- package/dist/memory/access-pattern-tracker.js.map +0 -1
- package/dist/memory/completion-resolver.d.ts +0 -38
- package/dist/memory/completion-resolver.d.ts.map +0 -1
- package/dist/memory/completion-resolver.js +0 -127
- package/dist/memory/completion-resolver.js.map +0 -1
- package/dist/memory/convention-detector.d.ts +0 -11
- package/dist/memory/convention-detector.d.ts.map +0 -1
- package/dist/memory/convention-detector.js +0 -294
- package/dist/memory/convention-detector.js.map +0 -1
- package/dist/memory/correction-detector.d.ts +0 -33
- package/dist/memory/correction-detector.d.ts.map +0 -1
- package/dist/memory/correction-detector.js +0 -129
- package/dist/memory/correction-detector.js.map +0 -1
- package/dist/memory/cross-memory-linker.d.ts +0 -18
- package/dist/memory/cross-memory-linker.d.ts.map +0 -1
- package/dist/memory/cross-memory-linker.js +0 -115
- package/dist/memory/cross-memory-linker.js.map +0 -1
- package/dist/memory/daily-diary.d.ts +0 -30
- package/dist/memory/daily-diary.d.ts.map +0 -1
- package/dist/memory/daily-diary.js +0 -159
- package/dist/memory/daily-diary.js.map +0 -1
- package/dist/memory/embedding-cache.d.ts +0 -32
- package/dist/memory/embedding-cache.d.ts.map +0 -1
- package/dist/memory/embedding-cache.js +0 -76
- package/dist/memory/embedding-cache.js.map +0 -1
- package/dist/memory/error-learner.d.ts +0 -26
- package/dist/memory/error-learner.d.ts.map +0 -1
- package/dist/memory/error-learner.js +0 -145
- package/dist/memory/error-learner.js.map +0 -1
- package/dist/memory/file-relationships.d.ts +0 -47
- package/dist/memory/file-relationships.d.ts.map +0 -1
- package/dist/memory/file-relationships.js +0 -130
- package/dist/memory/file-relationships.js.map +0 -1
- package/dist/memory/impact-analyzer.d.ts +0 -16
- package/dist/memory/impact-analyzer.d.ts.map +0 -1
- package/dist/memory/impact-analyzer.js +0 -189
- package/dist/memory/impact-analyzer.js.map +0 -1
- package/dist/memory/instructions-generator.d.ts +0 -30
- package/dist/memory/instructions-generator.d.ts.map +0 -1
- package/dist/memory/instructions-generator.js +0 -117
- package/dist/memory/instructions-generator.js.map +0 -1
- package/dist/memory/memory-export-md.d.ts +0 -12
- package/dist/memory/memory-export-md.d.ts.map +0 -1
- package/dist/memory/memory-export-md.js +0 -188
- package/dist/memory/memory-export-md.js.map +0 -1
- package/dist/memory/mmr-reranker.d.ts +0 -39
- package/dist/memory/mmr-reranker.d.ts.map +0 -1
- package/dist/memory/mmr-reranker.js +0 -115
- package/dist/memory/mmr-reranker.js.map +0 -1
- package/dist/memory/pre-flight.d.ts +0 -24
- package/dist/memory/pre-flight.d.ts.map +0 -1
- package/dist/memory/pre-flight.js +0 -121
- package/dist/memory/pre-flight.js.map +0 -1
- package/dist/memory/preference-learner.d.ts +0 -28
- package/dist/memory/preference-learner.d.ts.map +0 -1
- package/dist/memory/preference-learner.js +0 -144
- package/dist/memory/preference-learner.js.map +0 -1
- package/dist/memory/query-expansion.d.ts +0 -28
- package/dist/memory/query-expansion.d.ts.map +0 -1
- package/dist/memory/query-expansion.js +0 -140
- package/dist/memory/query-expansion.js.map +0 -1
- package/dist/memory/regression-guard.d.ts +0 -35
- package/dist/memory/regression-guard.d.ts.map +0 -1
- package/dist/memory/regression-guard.js +0 -90
- package/dist/memory/regression-guard.js.map +0 -1
- package/dist/memory/resume-work.d.ts +0 -37
- package/dist/memory/resume-work.d.ts.map +0 -1
- package/dist/memory/resume-work.js +0 -141
- package/dist/memory/resume-work.js.map +0 -1
- package/dist/memory/soul-manager.d.ts +0 -30
- package/dist/memory/soul-manager.d.ts.map +0 -1
- package/dist/memory/soul-manager.js +0 -171
- package/dist/memory/soul-manager.js.map +0 -1
- package/dist/memory/success-tracker.d.ts +0 -33
- package/dist/memory/success-tracker.d.ts.map +0 -1
- package/dist/memory/success-tracker.js +0 -75
- package/dist/memory/success-tracker.js.map +0 -1
- package/dist/memory/tool-recommender.d.ts +0 -29
- package/dist/memory/tool-recommender.d.ts.map +0 -1
- package/dist/memory/tool-recommender.js +0 -117
- package/dist/memory/tool-recommender.js.map +0 -1
- package/dist/memory/usage-stats.d.ts +0 -98
- package/dist/memory/usage-stats.d.ts.map +0 -1
- package/dist/memory/usage-stats.js +0 -345
- package/dist/memory/usage-stats.js.map +0 -1
- package/dist/utils/extract-tags.d.ts +0 -16
- package/dist/utils/extract-tags.d.ts.map +0 -1
- package/dist/utils/extract-tags.js +0 -40
- package/dist/utils/extract-tags.js.map +0 -1
|
@@ -23,35 +23,39 @@ const git_memory_1 = require("../memory/git-memory");
|
|
|
23
23
|
const export_map_1 = require("../scanners/export-map");
|
|
24
24
|
const architecture_graph_1 = require("../scanners/architecture-graph");
|
|
25
25
|
const rate_limiter_1 = require("../security/rate-limiter");
|
|
26
|
+
const license_1 = require("../security/license");
|
|
26
27
|
const feature_gate_1 = require("../security/feature-gate");
|
|
27
28
|
const export_import_1 = require("../memory/export-import");
|
|
28
29
|
const llm_enhancer_1 = require("../memory/llm-enhancer");
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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) ---
|
|
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
|
+
};
|
|
53
43
|
function expandQuery(query) {
|
|
54
|
-
|
|
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 ');
|
|
55
59
|
}
|
|
56
60
|
// --- MCP Tool Definitions ---
|
|
57
61
|
const MCP_TOOLS = [
|
|
@@ -139,7 +143,7 @@ const MCP_TOOLS = [
|
|
|
139
143
|
},
|
|
140
144
|
{
|
|
141
145
|
name: 'force_recall',
|
|
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.
|
|
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.',
|
|
143
147
|
inputSchema: {
|
|
144
148
|
type: 'object',
|
|
145
149
|
properties: {
|
|
@@ -198,7 +202,7 @@ const MCP_TOOLS = [
|
|
|
198
202
|
},
|
|
199
203
|
{
|
|
200
204
|
name: 'auto_learn',
|
|
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.
|
|
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.',
|
|
202
206
|
inputSchema: {
|
|
203
207
|
type: 'object',
|
|
204
208
|
properties: {
|
|
@@ -236,47 +240,157 @@ const MCP_TOOLS = [
|
|
|
236
240
|
},
|
|
237
241
|
},
|
|
238
242
|
{
|
|
239
|
-
name: '
|
|
240
|
-
description: '
|
|
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.',
|
|
241
245
|
inputSchema: {
|
|
242
246
|
type: 'object',
|
|
243
247
|
properties: {
|
|
244
|
-
|
|
245
|
-
|
|
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)' },
|
|
246
250
|
},
|
|
247
|
-
required: ['
|
|
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?".',
|
|
301
|
+
inputSchema: {
|
|
302
|
+
type: 'object',
|
|
303
|
+
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)' },
|
|
307
|
+
},
|
|
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'],
|
|
248
320
|
},
|
|
249
321
|
},
|
|
250
322
|
{
|
|
251
|
-
name: '
|
|
252
|
-
description: '
|
|
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.',
|
|
253
325
|
inputSchema: {
|
|
254
326
|
type: 'object',
|
|
255
327
|
properties: {
|
|
256
|
-
|
|
257
|
-
|
|
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)' },
|
|
258
330
|
},
|
|
331
|
+
required: ['id'],
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: 'backup_brain',
|
|
336
|
+
description: 'Create a local backup of the entire memory database file. Run this periodically to ensure the user NEVER loses their project knowledge.',
|
|
337
|
+
inputSchema: {
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {},
|
|
259
340
|
},
|
|
260
341
|
},
|
|
261
342
|
{
|
|
262
|
-
name: '
|
|
263
|
-
description: '
|
|
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.',
|
|
264
345
|
inputSchema: {
|
|
265
346
|
type: 'object',
|
|
266
347
|
properties: {
|
|
267
|
-
|
|
348
|
+
date: { type: 'string', description: 'Date in YYYY-MM-DD format (optional, defaults to today)' },
|
|
268
349
|
},
|
|
269
|
-
required: ['file'],
|
|
270
350
|
},
|
|
271
351
|
},
|
|
272
352
|
{
|
|
273
|
-
name: '
|
|
274
|
-
description: '
|
|
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.',
|
|
275
355
|
inputSchema: {
|
|
276
356
|
type: 'object',
|
|
277
357
|
properties: {},
|
|
278
358
|
},
|
|
279
359
|
},
|
|
360
|
+
{
|
|
361
|
+
name: 'export_memories',
|
|
362
|
+
description: 'Export ALL memories to a JSON file on disk. Use this to back up your project knowledge, share it with teammates, or migrate to a new machine. The JSON file can later be imported with import_memories.',
|
|
363
|
+
inputSchema: {
|
|
364
|
+
type: 'object',
|
|
365
|
+
properties: {
|
|
366
|
+
filePath: { type: 'string', description: 'Full path where the export JSON should be saved (e.g. /Users/me/cortex-backup.json)' },
|
|
367
|
+
},
|
|
368
|
+
required: ['filePath'],
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'import_memories',
|
|
373
|
+
description: 'Import memories from a previously exported JSON file. Skips duplicates automatically. Use this to restore a backup or share project knowledge with a teammate.',
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: 'object',
|
|
376
|
+
properties: {
|
|
377
|
+
filePath: { type: 'string', description: 'Full path to the cortex export JSON file to import' },
|
|
378
|
+
},
|
|
379
|
+
required: ['filePath'],
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: 'get_related_memories',
|
|
384
|
+
description: 'Follow the graph of memory relationships to find connected memories. Given a memory ID, traverses CAUSED_BY, FIXED_BY, RELATED_TO, and DEPENDS_ON edges to surface related context you might not have found with search.',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
properties: {
|
|
388
|
+
id: { type: 'string', description: 'ID of the starting memory (from recall_memory or list_memories)' },
|
|
389
|
+
maxHops: { type: 'number', description: 'How many relationship hops to traverse (default 2, max 4)' },
|
|
390
|
+
},
|
|
391
|
+
required: ['id'],
|
|
392
|
+
},
|
|
393
|
+
},
|
|
280
394
|
];
|
|
281
395
|
// --- Dynamic Context via ContextBuilder ---
|
|
282
396
|
let cachedContextBuilder = null;
|
|
@@ -298,12 +412,68 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
298
412
|
result: {
|
|
299
413
|
protocolVersion: '2024-11-05',
|
|
300
414
|
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
301
|
-
serverInfo: { name: '
|
|
415
|
+
serverInfo: { name: 'cortex', version: '2.7.0' },
|
|
302
416
|
},
|
|
303
417
|
};
|
|
304
418
|
case 'notifications/initialized':
|
|
305
|
-
case 'notifications/cancelled':
|
|
306
419
|
return null;
|
|
420
|
+
case 'prompts/list':
|
|
421
|
+
return {
|
|
422
|
+
jsonrpc: '2.0',
|
|
423
|
+
id,
|
|
424
|
+
result: {
|
|
425
|
+
prompts: [
|
|
426
|
+
{
|
|
427
|
+
name: 'cortex_system_context',
|
|
428
|
+
description: 'Injects crucial persistent project memories, decisions, corrections, and conventions directly into the system context. Use this to start every conversation with full project awareness.',
|
|
429
|
+
arguments: [
|
|
430
|
+
{
|
|
431
|
+
name: 'currentFile',
|
|
432
|
+
description: 'The currently active file path (optional, improves relevance)',
|
|
433
|
+
required: false,
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: 'topic',
|
|
437
|
+
description: 'What the user is asking about (optional, improves topic search)',
|
|
438
|
+
required: false,
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
case 'prompts/get': {
|
|
446
|
+
const promptName = rpc.params?.name;
|
|
447
|
+
const promptArgs = rpc.params?.arguments || {};
|
|
448
|
+
if (promptName === 'cortex_system_context') {
|
|
449
|
+
const builder = getContextBuilder(memoryStore);
|
|
450
|
+
const memoryPayload = builder.build({
|
|
451
|
+
currentFile: promptArgs.currentFile,
|
|
452
|
+
maxChars: 6000,
|
|
453
|
+
});
|
|
454
|
+
return {
|
|
455
|
+
jsonrpc: '2.0',
|
|
456
|
+
id,
|
|
457
|
+
result: {
|
|
458
|
+
description: 'Cortex Auto-Injected System Context',
|
|
459
|
+
messages: [
|
|
460
|
+
{
|
|
461
|
+
role: 'user',
|
|
462
|
+
content: {
|
|
463
|
+
type: 'text',
|
|
464
|
+
text: `You are connected to Cortex MCP — a persistent memory system. The following are critical memories, rules, and decisions from previous sessions. YOU MUST OBEY THESE RULES and reference this context when relevant:\n\n${memoryPayload}`,
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
jsonrpc: '2.0',
|
|
473
|
+
id,
|
|
474
|
+
error: { code: -32602, message: `Unknown prompt: ${promptName}` },
|
|
475
|
+
};
|
|
476
|
+
}
|
|
307
477
|
case 'tools/list':
|
|
308
478
|
return {
|
|
309
479
|
jsonrpc: '2.0',
|
|
@@ -317,7 +487,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
317
487
|
result: {
|
|
318
488
|
resources: [{
|
|
319
489
|
uri: 'memory://brain/context',
|
|
320
|
-
name: '
|
|
490
|
+
name: 'Brain Context',
|
|
321
491
|
description: 'Top memories — corrections, decisions, conventions. Read this before every response.',
|
|
322
492
|
mimeType: 'text/plain',
|
|
323
493
|
}],
|
|
@@ -344,97 +514,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
344
514
|
error: { code: -32602, message: `Unknown resource: ${uri}` },
|
|
345
515
|
};
|
|
346
516
|
}
|
|
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
|
-
}
|
|
438
517
|
case 'tools/call': {
|
|
439
518
|
const toolName = rpc.params?.name;
|
|
440
519
|
const args = rpc.params?.arguments || {};
|
|
@@ -445,10 +524,10 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
445
524
|
result: { content: [{ type: 'text', text: 'Error: query too long (max 1000 chars)' }], isError: true },
|
|
446
525
|
};
|
|
447
526
|
}
|
|
448
|
-
if (args.content && typeof args.content === 'string' && args.content.length >
|
|
527
|
+
if (args.content && typeof args.content === 'string' && args.content.length > 5000) {
|
|
449
528
|
return {
|
|
450
529
|
jsonrpc: '2.0', id,
|
|
451
|
-
result: { content: [{ type: 'text', text: 'Error: content too long (max
|
|
530
|
+
result: { content: [{ type: 'text', text: 'Error: content too long (max 5000 chars)' }], isError: true },
|
|
452
531
|
};
|
|
453
532
|
}
|
|
454
533
|
if (toolName === 'recall_memory') {
|
|
@@ -491,25 +570,49 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
491
570
|
return handleAutoLearn(id, args);
|
|
492
571
|
}
|
|
493
572
|
else if (toolName === 'export_memories') {
|
|
494
|
-
return
|
|
573
|
+
return handleExportMemoriesFn(memoryStore, id, args);
|
|
495
574
|
}
|
|
496
575
|
else if (toolName === 'import_memories') {
|
|
497
|
-
return
|
|
576
|
+
return handleImportMemoriesFn(memoryStore, memory_cache_1, id, args);
|
|
577
|
+
}
|
|
578
|
+
else if (toolName === 'get_related_memories') {
|
|
579
|
+
return handleGetRelatedMemoriesFn(memoryStore, id, args);
|
|
498
580
|
}
|
|
499
581
|
else if (toolName === 'health_check') {
|
|
500
582
|
return handleHealthCheck(id);
|
|
501
583
|
}
|
|
502
|
-
else if (toolName === '
|
|
503
|
-
return
|
|
584
|
+
else if (toolName === 'search_by_file') {
|
|
585
|
+
return handleSearchByFile(id, args);
|
|
586
|
+
}
|
|
587
|
+
else if (toolName === 'pin_memory') {
|
|
588
|
+
return handlePinMemory(id, args);
|
|
504
589
|
}
|
|
505
|
-
else if (toolName === '
|
|
506
|
-
return
|
|
590
|
+
else if (toolName === 'unpin_memory') {
|
|
591
|
+
return handleUnpinMemory(id, args);
|
|
507
592
|
}
|
|
508
|
-
else if (toolName === '
|
|
509
|
-
return
|
|
593
|
+
else if (toolName === 'undo_last') {
|
|
594
|
+
return handleUndoLast(id, args);
|
|
510
595
|
}
|
|
511
|
-
else if (toolName === '
|
|
512
|
-
return
|
|
596
|
+
else if (toolName === 'clear_memories') {
|
|
597
|
+
return handleClearMemories(id, args);
|
|
598
|
+
}
|
|
599
|
+
else if (toolName === 'search_timeline') {
|
|
600
|
+
return handleSearchTimeline(id, args);
|
|
601
|
+
}
|
|
602
|
+
else if (toolName === 'thumbs_up') {
|
|
603
|
+
return handleThumbsUp(id, args);
|
|
604
|
+
}
|
|
605
|
+
else if (toolName === 'thumbs_down') {
|
|
606
|
+
return handleThumbsDown(id, args);
|
|
607
|
+
}
|
|
608
|
+
else if (toolName === 'backup_brain') {
|
|
609
|
+
return handleBackupBrain(id);
|
|
610
|
+
}
|
|
611
|
+
else if (toolName === 'get_daily_summary') {
|
|
612
|
+
return handleGetDailySummary(id, args);
|
|
613
|
+
}
|
|
614
|
+
else if (toolName === 'analytics') {
|
|
615
|
+
return handleAnalytics(id);
|
|
513
616
|
}
|
|
514
617
|
else {
|
|
515
618
|
return {
|
|
@@ -562,65 +665,27 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
562
665
|
if (ftsResults.length === 0) {
|
|
563
666
|
ftsResults = memoryStore.searchFTS(queryText, maxResults * 2);
|
|
564
667
|
}
|
|
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
|
-
}
|
|
574
668
|
console.log(` [FTS] ${ftsResults.length} results`);
|
|
575
|
-
// 2. Vector Search (if worker ready)
|
|
669
|
+
// 2. Vector Search (if worker ready)
|
|
576
670
|
let vectorResults = [];
|
|
577
671
|
if ((0, embedding_manager_1.isWorkerReady)()) {
|
|
578
|
-
|
|
579
|
-
|
|
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);
|
|
672
|
+
const embedding = await (0, embedding_manager_1.embedText)(queryText);
|
|
673
|
+
vectorResults = memoryStore.searchVector(new Float32Array(embedding), maxResults * 2);
|
|
585
674
|
if (vectorResults.length > 0) {
|
|
586
675
|
console.log(` [VECTOR] ${vectorResults.length} results`);
|
|
587
676
|
}
|
|
588
677
|
}
|
|
589
678
|
// 3. Hybrid Ranking
|
|
590
679
|
const rawRanked = (0, memory_ranker_1.rankResults)(ftsResults, vectorResults, maxResults * 2, currentFile);
|
|
591
|
-
//
|
|
592
|
-
|
|
680
|
+
// Map RankedResult to ScoredMemory
|
|
681
|
+
ranked = rawRanked.map(r => ({
|
|
593
682
|
...r,
|
|
594
683
|
matchMethod: 'hybrid'
|
|
595
|
-
}))
|
|
596
|
-
//
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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 */ }
|
|
684
|
+
}));
|
|
685
|
+
// 3b. Context-based boost (if current file provided)
|
|
686
|
+
if (args.currentFile) {
|
|
687
|
+
// Boost memories related to this file or its directory
|
|
688
|
+
// Implementation in memory-ranker.ts (pending)
|
|
624
689
|
}
|
|
625
690
|
// 3c. Apply attention-based re-ranking (debugging→bugs, coding→conventions)
|
|
626
691
|
const recallContext = (0, attention_ranker_1.detectActionContext)(queryText, currentFile);
|
|
@@ -653,16 +718,14 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
653
718
|
ranked = ranked.slice(0, maxResults);
|
|
654
719
|
// 4. Touch for access tracking (reinforcement — used memories get stronger)
|
|
655
720
|
if (ranked.length > 0) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
721
|
+
await Promise.all(ranked.map(m => {
|
|
722
|
+
try {
|
|
723
|
+
return memoryStore.touch(m.memory.id);
|
|
724
|
+
}
|
|
725
|
+
catch {
|
|
726
|
+
return Promise.resolve();
|
|
662
727
|
}
|
|
663
|
-
});
|
|
664
|
-
// NEW: Record access patterns for personalized boosting
|
|
665
|
-
(0, access_pattern_tracker_1.recordBatchAccess)(ranked.map(r => ({ type: r.memory.type })));
|
|
728
|
+
}));
|
|
666
729
|
(0, confidence_decay_1.runDecayMaintenance)(memoryStore); // Opportunistic decay
|
|
667
730
|
}
|
|
668
731
|
(0, memory_cache_1.setCache)(cacheKey, ranked);
|
|
@@ -715,7 +778,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
715
778
|
}
|
|
716
779
|
try {
|
|
717
780
|
// License check — gate memory storage
|
|
718
|
-
const activeCount = memoryStore.
|
|
781
|
+
const activeCount = memoryStore.getActive(9999).length;
|
|
719
782
|
const storeCheck = (0, feature_gate_1.canStoreMemory)(activeCount);
|
|
720
783
|
if (!storeCheck.allowed) {
|
|
721
784
|
return {
|
|
@@ -772,24 +835,25 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
772
835
|
}
|
|
773
836
|
}
|
|
774
837
|
catch { /* non-fatal */ }
|
|
775
|
-
//
|
|
838
|
+
// Auto-edge creation — link to recent memories of same type/files
|
|
776
839
|
try {
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
840
|
+
const recent = memoryStore.getByType(memType, 5);
|
|
841
|
+
for (const r of recent) {
|
|
842
|
+
if (r.id !== memory.id) {
|
|
843
|
+
memoryStore.addEdge({
|
|
844
|
+
sourceId: memory.id,
|
|
845
|
+
targetId: r.id,
|
|
846
|
+
relation: 'related_to',
|
|
847
|
+
weight: 0.5,
|
|
848
|
+
timestamp: Date.now(),
|
|
849
|
+
});
|
|
850
|
+
break; // Link to most recent only
|
|
851
|
+
}
|
|
852
|
+
}
|
|
780
853
|
}
|
|
781
854
|
catch { /* non-fatal */ }
|
|
782
855
|
// Feed session tracker
|
|
783
856
|
(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 */ }
|
|
793
857
|
// Queue background embedding
|
|
794
858
|
if ((0, embedding_manager_1.isWorkerReady)()) {
|
|
795
859
|
const embedText_ = [sanitized, reason || ''].join(' ').trim();
|
|
@@ -812,15 +876,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
812
876
|
}
|
|
813
877
|
}
|
|
814
878
|
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 { /* */ }
|
|
824
879
|
return {
|
|
825
880
|
jsonrpc: '2.0', id,
|
|
826
881
|
result: {
|
|
@@ -832,16 +887,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
832
887
|
totalEvents: eventLog.count(),
|
|
833
888
|
vectorSearchReady: (0, embedding_manager_1.isWorkerReady)(),
|
|
834
889
|
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,
|
|
845
890
|
}, null, 2),
|
|
846
891
|
}],
|
|
847
892
|
},
|
|
@@ -871,46 +916,16 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
871
916
|
extraMemories += (0, architecture_graph_1.storeArchitectureGraph)(memoryStore, archGraph);
|
|
872
917
|
}
|
|
873
918
|
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 */ }
|
|
894
919
|
(0, memory_cache_1.invalidateCache)();
|
|
895
|
-
(0, usage_stats_1.trackScan)();
|
|
896
920
|
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 */ }
|
|
906
921
|
return {
|
|
907
922
|
jsonrpc: '2.0', id,
|
|
908
923
|
result: {
|
|
909
924
|
content: [{
|
|
910
925
|
type: 'text',
|
|
911
926
|
text: total > 0
|
|
912
|
-
? `Project scanned successfully. ${total} memories created (stack, structure, config, git history, export map, architecture graph
|
|
913
|
-
:
|
|
927
|
+
? `Project scanned successfully. ${total} memories created (stack, structure, config, git history, export map, architecture graph).`
|
|
928
|
+
: 'Project was already scanned. No new memories created.',
|
|
914
929
|
}],
|
|
915
930
|
},
|
|
916
931
|
};
|
|
@@ -986,9 +1001,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
986
1001
|
};
|
|
987
1002
|
}
|
|
988
1003
|
try {
|
|
989
|
-
// Track file for relationship mapping
|
|
990
|
-
if (args.filename)
|
|
991
|
-
(0, file_relationships_1.recordFileEdit)(args.filename);
|
|
992
1004
|
const result = (0, code_verifier_1.verifyCode)(args.code, root);
|
|
993
1005
|
const lines = [];
|
|
994
1006
|
// Imports
|
|
@@ -1015,20 +1027,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1015
1027
|
for (const [file, available] of Object.entries(result.exports.available)) {
|
|
1016
1028
|
lines.push(` ${file} exports: ${available.join(', ')}`);
|
|
1017
1029
|
}
|
|
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 */ }
|
|
1032
1030
|
}
|
|
1033
1031
|
}
|
|
1034
1032
|
// Env vars
|
|
@@ -1047,12 +1045,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1047
1045
|
if (lines.length === 0) {
|
|
1048
1046
|
lines.push('No imports, exports, or env vars detected in the code.');
|
|
1049
1047
|
}
|
|
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
|
-
}
|
|
1056
1048
|
return {
|
|
1057
1049
|
jsonrpc: '2.0', id,
|
|
1058
1050
|
result: { content: [{ type: 'text', text: lines.join('\n') }] },
|
|
@@ -1136,107 +1128,33 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1136
1128
|
}
|
|
1137
1129
|
async function handleForceRecall(id, args) {
|
|
1138
1130
|
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
|
-
}
|
|
1146
1131
|
const parts = [];
|
|
1147
1132
|
// ─── BRAIN LAYER 0: End previous session + start new one ─────────
|
|
1148
1133
|
(0, session_tracker_1.endSession)(memoryStore); // Save previous session summary
|
|
1149
|
-
(0, file_relationships_1.storeRelationships)(memoryStore); // Persist file co-edit relationships
|
|
1150
1134
|
(0, session_tracker_1.startSession)();
|
|
1151
|
-
(0, usage_stats_1.resetSessionStats)();
|
|
1152
1135
|
if (args.topic)
|
|
1153
1136
|
(0, session_tracker_1.feedSession)({ topic: args.topic });
|
|
1154
|
-
// ───
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1137
|
+
// ─── BRAIN LAYER 1: Maintenance (runs in background) ─────────────
|
|
1138
|
+
try {
|
|
1139
|
+
// Decay old unused memories
|
|
1140
|
+
(0, confidence_decay_1.runDecayMaintenance)(memoryStore);
|
|
1141
|
+
// Boost frequently corrected topics
|
|
1142
|
+
(0, learning_rate_1.boostFrequentCorrections)(memoryStore);
|
|
1143
|
+
// Consolidate similar memories if needed
|
|
1144
|
+
if ((0, memory_consolidator_1.shouldConsolidate)(memoryStore)) {
|
|
1145
|
+
(0, memory_consolidator_1.consolidateMemories)(memoryStore);
|
|
1161
1146
|
}
|
|
1162
|
-
catch { /* soul is non-critical */ }
|
|
1163
1147
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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 });
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
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}`);
|
|
1148
|
+
catch { /* maintenance errors are non-fatal */ }
|
|
1149
|
+
// ─── BRAIN LAYER 1.5: PINNED MEMORIES (Critical Rules) ───────────
|
|
1150
|
+
// Scan ALL memories for pinned — critical rules must never be missed
|
|
1151
|
+
const pinnedMemories = memoryStore.getActive(10000).filter(m => (m.tags || []).includes('pinned'));
|
|
1152
|
+
if (pinnedMemories.length > 0) {
|
|
1153
|
+
parts.push('\n## 📌 PINNED CRITICAL RULES (Must Follow)');
|
|
1154
|
+
for (const p of pinnedMemories) {
|
|
1155
|
+
parts.push(`- **[${p.type}]** ${p.intent}`);
|
|
1208
1156
|
}
|
|
1209
1157
|
}
|
|
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
1158
|
// ─── BRAIN LAYER 2: Attention Context ────────────────────────────
|
|
1241
1159
|
const actionContext = (0, feature_gate_1.isFeatureAllowed)('attentionRanking') ? (0, attention_ranker_1.detectActionContext)(args.topic, args.currentFile) : {};
|
|
1242
1160
|
const attentionLabel = (0, feature_gate_1.isFeatureAllowed)('attentionRanking') ? (0, attention_ranker_1.formatAttentionContext)(actionContext) : '';
|
|
@@ -1260,335 +1178,149 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1260
1178
|
maxChars: 8000, // leave room for brain layers
|
|
1261
1179
|
});
|
|
1262
1180
|
parts.push(fullContext);
|
|
1263
|
-
// ─── BRAIN
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
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 '';
|
|
1181
|
+
// ─── BRAIN LAYER 6: Anticipation (file-aware proactive recall) ───
|
|
1182
|
+
if (args.currentFile && (0, feature_gate_1.isFeatureAllowed)('anticipation')) {
|
|
1183
|
+
const anticipated = (0, anticipation_engine_1.formatAnticipation)((0, anticipation_engine_1.anticipate)(memoryStore, args.currentFile));
|
|
1184
|
+
if (anticipated)
|
|
1185
|
+
parts.push('\n' + anticipated);
|
|
1186
|
+
}
|
|
1187
|
+
// ─── BRAIN LAYER 7: Temporal Context (what changed recently) ─────
|
|
1188
|
+
if ((0, feature_gate_1.isFeatureAllowed)('temporalContext')) {
|
|
1189
|
+
const temporal = (0, temporal_engine_1.formatTemporalContext)(memoryStore);
|
|
1190
|
+
if (temporal)
|
|
1191
|
+
parts.push('\n' + temporal);
|
|
1192
|
+
}
|
|
1193
|
+
// ─── BRAIN LAYER 8: Workspace State (git changes) ────────────────
|
|
1194
|
+
try {
|
|
1195
|
+
const workspace = (0, temporal_engine_1.getWorkspaceDiff)(workspaceRoot || '');
|
|
1196
|
+
if (workspace)
|
|
1197
|
+
parts.push('\n' + workspace);
|
|
1198
|
+
}
|
|
1199
|
+
catch { /* git not available */ }
|
|
1200
|
+
// ─── BRAIN LAYER 8.5: Git Memory (commit capture + file changes) ───
|
|
1201
|
+
if ((0, feature_gate_1.isFeatureAllowed)('gitMemory')) {
|
|
1202
|
+
try {
|
|
1203
|
+
// Capture recent commits as memories
|
|
1204
|
+
const commitsCaptured = (0, git_memory_1.captureGitCommits)(memoryStore, workspaceRoot || '', 5);
|
|
1205
|
+
if (commitsCaptured > 0) {
|
|
1206
|
+
parts.push(`\n> Captured ${commitsCaptured} new git commit(s) as memories`);
|
|
1328
1207
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
if (
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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
|
-
}
|
|
1208
|
+
// Show uncommitted file changes
|
|
1209
|
+
const fileChanges = (0, git_memory_1.detectFileChanges)(workspaceRoot || '');
|
|
1210
|
+
const fileChangeText = (0, git_memory_1.formatFileChanges)(fileChanges);
|
|
1211
|
+
if (fileChangeText)
|
|
1212
|
+
parts.push('\n' + fileChangeText);
|
|
1213
|
+
}
|
|
1214
|
+
catch { /* git not available */ }
|
|
1215
|
+
} // end isPro() for git memory
|
|
1216
|
+
// ─── BRAIN LAYER 9: Topic-Specific Search ────────────────────────
|
|
1217
|
+
if (args.topic) {
|
|
1218
|
+
try {
|
|
1219
|
+
let ftsResults = memoryStore.searchFTS(args.topic, 15);
|
|
1220
|
+
// Apply confidence decay + attention ranking
|
|
1221
|
+
ftsResults = (0, confidence_decay_1.applyConfidenceDecay)(ftsResults);
|
|
1222
|
+
ftsResults = (0, attention_ranker_1.rankByAttention)(ftsResults, actionContext);
|
|
1223
|
+
// Causal chain: follow graph edges for top results
|
|
1224
|
+
const seen = new Set();
|
|
1225
|
+
const enriched = [];
|
|
1226
|
+
for (const r of ftsResults) {
|
|
1227
|
+
if (seen.has(r.memory.id))
|
|
1228
|
+
continue;
|
|
1229
|
+
seen.add(r.memory.id);
|
|
1230
|
+
enriched.push(r);
|
|
1231
|
+
// Follow causal links (1 hop)
|
|
1232
|
+
try {
|
|
1233
|
+
const related = memoryStore.getRelated(r.memory.id, 1, 3);
|
|
1234
|
+
for (const rel of related) {
|
|
1235
|
+
if (!seen.has(rel.memory.id)) {
|
|
1236
|
+
seen.add(rel.memory.id);
|
|
1237
|
+
enriched.push({ ...rel, score: rel.score * 0.7 });
|
|
1365
1238
|
}
|
|
1366
1239
|
}
|
|
1367
|
-
catch { }
|
|
1368
1240
|
}
|
|
1369
|
-
|
|
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');
|
|
1375
|
-
}
|
|
1376
|
-
return '';
|
|
1377
|
-
}
|
|
1378
|
-
catch {
|
|
1379
|
-
return '\n> Note: Topic search unavailable (FTS index needs rebuild).';
|
|
1241
|
+
catch { }
|
|
1380
1242
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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 '';
|
|
1243
|
+
if (enriched.length > 0) {
|
|
1244
|
+
parts.push('\n## Topic: "' + args.topic + '"');
|
|
1245
|
+
for (const m of enriched.slice(0, 15)) {
|
|
1246
|
+
parts.push(`- [${m.memory.type}] ${m.memory.intent}${m.memory.reason ? ` — ${m.memory.reason}` : ''}`);
|
|
1247
|
+
}
|
|
1420
1248
|
}
|
|
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);
|
|
1427
1249
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
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');
|
|
1441
|
-
}
|
|
1442
|
-
if (output.length > MAX_CHARS) {
|
|
1443
|
-
output = output.slice(0, MAX_CHARS);
|
|
1250
|
+
catch {
|
|
1251
|
+
parts.push('\n> Note: Topic search unavailable (FTS index needs rebuild).');
|
|
1444
1252
|
}
|
|
1445
|
-
output += '\n\n> (Some context trimmed to fit token budget. Use `recall_memory` for specific queries.)';
|
|
1446
1253
|
}
|
|
1447
|
-
//
|
|
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)
|
|
1254
|
+
// ─── BRAIN LAYER 10: Knowledge Gaps (meta-memory) ────────────────
|
|
1451
1255
|
try {
|
|
1452
|
-
const
|
|
1453
|
-
const
|
|
1454
|
-
if (
|
|
1455
|
-
|
|
1256
|
+
const gaps = (0, meta_memory_1.detectKnowledgeGaps)(memoryStore, workspaceRoot || '');
|
|
1257
|
+
const gapText = (0, meta_memory_1.formatKnowledgeGaps)(gaps);
|
|
1258
|
+
if (gapText)
|
|
1259
|
+
parts.push('\n' + gapText);
|
|
1456
1260
|
}
|
|
1457
1261
|
catch { /* non-fatal */ }
|
|
1458
|
-
//
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
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
|
-
}
|
|
1262
|
+
// ─── BRAIN LAYER 11: Export Map (anti-hallucination) ──────────────
|
|
1263
|
+
if (workspaceRoot && (0, feature_gate_1.isFeatureAllowed)('exportMap')) {
|
|
1264
|
+
try {
|
|
1265
|
+
const exportMap = (0, export_map_1.buildExportMap)(workspaceRoot);
|
|
1266
|
+
if (exportMap.totalExports > 0) {
|
|
1267
|
+
const exportText = (0, export_map_1.formatExportMap)(exportMap);
|
|
1268
|
+
if (exportText)
|
|
1269
|
+
parts.push('\n' + exportText);
|
|
1535
1270
|
}
|
|
1536
1271
|
}
|
|
1272
|
+
catch { /* non-fatal */ }
|
|
1537
1273
|
}
|
|
1538
|
-
//
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
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}"`);
|
|
1274
|
+
// ─── BRAIN LAYER 12: Architecture Graph (deep understanding) ──────
|
|
1275
|
+
if (workspaceRoot && (0, feature_gate_1.isFeatureAllowed)('architectureGraph')) {
|
|
1276
|
+
try {
|
|
1277
|
+
const archGraph = (0, architecture_graph_1.buildArchitectureGraph)(workspaceRoot);
|
|
1278
|
+
if (archGraph.totalFiles > 0) {
|
|
1279
|
+
const archText = (0, architecture_graph_1.formatArchitectureGraph)(archGraph);
|
|
1280
|
+
if (archText)
|
|
1281
|
+
parts.push('\n' + archText);
|
|
1282
|
+
}
|
|
1557
1283
|
}
|
|
1284
|
+
catch { /* non-fatal */ }
|
|
1558
1285
|
}
|
|
1559
|
-
//
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1286
|
+
// ─── SMART CONTEXT SELECTION: Trim to token budget ───────────────
|
|
1287
|
+
// ─── SMART CONTEXT TRIMMING: Remove low-priority sections first ─
|
|
1288
|
+
const MAX_CHARS = 12000; // ~3000 tokens — fits any model
|
|
1289
|
+
let output;
|
|
1290
|
+
if (parts.join('\n').length > MAX_CHARS) {
|
|
1291
|
+
// Priority order: keep critical layers, trim optional ones from the end
|
|
1292
|
+
// Parts are added in priority order (sessions, corrections, core context first)
|
|
1293
|
+
// So we trim from the END (architecture graph, export map, knowledge gaps)
|
|
1294
|
+
let trimmedParts = [...parts];
|
|
1295
|
+
let totalLen = trimmedParts.join('\n').length;
|
|
1296
|
+
while (totalLen > MAX_CHARS && trimmedParts.length > 3) {
|
|
1297
|
+
trimmedParts.pop(); // Remove lowest priority section
|
|
1298
|
+
totalLen = trimmedParts.join('\n').length;
|
|
1299
|
+
}
|
|
1300
|
+
output = trimmedParts.join('\n');
|
|
1301
|
+
if (output.length > MAX_CHARS) {
|
|
1302
|
+
output = output.slice(0, MAX_CHARS);
|
|
1564
1303
|
}
|
|
1304
|
+
output += '\n\n> (Lower-priority context removed to fit token budget. Use `recall_memory` for specific queries.)';
|
|
1305
|
+
} else {
|
|
1306
|
+
output = parts.join('\n');
|
|
1565
1307
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
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._`);
|
|
1308
|
+
// ─── DAY 1 EMPTY BRAIN: Guide new users ──────────────────────────
|
|
1309
|
+
if (memoryStore.activeCount() === 0) {
|
|
1310
|
+
return {
|
|
1311
|
+
jsonrpc: '2.0', id,
|
|
1312
|
+
result: { content: [{ type: 'text', text: '## 🧠 Cortex Brain — Day 1\n\nNo memories yet. Here is how to build your brain:\n\n1. **Scan this project** — call `scan_project` with the workspace root. This creates memories from your package.json, README, git history, and code structure.\n2. **Just work normally** — after every response, `auto_learn` will automatically extract and store decisions, corrections, and bug fixes.\n3. **Store manually** — use `store_memory` or `quick_store` for important rules.\n\nOnce you have memories, `force_recall` will inject them here automatically.' }] },
|
|
1313
|
+
};
|
|
1582
1314
|
}
|
|
1583
1315
|
return {
|
|
1584
1316
|
jsonrpc: '2.0', id,
|
|
1585
|
-
result: { content: [{ type: 'text', text:
|
|
1317
|
+
result: { content: [{ type: 'text', text: output }] },
|
|
1586
1318
|
};
|
|
1587
1319
|
}
|
|
1588
1320
|
catch (err) {
|
|
1589
1321
|
return {
|
|
1590
1322
|
jsonrpc: '2.0', id,
|
|
1591
|
-
result: { content: [{ type: 'text', text: `
|
|
1323
|
+
result: { content: [{ type: 'text', text: `Force recall error: ${err.message}` }], isError: true },
|
|
1592
1324
|
};
|
|
1593
1325
|
}
|
|
1594
1326
|
}
|
|
@@ -1732,7 +1464,7 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1732
1464
|
}
|
|
1733
1465
|
async function handleAutoLearn(id, args) {
|
|
1734
1466
|
try {
|
|
1735
|
-
// Feature gate (
|
|
1467
|
+
// Feature gate check (auto_learn is now free!)
|
|
1736
1468
|
if (!(0, feature_gate_1.isFeatureAllowed)('autoLearn')) {
|
|
1737
1469
|
return {
|
|
1738
1470
|
jsonrpc: '2.0', id,
|
|
@@ -1756,50 +1488,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1756
1488
|
}
|
|
1757
1489
|
// Extract memory-worthy patterns (regex-based)
|
|
1758
1490
|
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 */ }
|
|
1803
1491
|
// LLM enhancement: when API key is available and regex found nothing,
|
|
1804
1492
|
// use LLM to catch implicit patterns that keywords miss
|
|
1805
1493
|
if (extracted.length === 0 && (0, llm_enhancer_1.isLLMAvailable)() && text.length > 50) {
|
|
@@ -1827,62 +1515,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1827
1515
|
};
|
|
1828
1516
|
}
|
|
1829
1517
|
// 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
|
-
}
|
|
1886
1518
|
const stored = [];
|
|
1887
1519
|
const skipped = [];
|
|
1888
1520
|
for (const item of extracted) {
|
|
@@ -1908,25 +1540,31 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1908
1540
|
(0, session_tracker_1.feedSession)({ topic: item.content.slice(0, 60) });
|
|
1909
1541
|
break;
|
|
1910
1542
|
}
|
|
1543
|
+
// Map auto_learn string types to MemoryType enum values
|
|
1544
|
+
// (auto_learner returns uppercase like "DECISION", but MemoryType enum uses lowercase like "decision")
|
|
1545
|
+
const autoLearnTypeMap = {
|
|
1546
|
+
'DECISION': types_1.MemoryType.DECISION || 'decision',
|
|
1547
|
+
'CORRECTION': types_1.MemoryType.CORRECTION || 'correction',
|
|
1548
|
+
'BUG_FIX': types_1.MemoryType.BUG_FIX || 'bug_fix',
|
|
1549
|
+
'CONVENTION': types_1.MemoryType.CONVENTION || 'convention',
|
|
1550
|
+
'INSIGHT': types_1.MemoryType.INSIGHT || 'insight',
|
|
1551
|
+
'FAILED_ATTEMPT': types_1.MemoryType.FAILED_SUGGESTION || 'failed_suggestion',
|
|
1552
|
+
'BUSINESS_RULE': types_1.MemoryType.DECISION || 'decision',
|
|
1553
|
+
'GOTCHA': types_1.MemoryType.CORRECTION || 'correction',
|
|
1554
|
+
'CURRENT_TASK': types_1.MemoryType.INSIGHT || 'insight',
|
|
1555
|
+
};
|
|
1556
|
+
const mappedType = autoLearnTypeMap[item.type] || item.type.toLowerCase();
|
|
1911
1557
|
const result = (0, memory_quality_1.storeWithQuality)(memoryStore, {
|
|
1912
|
-
type:
|
|
1558
|
+
type: mappedType,
|
|
1913
1559
|
intent: item.content,
|
|
1914
|
-
action:
|
|
1560
|
+
action: item.content,
|
|
1915
1561
|
reason: item.reason,
|
|
1916
1562
|
confidence: item.confidence,
|
|
1917
|
-
importance:
|
|
1918
|
-
tags:
|
|
1563
|
+
importance: item.confidence,
|
|
1564
|
+
tags: [item.type.toLowerCase()],
|
|
1919
1565
|
});
|
|
1920
|
-
if (result
|
|
1566
|
+
if (result) {
|
|
1921
1567
|
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 */ }
|
|
1930
1568
|
}
|
|
1931
1569
|
else {
|
|
1932
1570
|
skipped.push(`[${item.type}] ${item.content.slice(0, 40)}… (duplicate)`);
|
|
@@ -1938,144 +1576,6 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
1938
1576
|
}
|
|
1939
1577
|
if (stored.length > 0) {
|
|
1940
1578
|
(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
|
-
}
|
|
2079
1579
|
}
|
|
2080
1580
|
const lines = ['**Auto-Learn Results:**'];
|
|
2081
1581
|
if (stored.length > 0) {
|
|
@@ -2158,16 +1658,14 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
2158
1658
|
'# Cortex Health Check\n',
|
|
2159
1659
|
`| Metric | Value |`,
|
|
2160
1660
|
`|--------|-------|`,
|
|
1661
|
+
`| Plan | **Full Brain — 100% Free** |`,
|
|
2161
1662
|
`| Active Memories | ${activeCount} |`,
|
|
2162
|
-
`|
|
|
2163
|
-
`| Session
|
|
2164
|
-
`| Session
|
|
1663
|
+
`| Brain Layers | All 12 Active |`,
|
|
1664
|
+
`| Session Store Count | ${stats.storeCount}/500 |`,
|
|
1665
|
+
`| Session Auto-Learn Count | ${stats.autoLearnCount}/1000 |`,
|
|
1666
|
+
`| Session Total Calls | ${stats.totalCalls}/5000 |`,
|
|
2165
1667
|
`| Uptime | ${Math.floor(stats.uptime / 60)}m ${stats.uptime % 60}s |`,
|
|
2166
|
-
`|
|
|
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 |`,
|
|
1668
|
+
`| Status | ✅ Healthy |`,
|
|
2171
1669
|
];
|
|
2172
1670
|
return {
|
|
2173
1671
|
jsonrpc: '2.0', id,
|
|
@@ -2181,101 +1679,428 @@ function createMCPHandler(memoryStore, eventLog, workspaceRoot) {
|
|
|
2181
1679
|
};
|
|
2182
1680
|
}
|
|
2183
1681
|
}
|
|
2184
|
-
//
|
|
2185
|
-
function
|
|
1682
|
+
// ═══ NEW TOOLS: search_by_file, pin, unpin, undo, clear, timeline, thumbs ═══
|
|
1683
|
+
function handleSearchByFile(id, args) {
|
|
2186
1684
|
try {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
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
|
-
}
|
|
1685
|
+
const filePath = args.filePath;
|
|
1686
|
+
const limit = args.limit || 20;
|
|
1687
|
+
if (!filePath) {
|
|
1688
|
+
return {
|
|
1689
|
+
jsonrpc: '2.0', id,
|
|
1690
|
+
result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true },
|
|
1691
|
+
};
|
|
2199
1692
|
}
|
|
2200
|
-
//
|
|
2201
|
-
|
|
1693
|
+
// Use the existing getByFile method + FTS fallback
|
|
1694
|
+
let memories = memoryStore.getByFile(filePath, limit);
|
|
1695
|
+
// Also do FTS search for the filename
|
|
1696
|
+
if (memories.length < limit) {
|
|
1697
|
+
const basename = filePath.split(/[\\/]/).pop() || filePath;
|
|
2202
1698
|
try {
|
|
2203
|
-
const
|
|
2204
|
-
const
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
if (key.endsWith(basename)) {
|
|
2209
|
-
deps = node;
|
|
2210
|
-
break;
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
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(', ')}`;
|
|
1699
|
+
const ftsResults = memoryStore.searchFTS(basename, limit - memories.length);
|
|
1700
|
+
const existingIds = new Set(memories.map(m => m.id));
|
|
1701
|
+
for (const r of ftsResults) {
|
|
1702
|
+
if (!existingIds.has(r.memory.id)) {
|
|
1703
|
+
memories.push(r.memory);
|
|
2220
1704
|
}
|
|
2221
1705
|
}
|
|
2222
|
-
}
|
|
2223
|
-
|
|
1706
|
+
} catch { /* FTS may fail on special characters */ }
|
|
1707
|
+
}
|
|
1708
|
+
if (memories.length === 0) {
|
|
1709
|
+
return {
|
|
1710
|
+
jsonrpc: '2.0', id,
|
|
1711
|
+
result: { content: [{ type: 'text', text: `No memories found for file: "${filePath}"` }] },
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
const lines = [`# Memories related to "${filePath}"\n`];
|
|
1715
|
+
for (const m of memories) {
|
|
1716
|
+
const age = Math.floor((Date.now() - m.createdAt) / 86400000);
|
|
1717
|
+
lines.push(`- **[${m.type}]** ${m.intent}`);
|
|
1718
|
+
if (m.reason) lines.push(` _${m.reason}_`);
|
|
1719
|
+
lines.push(` \`id: ${m.id}\` · ${age}d old`);
|
|
2224
1720
|
}
|
|
1721
|
+
lines.push(`\n_Total: ${memories.length} memories for this file._`);
|
|
2225
1722
|
return {
|
|
2226
1723
|
jsonrpc: '2.0', id,
|
|
2227
|
-
result: { content: [{ type: 'text', text }] },
|
|
1724
|
+
result: { content: [{ type: 'text', text: lines.join('\n') }] },
|
|
1725
|
+
};
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
return {
|
|
1728
|
+
jsonrpc: '2.0', id,
|
|
1729
|
+
result: { content: [{ type: 'text', text: `search_by_file error: ${err.message}` }], isError: true },
|
|
2228
1730
|
};
|
|
2229
1731
|
}
|
|
2230
|
-
|
|
1732
|
+
}
|
|
1733
|
+
function handlePinMemory(id, args) {
|
|
1734
|
+
try {
|
|
1735
|
+
const memoryId = args.id;
|
|
1736
|
+
if (!memoryId) {
|
|
1737
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
|
|
1738
|
+
}
|
|
1739
|
+
const existing = memoryStore.get(memoryId);
|
|
1740
|
+
if (!existing) {
|
|
1741
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
|
|
1742
|
+
}
|
|
1743
|
+
const tags = existing.tags || [];
|
|
1744
|
+
if (!tags.includes('pinned')) tags.push('pinned');
|
|
1745
|
+
memoryStore.update(memoryId, { importance: 1.0, tags });
|
|
1746
|
+
(0, memory_cache_1.invalidateCache)();
|
|
1747
|
+
return {
|
|
1748
|
+
jsonrpc: '2.0', id,
|
|
1749
|
+
result: { content: [{ type: 'text', text: `📌 Memory PINNED: "${existing.intent}"\n\nThis memory will ALWAYS be included in context, regardless of topic.` }] },
|
|
1750
|
+
};
|
|
1751
|
+
} catch (err) {
|
|
1752
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `pin error: ${err.message}` }], isError: true } };
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
function handleUnpinMemory(id, args) {
|
|
1756
|
+
try {
|
|
1757
|
+
const memoryId = args.id;
|
|
1758
|
+
if (!memoryId) {
|
|
1759
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
|
|
1760
|
+
}
|
|
1761
|
+
const existing = memoryStore.get(memoryId);
|
|
1762
|
+
if (!existing) {
|
|
1763
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
|
|
1764
|
+
}
|
|
1765
|
+
const tags = (existing.tags || []).filter(t => t !== 'pinned');
|
|
1766
|
+
memoryStore.update(memoryId, { importance: 0.7, tags });
|
|
1767
|
+
(0, memory_cache_1.invalidateCache)();
|
|
2231
1768
|
return {
|
|
2232
1769
|
jsonrpc: '2.0', id,
|
|
2233
|
-
result: { content: [{ type: 'text', text:
|
|
1770
|
+
result: { content: [{ type: 'text', text: `📌 Memory UNPINNED: "${existing.intent}"\n\nThis memory will now use normal priority ranking.` }] },
|
|
2234
1771
|
};
|
|
1772
|
+
} catch (err) {
|
|
1773
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `unpin error: ${err.message}` }], isError: true } };
|
|
2235
1774
|
}
|
|
2236
1775
|
}
|
|
2237
|
-
|
|
2238
|
-
function handleCheckImpact(id, args) {
|
|
1776
|
+
function handleUndoLast(id, args) {
|
|
2239
1777
|
try {
|
|
2240
|
-
const
|
|
2241
|
-
|
|
1778
|
+
const count = Math.min(Math.max(args.count || 1, 1), 10);
|
|
1779
|
+
// Get the N most recently CREATED active memories (order by created_at, not timestamp)
|
|
1780
|
+
const db = memoryStore.db;
|
|
1781
|
+
const recentRows = db.prepare('SELECT * FROM memory_units WHERE is_active = 1 ORDER BY created_at DESC LIMIT ?').all(count);
|
|
1782
|
+
const recent = recentRows.map(r => memoryStore.get(r.id)).filter(Boolean);
|
|
1783
|
+
if (recent.length === 0) {
|
|
2242
1784
|
return {
|
|
2243
1785
|
jsonrpc: '2.0', id,
|
|
2244
|
-
result: { content: [{ type: 'text', text: '
|
|
1786
|
+
result: { content: [{ type: 'text', text: 'No memories to undo.' }] },
|
|
2245
1787
|
};
|
|
2246
1788
|
}
|
|
2247
|
-
const
|
|
2248
|
-
const
|
|
2249
|
-
const
|
|
1789
|
+
const toUndo = recent;
|
|
1790
|
+
const undone = [];
|
|
1791
|
+
for (const m of toUndo) {
|
|
1792
|
+
memoryStore.deactivate(m.id);
|
|
1793
|
+
undone.push(`- [${m.type}] "${m.intent.slice(0, 80)}"`);
|
|
1794
|
+
}
|
|
1795
|
+
(0, memory_cache_1.invalidateCache)();
|
|
2250
1796
|
return {
|
|
2251
1797
|
jsonrpc: '2.0', id,
|
|
2252
|
-
result: { content: [{ type: 'text', text }] },
|
|
1798
|
+
result: { content: [{ type: 'text', text: `⏪ Undone ${undone.length} memory(ies):\n\n${undone.join('\n')}` }] },
|
|
2253
1799
|
};
|
|
1800
|
+
} catch (err) {
|
|
1801
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `undo error: ${err.message}` }], isError: true } };
|
|
2254
1802
|
}
|
|
2255
|
-
|
|
1803
|
+
}
|
|
1804
|
+
function handleClearMemories(id, args) {
|
|
1805
|
+
try {
|
|
1806
|
+
const type = args.type;
|
|
1807
|
+
const olderThanDays = args.olderThanDays;
|
|
1808
|
+
if (!type) {
|
|
1809
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: type is required' }], isError: true } };
|
|
1810
|
+
}
|
|
1811
|
+
let query = 'SELECT * FROM memory_units WHERE is_active = 1';
|
|
1812
|
+
const params = [];
|
|
1813
|
+
if (type !== 'ALL') {
|
|
1814
|
+
query += ' AND type = ?';
|
|
1815
|
+
params.push(type);
|
|
1816
|
+
}
|
|
1817
|
+
if (olderThanDays) {
|
|
1818
|
+
const cutoff = Date.now() - (olderThanDays * 86400000);
|
|
1819
|
+
query += ' AND created_at < ?';
|
|
1820
|
+
params.push(cutoff);
|
|
1821
|
+
}
|
|
1822
|
+
const rows = memoryStore.db ? memoryStore.db.prepare(query).all.apply(memoryStore.db.prepare(query), params) : [];
|
|
1823
|
+
let cleared = 0;
|
|
1824
|
+
for (const row of rows) {
|
|
1825
|
+
memoryStore.deactivate(row.id);
|
|
1826
|
+
cleared++;
|
|
1827
|
+
}
|
|
1828
|
+
(0, memory_cache_1.invalidateCache)();
|
|
1829
|
+
const ageMsg = olderThanDays ? ` older than ${olderThanDays} days` : '';
|
|
2256
1830
|
return {
|
|
2257
1831
|
jsonrpc: '2.0', id,
|
|
2258
|
-
result: { content: [{ type: 'text', text:
|
|
1832
|
+
result: { content: [{ type: 'text', text: `🗑️ Cleared ${cleared} ${type === 'ALL' ? '' : type + ' '}memories${ageMsg}.` }] },
|
|
2259
1833
|
};
|
|
1834
|
+
} catch (err) {
|
|
1835
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `clear error: ${err.message}` }], isError: true } };
|
|
2260
1836
|
}
|
|
2261
1837
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
1838
|
+
function parseRelativeDate(str) {
|
|
1839
|
+
if (!str) return Date.now();
|
|
1840
|
+
const lower = str.toLowerCase().trim();
|
|
1841
|
+
if (lower === 'now' || lower === 'today') return Date.now();
|
|
1842
|
+
if (lower === 'yesterday') return Date.now() - 86400000;
|
|
1843
|
+
// "N days ago", "N hours ago", "N weeks ago"
|
|
1844
|
+
const relMatch = lower.match(/^(\d+)\s+(day|hour|week|month)s?\s+ago$/);
|
|
1845
|
+
if (relMatch) {
|
|
1846
|
+
const n = parseInt(relMatch[1], 10);
|
|
1847
|
+
const unit = relMatch[2];
|
|
1848
|
+
const multipliers = { hour: 3600000, day: 86400000, week: 604800000, month: 2592000000 };
|
|
1849
|
+
return Date.now() - (n * (multipliers[unit] || 86400000));
|
|
1850
|
+
}
|
|
1851
|
+
// Try ISO date
|
|
1852
|
+
const parsed = new Date(str).getTime();
|
|
1853
|
+
return isNaN(parsed) ? Date.now() - 604800000 : parsed; // default to 7 days ago
|
|
1854
|
+
}
|
|
1855
|
+
function handleSearchTimeline(id, args) {
|
|
2264
1856
|
try {
|
|
2265
|
-
const
|
|
2266
|
-
const
|
|
1857
|
+
const fromTs = parseRelativeDate(args.from);
|
|
1858
|
+
const toTs = parseRelativeDate(args.to || 'now');
|
|
1859
|
+
const type = args.type;
|
|
1860
|
+
let query = 'SELECT * FROM memory_units WHERE is_active = 1 AND created_at >= ? AND created_at <= ?';
|
|
1861
|
+
const params = [fromTs, toTs];
|
|
1862
|
+
if (type && type !== 'ALL') {
|
|
1863
|
+
query += ' AND type = ?';
|
|
1864
|
+
params.push(type);
|
|
1865
|
+
}
|
|
1866
|
+
query += ' ORDER BY created_at DESC LIMIT 50';
|
|
1867
|
+
const db = memoryStore.db || memoryStore.connection;
|
|
1868
|
+
const stmt = db.prepare(query);
|
|
1869
|
+
const rows = stmt.all.apply(stmt, params);
|
|
1870
|
+
if (rows.length === 0) {
|
|
1871
|
+
return {
|
|
1872
|
+
jsonrpc: '2.0', id,
|
|
1873
|
+
result: { content: [{ type: 'text', text: `No memories found between ${new Date(fromTs).toLocaleDateString()} and ${new Date(toTs).toLocaleDateString()}.` }] },
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
const lines = [`# Timeline: ${new Date(fromTs).toLocaleDateString()} → ${new Date(toTs).toLocaleDateString()}\n`];
|
|
1877
|
+
let lastDate = '';
|
|
1878
|
+
for (const row of rows) {
|
|
1879
|
+
const dateStr = new Date(row.created_at).toLocaleDateString();
|
|
1880
|
+
if (dateStr !== lastDate) {
|
|
1881
|
+
lines.push(`\n## ${dateStr}`);
|
|
1882
|
+
lastDate = dateStr;
|
|
1883
|
+
}
|
|
1884
|
+
lines.push(`- **[${row.type}]** ${row.intent}`);
|
|
1885
|
+
if (row.reason) lines.push(` _${row.reason}_`);
|
|
1886
|
+
}
|
|
1887
|
+
lines.push(`\n_Total: ${rows.length} memories in this period._`);
|
|
2267
1888
|
return {
|
|
2268
1889
|
jsonrpc: '2.0', id,
|
|
2269
|
-
result: { content: [{ type: 'text', text }] },
|
|
1890
|
+
result: { content: [{ type: 'text', text: lines.join('\n') }] },
|
|
2270
1891
|
};
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `timeline error: ${err.message}` }], isError: true } };
|
|
2271
1894
|
}
|
|
2272
|
-
|
|
1895
|
+
}
|
|
1896
|
+
function handleThumbsUp(id, args) {
|
|
1897
|
+
try {
|
|
1898
|
+
const memoryId = args.id;
|
|
1899
|
+
if (!memoryId) {
|
|
1900
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
|
|
1901
|
+
}
|
|
1902
|
+
const existing = memoryStore.get(memoryId);
|
|
1903
|
+
if (!existing) {
|
|
1904
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
|
|
1905
|
+
}
|
|
1906
|
+
// Boost importance (cap at 1.0)
|
|
1907
|
+
const newImportance = Math.min(1.0, (existing.importance || 0.5) + 0.1);
|
|
1908
|
+
memoryStore.update(memoryId, { importance: newImportance });
|
|
1909
|
+
// Record in user_signals table
|
|
1910
|
+
try {
|
|
1911
|
+
const db = memoryStore.db || memoryStore.connection;
|
|
1912
|
+
db.prepare('INSERT INTO user_signals (memory_id, signal, timestamp) VALUES (?, ?, ?)')
|
|
1913
|
+
.run(memoryId, 'thumbs_up', Date.now());
|
|
1914
|
+
} catch { /* table might not exist */ }
|
|
1915
|
+
(0, memory_cache_1.invalidateCache)();
|
|
2273
1916
|
return {
|
|
2274
1917
|
jsonrpc: '2.0', id,
|
|
2275
|
-
result: { content: [{ type: 'text', text:
|
|
1918
|
+
result: { content: [{ type: 'text', text: `👍 Upvoted: "${existing.intent.slice(0, 80)}"\nImportance: ${(newImportance * 100).toFixed(0)}%` }] },
|
|
2276
1919
|
};
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `thumbs_up error: ${err.message}` }], isError: true } };
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
function handleThumbsDown(id, args) {
|
|
1925
|
+
try {
|
|
1926
|
+
const memoryId = args.id;
|
|
1927
|
+
const reason = args.reason || 'Not useful';
|
|
1928
|
+
if (!memoryId) {
|
|
1929
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
|
|
1930
|
+
}
|
|
1931
|
+
const existing = memoryStore.get(memoryId);
|
|
1932
|
+
if (!existing) {
|
|
1933
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: Memory ${memoryId} not found` }], isError: true } };
|
|
1934
|
+
}
|
|
1935
|
+
// Demote importance
|
|
1936
|
+
const newImportance = Math.max(0.0, (existing.importance || 0.5) - 0.15);
|
|
1937
|
+
memoryStore.update(memoryId, { importance: newImportance });
|
|
1938
|
+
// Record in user_signals table
|
|
1939
|
+
let downvoteCount = 0;
|
|
1940
|
+
try {
|
|
1941
|
+
const db = memoryStore.db || memoryStore.connection;
|
|
1942
|
+
db.prepare('INSERT INTO user_signals (memory_id, signal, correction, timestamp) VALUES (?, ?, ?, ?)')
|
|
1943
|
+
.run(memoryId, 'thumbs_down', reason, Date.now());
|
|
1944
|
+
// Count total downvotes for this memory
|
|
1945
|
+
const row = db.prepare('SELECT COUNT(*) as cnt FROM user_signals WHERE memory_id = ? AND signal = ?')
|
|
1946
|
+
.get(memoryId, 'thumbs_down');
|
|
1947
|
+
downvoteCount = row ? row.cnt : 0;
|
|
1948
|
+
} catch { /* table might not exist */ }
|
|
1949
|
+
// Auto-deactivate if 3+ downvotes
|
|
1950
|
+
let deactivated = false;
|
|
1951
|
+
if (downvoteCount >= 3) {
|
|
1952
|
+
memoryStore.deactivate(memoryId);
|
|
1953
|
+
deactivated = true;
|
|
1954
|
+
}
|
|
1955
|
+
(0, memory_cache_1.invalidateCache)();
|
|
1956
|
+
let msg = `👎 Downvoted: "${existing.intent.slice(0, 80)}"\nImportance: ${(newImportance * 100).toFixed(0)}%\nReason: ${reason}`;
|
|
1957
|
+
if (deactivated) {
|
|
1958
|
+
msg += `\n\n⚠️ Memory auto-deactivated (received ${downvoteCount} downvotes).`;
|
|
1959
|
+
}
|
|
1960
|
+
return {
|
|
1961
|
+
jsonrpc: '2.0', id,
|
|
1962
|
+
result: { content: [{ type: 'text', text: msg }] },
|
|
1963
|
+
};
|
|
1964
|
+
} catch (err) {
|
|
1965
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `thumbs_down error: ${err.message}` }], isError: true } };
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
function handleBackupBrain(id) {
|
|
1969
|
+
try {
|
|
1970
|
+
const fs = require('fs');
|
|
1971
|
+
const path = require('path');
|
|
1972
|
+
const dbPath = memoryStore.db?.name || memoryStore._dbPath;
|
|
1973
|
+
if (!dbPath || !fs.existsSync(dbPath)) {
|
|
1974
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: Cannot locate database file.' }], isError: true } };
|
|
1975
|
+
}
|
|
1976
|
+
const backupDate = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1977
|
+
const backupPath = path.join(path.dirname(dbPath), `cognitive-backup-${backupDate}.db`);
|
|
1978
|
+
// Close active WAL before backup
|
|
1979
|
+
memoryStore.checkpoint?.();
|
|
1980
|
+
fs.copyFileSync(dbPath, backupPath);
|
|
1981
|
+
return {
|
|
1982
|
+
jsonrpc: '2.0', id,
|
|
1983
|
+
result: { content: [{ type: 'text', text: `✅ Database backed up successfully!\n\nSaved to: ${backupPath}\nSize: ${(fs.statSync(backupPath).size / 1024 / 1024).toFixed(2)} MB` }] },
|
|
1984
|
+
};
|
|
1985
|
+
} catch (err) {
|
|
1986
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `backup error: ${err.message}` }], isError: true } };
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
function handleGetDailySummary(id, args) {
|
|
1990
|
+
try {
|
|
1991
|
+
const dateStr = args.date || new Date().toISOString().split('T')[0];
|
|
1992
|
+
const db = memoryStore.db || memoryStore.connection;
|
|
1993
|
+
// First check if it exists
|
|
1994
|
+
const row = db.prepare('SELECT summary FROM daily_summaries WHERE date = ?').get(dateStr);
|
|
1995
|
+
if (row) {
|
|
1996
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `# Summary for ${dateStr}\n\n${row.summary}` }] } };
|
|
1997
|
+
}
|
|
1998
|
+
// If checking today and no summary exists, let's create a dynamic one
|
|
1999
|
+
const isToday = dateStr === new Date().toISOString().split('T')[0];
|
|
2000
|
+
if (!isToday) {
|
|
2001
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `No summary found for ${dateStr}.` }] } };
|
|
2002
|
+
}
|
|
2003
|
+
// Generate dynamic summary for today
|
|
2004
|
+
const startOfDay = new Date(dateStr).setHours(0,0,0,0);
|
|
2005
|
+
const todayMemories = db.prepare('SELECT * FROM memory_units WHERE is_active = 1 AND created_at >= ? ORDER BY created_at ASC').all(startOfDay);
|
|
2006
|
+
if (todayMemories.length === 0) {
|
|
2007
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `No memories recorded yet today (${dateStr}).` }] } };
|
|
2008
|
+
}
|
|
2009
|
+
const lines = [];
|
|
2010
|
+
let types = { DECISION: 0, BUG_FIX: 0, CORRECTION: 0, CONVENTION: 0 };
|
|
2011
|
+
for (const m of todayMemories) {
|
|
2012
|
+
if (types[m.type] !== undefined) types[m.type]++;
|
|
2013
|
+
lines.push(`- **[${m.type}]** ${m.intent}`);
|
|
2014
|
+
}
|
|
2015
|
+
const summaryText = `Today we recorded ${todayMemories.length} memories (${types.DECISION} decisions, ${types.BUG_FIX} fixes, ${types.CORRECTION} corrections, ${types.CONVENTION} conventions).\n\nKey updates:\n${lines.join('\n')}`;
|
|
2016
|
+
// Save it
|
|
2017
|
+
db.prepare('INSERT OR REPLACE INTO daily_summaries (date, summary, created_at) VALUES (?, ?, ?)')
|
|
2018
|
+
.run(dateStr, summaryText, Date.now());
|
|
2019
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `# Summary for Today (${dateStr})\n\n${summaryText}` }] } };
|
|
2020
|
+
} catch (err) {
|
|
2021
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `summary error: ${err.message}` }], isError: true } };
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
function handleAnalytics(id) {
|
|
2025
|
+
try {
|
|
2026
|
+
const db = memoryStore.db || memoryStore.connection;
|
|
2027
|
+
const stats = [];
|
|
2028
|
+
// Basic counts
|
|
2029
|
+
const total = db.prepare('SELECT COUNT(*) as c FROM memory_units').get().c;
|
|
2030
|
+
const active = db.prepare('SELECT COUNT(*) as c FROM memory_units WHERE is_active = 1').get().c;
|
|
2031
|
+
const pinned = db.prepare("SELECT COUNT(*) as c FROM memory_units WHERE is_active = 1 AND tags LIKE '%pinned%'").get().c;
|
|
2032
|
+
stats.push(`## 📊 Brain Health\n- Total Memories: ${total}\n- Active Memories: ${active}\n- Pinned Rules: ${pinned}`);
|
|
2033
|
+
// By Type
|
|
2034
|
+
stats.push('\n## 🧩 Distribution by Type');
|
|
2035
|
+
const types = db.prepare('SELECT type, COUNT(*) as c FROM memory_units WHERE is_active = 1 GROUP BY type ORDER BY c DESC').all();
|
|
2036
|
+
for (const t of types) stats.push(`- **${t.type}**: ${t.c}`);
|
|
2037
|
+
// Most Accessed
|
|
2038
|
+
stats.push('\n## 🔥 Most Accessed Memories (Top 5)');
|
|
2039
|
+
const hot = db.prepare('SELECT id, type, intent, access_count FROM memory_units WHERE is_active = 1 AND access_count > 0 ORDER BY access_count DESC LIMIT 5').all();
|
|
2040
|
+
if (hot.length) {
|
|
2041
|
+
for (const h of hot) stats.push(`- [${h.access_count}x] **${h.type}**: ${h.intent} (id: ${h.id})`);
|
|
2042
|
+
} else {
|
|
2043
|
+
stats.push('- No memory accesses recorded yet.');
|
|
2044
|
+
}
|
|
2045
|
+
// Feedback stats
|
|
2046
|
+
try {
|
|
2047
|
+
const up = db.prepare("SELECT COUNT(*) as c FROM user_signals WHERE signal = 'thumbs_up'").get().c;
|
|
2048
|
+
const down = db.prepare("SELECT COUNT(*) as c FROM user_signals WHERE signal = 'thumbs_down'").get().c;
|
|
2049
|
+
if (up > 0 || down > 0) {
|
|
2050
|
+
stats.push(`\n## 👍 User Feedback\n- Upvotes: ${up}\n- Downvotes: ${down}`);
|
|
2051
|
+
}
|
|
2052
|
+
} catch { /* ignore if table missing */ }
|
|
2053
|
+
return {
|
|
2054
|
+
jsonrpc: '2.0', id,
|
|
2055
|
+
result: { content: [{ type: 'text', text: stats.join('\n') }] },
|
|
2056
|
+
};
|
|
2057
|
+
} catch (err) {
|
|
2058
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `analytics error: ${err.message}` }], isError: true } };
|
|
2277
2059
|
}
|
|
2278
2060
|
}
|
|
2279
2061
|
return { handleMCPRequest };
|
|
2280
2062
|
}
|
|
2063
|
+
// ─── NEW: Export/Import/Graph Handlers ─────────────────────────────────────
|
|
2064
|
+
async function handleExportMemoriesFn(memoryStore, id, args) {
|
|
2065
|
+
try {
|
|
2066
|
+
const { filePath } = args;
|
|
2067
|
+
if (!filePath) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true } };
|
|
2068
|
+
const result = (0, export_import_1.exportToFile)(memoryStore, filePath);
|
|
2069
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: '✅ Exported ' + result.count + ' memories to:\n' + result.path + '\n\nShare this file with teammates or use import_memories to restore.' }] } };
|
|
2070
|
+
} catch (err) {
|
|
2071
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'export error: ' + err.message }], isError: true } };
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
async function handleImportMemoriesFn(memoryStore, memory_cache_1, id, args) {
|
|
2075
|
+
try {
|
|
2076
|
+
const { filePath } = args;
|
|
2077
|
+
if (!filePath) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true } };
|
|
2078
|
+
const result = (0, export_import_1.importFromFile)(memoryStore, filePath);
|
|
2079
|
+
if (result.imported > 0) (0, memory_cache_1.invalidateCache)();
|
|
2080
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: '✅ Import complete!\n\n- Imported: ' + result.imported + ' memories\n- Skipped (duplicates): ' + result.skipped + '\n- Errors: ' + result.errors }] } };
|
|
2081
|
+
} catch (err) {
|
|
2082
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'import error: ' + err.message }], isError: true } };
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
async function handleGetRelatedMemoriesFn(memoryStore, id, args) {
|
|
2086
|
+
try {
|
|
2087
|
+
const memoryId = args.id;
|
|
2088
|
+
const maxHops = args.maxHops || 2;
|
|
2089
|
+
if (!memoryId) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: id is required' }], isError: true } };
|
|
2090
|
+
const hops = Math.min(4, Math.max(1, maxHops));
|
|
2091
|
+
const starting = memoryStore.get(memoryId);
|
|
2092
|
+
if (!starting) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'No memory found with id: ' + memoryId }] } };
|
|
2093
|
+
const related = memoryStore.getRelated(memoryId, hops, 20);
|
|
2094
|
+
if (related.length === 0) {
|
|
2095
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Memory: [' + starting.type + '] ' + starting.intent + '\n\nNo related memories found yet (no graph edges). Use store_memory to build connections.' }] } };
|
|
2096
|
+
}
|
|
2097
|
+
const lines = ['## Starting Memory', '[' + starting.type + '] ' + starting.intent + ' (id: ' + starting.id + ')', '', '## Related Memories (via graph)'];
|
|
2098
|
+
for (const r of related) {
|
|
2099
|
+
lines.push('- [' + r.memory.type + '] ' + r.memory.intent + ' (id: ' + r.memory.id + ') (' + (r.score * 100).toFixed(0) + '% relevance)');
|
|
2100
|
+
}
|
|
2101
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: lines.join('\n') }] } };
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'get_related error: ' + err.message }], isError: true } };
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2281
2106
|
//# sourceMappingURL=mcp-handler.js.map
|