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