@mrxkun/mcfast-mcp 4.0.14 → 4.1.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/package.json +2 -2
- package/src/memory/bootstrap/agents-md.js +173 -0
- package/src/memory/index.js +26 -13
- package/src/memory/layers/curated-memory.js +324 -0
- package/src/memory/layers/daily-logs.js +236 -0
- package/src/memory/memory-engine.js +472 -452
- package/src/memory/stores/codebase-database.js +418 -0
- package/src/memory/stores/memory-database.js +425 -0
- package/src/memory/utils/markdown-chunker.js +242 -0
- package/src/memory/watchers/file-watcher.js +286 -20
- package/src/tools/memory_get.js +139 -100
- package/src/tools/memory_search.js +118 -86
|
@@ -1,35 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* memory_search Tool
|
|
3
|
-
* Hybrid search
|
|
4
|
-
*
|
|
2
|
+
* memory_search Tool v3.0
|
|
3
|
+
* Hybrid search using MemoryEngine v4.1.0
|
|
4
|
+
* Supports: Vector 70% + BM25 30% hybrid search
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { MemoryEngine } from '../memory/index.js';
|
|
8
8
|
|
|
9
9
|
// Memory engine instance (initialized lazily)
|
|
10
10
|
let memoryEngine = null;
|
|
11
|
+
let enginePromise = null;
|
|
11
12
|
|
|
12
13
|
async function getMemoryEngine() {
|
|
13
|
-
if (
|
|
14
|
+
if (memoryEngine) return memoryEngine;
|
|
15
|
+
if (enginePromise) return enginePromise;
|
|
16
|
+
|
|
17
|
+
enginePromise = (async () => {
|
|
14
18
|
memoryEngine = new MemoryEngine({
|
|
15
19
|
apiKey: process.env.MCFAST_TOKEN,
|
|
16
20
|
enableSync: true
|
|
17
21
|
});
|
|
18
22
|
await memoryEngine.initialize(process.cwd());
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
return memoryEngine;
|
|
24
|
+
})();
|
|
25
|
+
|
|
26
|
+
return enginePromise;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
/**
|
|
24
|
-
* Execute memory search using
|
|
30
|
+
* Execute memory search using hybrid search (Vector 70% + BM25 30%)
|
|
25
31
|
* @param {Object} args - Tool arguments
|
|
26
32
|
* @param {string} args.query - Search query
|
|
27
|
-
* @param {string} [args.type='all'] - Filter by type: 'all', '
|
|
33
|
+
* @param {string} [args.type='all'] - Filter by type: 'all', 'memory', 'codebase', 'facts'
|
|
28
34
|
* @param {number} [args.maxResults=6] - Maximum results to return
|
|
29
35
|
* @param {number} [args.minScore=0.35] - Minimum score threshold
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {string} [args.context.currentFile] - Current file being edited
|
|
32
|
-
* @param {number} [args.context.currentLine] - Current line number
|
|
36
|
+
* @param {boolean} [args.useHybrid=true] - Use hybrid search (Vector + BM25)
|
|
33
37
|
* @returns {Promise<Object>} Search results
|
|
34
38
|
*/
|
|
35
39
|
export async function execute(args) {
|
|
@@ -38,74 +42,72 @@ export async function execute(args) {
|
|
|
38
42
|
type = 'all',
|
|
39
43
|
maxResults = 6,
|
|
40
44
|
minScore = 0.35,
|
|
41
|
-
|
|
45
|
+
useHybrid = true
|
|
42
46
|
} = args;
|
|
43
47
|
|
|
48
|
+
if (!query || query.trim().length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: "❌ Error: 'query' parameter is required"
|
|
53
|
+
}],
|
|
54
|
+
isError: true
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
const startTime = Date.now();
|
|
45
59
|
|
|
46
60
|
try {
|
|
47
61
|
const engine = await getMemoryEngine();
|
|
48
62
|
let results = [];
|
|
63
|
+
let searchMetadata = {};
|
|
64
|
+
|
|
65
|
+
if (type === 'memory' || type === 'all') {
|
|
66
|
+
// Search memory notes (daily logs + curated)
|
|
67
|
+
const memoryResult = await engine.searchMemory(query, {
|
|
68
|
+
limit: maxResults * 2,
|
|
69
|
+
useHybrid,
|
|
70
|
+
minScore
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
results.push(...memoryResult.results.map(r => ({
|
|
74
|
+
type: 'memory',
|
|
75
|
+
content: r.content,
|
|
76
|
+
file: r.file_path || r.filePath,
|
|
77
|
+
lines: [r.start_line || r.startLine, r.end_line || r.endLine],
|
|
78
|
+
score: r.finalScore || r.score || r.similarity,
|
|
79
|
+
vectorScore: r.vectorScore,
|
|
80
|
+
textScore: r.textScore,
|
|
81
|
+
source: r.sources?.join('+') || 'vector'
|
|
82
|
+
})));
|
|
83
|
+
|
|
84
|
+
searchMetadata.memory = memoryResult.metadata;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (type === 'codebase' || type === 'all') {
|
|
88
|
+
// Search codebase
|
|
89
|
+
const codebaseResult = await engine.searchCodebase(query, maxResults * 2);
|
|
90
|
+
|
|
91
|
+
results.push(...codebaseResult.results.map(r => ({
|
|
92
|
+
type: 'code',
|
|
93
|
+
content: r.content || r.code,
|
|
94
|
+
file: r.file_path || r.filePath || r.path,
|
|
95
|
+
lines: [r.start_line || r.startLine, r.end_line || r.endLine],
|
|
96
|
+
score: r.similarity || r.score,
|
|
97
|
+
source: r.source || 'vector'
|
|
98
|
+
})));
|
|
99
|
+
|
|
100
|
+
searchMetadata.codebase = codebaseResult.metadata;
|
|
101
|
+
}
|
|
49
102
|
|
|
50
103
|
if (type === 'facts') {
|
|
51
104
|
// Search facts only
|
|
52
105
|
const facts = await engine.searchFacts(query, maxResults);
|
|
53
|
-
results = facts.map(fact => ({
|
|
54
|
-
type: 'fact',
|
|
55
|
-
content: `${fact.type}: ${fact.name}${fact.signature ? ` (${fact.signature})` : ''}`,
|
|
56
|
-
file: fact.file_id,
|
|
57
|
-
score: fact.confidence || 0.8,
|
|
58
|
-
source: 'facts'
|
|
59
|
-
}));
|
|
60
|
-
} else if (type === 'history') {
|
|
61
|
-
// Search edit history via database
|
|
62
|
-
const history = engine.db.getRecentEdits(maxResults * 2);
|
|
63
|
-
const filtered = history.filter(edit =>
|
|
64
|
-
edit.instruction.toLowerCase().includes(query.toLowerCase()) ||
|
|
65
|
-
(edit.files && edit.files.some(f => f.toLowerCase().includes(query.toLowerCase())))
|
|
66
|
-
);
|
|
67
|
-
results = filtered.slice(0, maxResults).map(edit => ({
|
|
68
|
-
type: 'history',
|
|
69
|
-
content: edit.instruction,
|
|
70
|
-
files: edit.files,
|
|
71
|
-
strategy: edit.strategy,
|
|
72
|
-
success: edit.success,
|
|
73
|
-
timestamp: edit.timestamp,
|
|
74
|
-
score: edit.success ? 0.8 : 0.5,
|
|
75
|
-
source: 'history'
|
|
76
|
-
}));
|
|
77
|
-
} else if (type === 'code') {
|
|
78
|
-
// Use ultra search for code
|
|
79
|
-
const searchResult = await engine.ultraSearch(query, { limit: maxResults });
|
|
80
|
-
results = searchResult.results.map(result => ({
|
|
81
|
-
type: 'chunk',
|
|
82
|
-
content: result.code || result.content,
|
|
83
|
-
file: result.filePath || result.path,
|
|
84
|
-
lines: [result.startLine, result.endLine],
|
|
85
|
-
score: result.finalScore || result.score,
|
|
86
|
-
source: 'ultra-hybrid'
|
|
87
|
-
}));
|
|
88
|
-
} else {
|
|
89
|
-
// Use intelligent search (all types)
|
|
90
|
-
const searchResult = await engine.intelligentSearch(query, {
|
|
91
|
-
limit: maxResults,
|
|
92
|
-
currentFile: contextInfo.currentFile
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
results = searchResult.results.map(result => ({
|
|
96
|
-
type: 'chunk',
|
|
97
|
-
content: result.code || result.content,
|
|
98
|
-
file: result.filePath || result.path,
|
|
99
|
-
score: result.finalScore || result.score,
|
|
100
|
-
source: searchResult.metadata?.method || 'intelligent'
|
|
101
|
-
}));
|
|
102
|
-
|
|
103
|
-
// Add facts if available
|
|
104
|
-
const facts = await engine.searchFacts(query, Math.floor(maxResults / 2));
|
|
105
106
|
results.push(...facts.map(fact => ({
|
|
106
107
|
type: 'fact',
|
|
107
|
-
content: `${fact.type}: ${fact.name}`,
|
|
108
|
-
file: fact.file_id,
|
|
108
|
+
content: `${fact.type}: ${fact.name}${fact.signature ? ` (${fact.signature})` : ''}`,
|
|
109
|
+
file: fact.file_path || fact.file_id,
|
|
110
|
+
lines: [fact.line_start, fact.line_end],
|
|
109
111
|
score: fact.confidence || 0.8,
|
|
110
112
|
source: 'facts'
|
|
111
113
|
})));
|
|
@@ -118,7 +120,7 @@ export async function execute(args) {
|
|
|
118
120
|
.slice(0, maxResults);
|
|
119
121
|
|
|
120
122
|
// Format output
|
|
121
|
-
const formattedResults = formatResults(results,
|
|
123
|
+
const formattedResults = formatResults(results, query);
|
|
122
124
|
const latency = Date.now() - startTime;
|
|
123
125
|
|
|
124
126
|
return {
|
|
@@ -131,6 +133,8 @@ export async function execute(args) {
|
|
|
131
133
|
type,
|
|
132
134
|
resultCount: results.length,
|
|
133
135
|
latency,
|
|
136
|
+
minScore,
|
|
137
|
+
searchMetadata,
|
|
134
138
|
sources: [...new Set(results.map(r => r.source || 'unknown'))]
|
|
135
139
|
}
|
|
136
140
|
};
|
|
@@ -149,13 +153,15 @@ export async function execute(args) {
|
|
|
149
153
|
/**
|
|
150
154
|
* Format results for display
|
|
151
155
|
*/
|
|
152
|
-
function formatResults(results,
|
|
156
|
+
function formatResults(results, query) {
|
|
153
157
|
if (!results || results.length === 0) {
|
|
154
|
-
return `🔍 Memory Search: No results found for query
|
|
158
|
+
return `🔍 Memory Search: No results found for "${query}"\n\nTip: Try a different search term or check if memory is indexed.`;
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
let output = `🔍 Memory Search Results\n`;
|
|
158
|
-
output += `
|
|
162
|
+
output += `Query: "${query}"\n`;
|
|
163
|
+
output += `Found: ${results.length} result(s)\n`;
|
|
164
|
+
output += '─'.repeat(50) + '\n\n';
|
|
159
165
|
|
|
160
166
|
// Group by type
|
|
161
167
|
const byType = {};
|
|
@@ -164,13 +170,21 @@ function formatResults(results, type) {
|
|
|
164
170
|
byType[result.type].push(result);
|
|
165
171
|
}
|
|
166
172
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
// Display by type
|
|
174
|
+
const typeOrder = ['memory', 'code', 'fact'];
|
|
175
|
+
for (const resultType of typeOrder) {
|
|
176
|
+
if (!byType[resultType]) continue;
|
|
177
|
+
|
|
178
|
+
const items = byType[resultType];
|
|
179
|
+
output += `${getTypeIcon(resultType)} ${getTypeLabel(resultType)} (${items.length})\n`;
|
|
180
|
+
output += '─'.repeat(50) + '\n';
|
|
181
|
+
|
|
182
|
+
for (let i = 0; i < items.length; i++) {
|
|
183
|
+
const item = items[i];
|
|
184
|
+
output += `[${i + 1}] ${formatItem(item)}\n\n`;
|
|
173
185
|
}
|
|
186
|
+
|
|
187
|
+
output += '\n';
|
|
174
188
|
}
|
|
175
189
|
|
|
176
190
|
return output;
|
|
@@ -178,18 +192,28 @@ function formatResults(results, type) {
|
|
|
178
192
|
|
|
179
193
|
function getTypeIcon(type) {
|
|
180
194
|
const icons = {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
195
|
+
memory: '📝',
|
|
196
|
+
code: '💻',
|
|
197
|
+
fact: '📌'
|
|
184
198
|
};
|
|
185
199
|
return icons[type] || '•';
|
|
186
200
|
}
|
|
187
201
|
|
|
202
|
+
function getTypeLabel(type) {
|
|
203
|
+
const labels = {
|
|
204
|
+
memory: 'Memory Notes',
|
|
205
|
+
code: 'Code',
|
|
206
|
+
fact: 'Facts'
|
|
207
|
+
};
|
|
208
|
+
return labels[type] || type;
|
|
209
|
+
}
|
|
210
|
+
|
|
188
211
|
function formatItem(item) {
|
|
189
212
|
let text = '';
|
|
190
213
|
|
|
214
|
+
// File path with line numbers
|
|
191
215
|
if (item.file) {
|
|
192
|
-
text +=
|
|
216
|
+
text += `📄 ${item.file}`;
|
|
193
217
|
if (item.lines && item.lines[0]) {
|
|
194
218
|
text += `:${item.lines[0]}`;
|
|
195
219
|
if (item.lines[1] && item.lines[1] !== item.lines[0]) {
|
|
@@ -199,12 +223,20 @@ function formatItem(item) {
|
|
|
199
223
|
text += '\n';
|
|
200
224
|
}
|
|
201
225
|
|
|
202
|
-
//
|
|
203
|
-
const content = item.content?.substring(0,
|
|
204
|
-
text += `
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
226
|
+
// Content preview (truncate)
|
|
227
|
+
const content = item.content?.substring(0, 200)?.replace(/\n/g, ' ') || '';
|
|
228
|
+
text += ` ${content}${content.length >= 200 ? '...' : ''}\n`;
|
|
229
|
+
|
|
230
|
+
// Score with breakdown
|
|
231
|
+
const scorePct = (item.score * 100).toFixed(1);
|
|
232
|
+
text += ` Score: ${scorePct}%`;
|
|
233
|
+
|
|
234
|
+
if (item.vectorScore !== undefined && item.textScore !== undefined) {
|
|
235
|
+
const vPct = (item.vectorScore * 100).toFixed(0);
|
|
236
|
+
const tPct = (item.textScore * 100).toFixed(0);
|
|
237
|
+
text += ` (vector: ${vPct}%, text: ${tPct}%)`;
|
|
238
|
+
}
|
|
239
|
+
|
|
208
240
|
if (item.source) {
|
|
209
241
|
text += ` • ${item.source}`;
|
|
210
242
|
}
|