@mrxkun/mcfast-mcp 4.1.10 → 4.1.12
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/index.js +297 -21
- package/src/memory/memory-engine.js +232 -25
- package/src/memory/stores/base-database.js +223 -0
- package/src/memory/utils/chunker.js +1 -0
- package/src/memory/utils/indexer.js +110 -4
- package/src/memory/utils/logger.js +162 -0
- package/src/memory/utils/vector-index.js +241 -0
- package/src/memory/watchers/file-watcher.js +257 -103
- package/src/tools/project_analyze.js +491 -0
- package/src/utils/audit-queue.js +1 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* project_analyze Tool
|
|
3
|
+
* Analyzes project structure and generates comprehensive overview
|
|
4
|
+
* Calls mcfast API to use Mercury for AI-powered analysis
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { MemoryEngine } from '../memory/index.js';
|
|
10
|
+
|
|
11
|
+
const API_URL = process.env.MCFAST_API_URL || 'https://mcfast.vercel.app/api/v1';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Execute project analysis
|
|
15
|
+
* @param {Object} args - Tool arguments
|
|
16
|
+
* @param {boolean} [args.force=false] - Force re-analysis
|
|
17
|
+
* @param {boolean} [args.updateMemory=true] - Update MEMORY.md with results
|
|
18
|
+
* @returns {Promise<Object>} Analysis results
|
|
19
|
+
*/
|
|
20
|
+
export async function execute(args) {
|
|
21
|
+
const {
|
|
22
|
+
force = false,
|
|
23
|
+
updateMemory = true
|
|
24
|
+
} = args;
|
|
25
|
+
|
|
26
|
+
const startTime = Date.now();
|
|
27
|
+
const projectPath = process.cwd();
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Check for API key
|
|
31
|
+
const apiKey = process.env.MCFAST_TOKEN;
|
|
32
|
+
if (!apiKey) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: 'text',
|
|
36
|
+
text: `❌ MCFAST_TOKEN not configured
|
|
37
|
+
|
|
38
|
+
To use project analysis, set MCFAST_TOKEN:
|
|
39
|
+
1. Get your token from https://mcfast.vercel.app
|
|
40
|
+
2. Set environment variable: MCFAST_TOKEN=your_token
|
|
41
|
+
|
|
42
|
+
Alternatively, use local-only mode with basic detection.`
|
|
43
|
+
}],
|
|
44
|
+
isError: true
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.error('[project_analyze] Starting analysis...');
|
|
49
|
+
console.error('[project_analyze] Project path:', projectPath);
|
|
50
|
+
|
|
51
|
+
// Gather project information
|
|
52
|
+
const projectInfo = await gatherProjectInfo(projectPath);
|
|
53
|
+
|
|
54
|
+
// Call mcfast API for analysis
|
|
55
|
+
const analysisResult = await callProjectAnalyzeAPI(projectInfo, apiKey, force);
|
|
56
|
+
|
|
57
|
+
// Update MEMORY.md if requested
|
|
58
|
+
let memoryUpdated = false;
|
|
59
|
+
if (updateMemory && analysisResult.success) {
|
|
60
|
+
memoryUpdated = await updateMemoryWithAnalysis(projectPath, analysisResult.analysis);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const latencyMs = Date.now() - startTime;
|
|
64
|
+
|
|
65
|
+
// Format output
|
|
66
|
+
const output = formatAnalysisResult(analysisResult.analysis, latencyMs, memoryUpdated);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
content: [{
|
|
70
|
+
type: 'text',
|
|
71
|
+
text: output
|
|
72
|
+
}],
|
|
73
|
+
metadata: {
|
|
74
|
+
analysis: analysisResult.analysis,
|
|
75
|
+
cached: analysisResult.cached,
|
|
76
|
+
latencyMs,
|
|
77
|
+
memoryUpdated,
|
|
78
|
+
projectPath
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('[project_analyze] Error:', error);
|
|
84
|
+
return {
|
|
85
|
+
content: [{
|
|
86
|
+
type: 'text',
|
|
87
|
+
text: `❌ Project analysis failed: ${error.message}`
|
|
88
|
+
}],
|
|
89
|
+
isError: true
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Gather project information for analysis
|
|
96
|
+
*/
|
|
97
|
+
async function gatherProjectInfo(projectPath) {
|
|
98
|
+
console.error('[project_analyze] Gathering project info...');
|
|
99
|
+
|
|
100
|
+
// Read package.json
|
|
101
|
+
let packageJson = null;
|
|
102
|
+
try {
|
|
103
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
104
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
105
|
+
packageJson = JSON.parse(content);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.warn('[project_analyze] No package.json found');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Get file structure
|
|
111
|
+
const fileStructure = await getFileStructure(projectPath);
|
|
112
|
+
|
|
113
|
+
// Read key files
|
|
114
|
+
const keyFiles = await readKeyFiles(projectPath, fileStructure);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
projectPath,
|
|
118
|
+
packageJson,
|
|
119
|
+
fileStructure,
|
|
120
|
+
keyFiles
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get file structure as tree
|
|
126
|
+
*/
|
|
127
|
+
async function getFileStructure(dir, depth = 3, prefix = '') {
|
|
128
|
+
const lines = [];
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
132
|
+
const ignored = ['node_modules', '.git', 'dist', 'build', '.next', '.mcfast', 'coverage'];
|
|
133
|
+
|
|
134
|
+
const filtered = entries
|
|
135
|
+
.filter(e => !ignored.includes(e.name) && !e.name.startsWith('.'))
|
|
136
|
+
.sort((a, b) => {
|
|
137
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
138
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
139
|
+
return a.name.localeCompare(b.name);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
143
|
+
const entry = filtered[i];
|
|
144
|
+
const isLast = i === filtered.length - 1;
|
|
145
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
146
|
+
const line = prefix + connector + entry.name + (entry.isDirectory() ? '/' : '');
|
|
147
|
+
lines.push(line);
|
|
148
|
+
|
|
149
|
+
if (entry.isDirectory() && depth > 1) {
|
|
150
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
151
|
+
const subLines = await getFileStructure(
|
|
152
|
+
path.join(dir, entry.name),
|
|
153
|
+
depth - 1,
|
|
154
|
+
newPrefix
|
|
155
|
+
);
|
|
156
|
+
lines.push(...subLines);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// Ignore errors
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return lines.join('\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Read key files for analysis
|
|
168
|
+
*/
|
|
169
|
+
async function readKeyFiles(projectPath, fileStructure) {
|
|
170
|
+
const keyFiles = {};
|
|
171
|
+
|
|
172
|
+
// Priority files to read
|
|
173
|
+
const priorityFiles = [
|
|
174
|
+
'README.md',
|
|
175
|
+
'AGENTS.md',
|
|
176
|
+
'app/page.tsx',
|
|
177
|
+
'app/layout.tsx',
|
|
178
|
+
'pages/index.tsx',
|
|
179
|
+
'pages/index.js',
|
|
180
|
+
'src/index.ts',
|
|
181
|
+
'src/index.js',
|
|
182
|
+
'src/main.ts',
|
|
183
|
+
'src/app.ts',
|
|
184
|
+
'server.ts',
|
|
185
|
+
'server.js',
|
|
186
|
+
'index.ts',
|
|
187
|
+
'index.js'
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
// Find route files
|
|
191
|
+
const routePatterns = [
|
|
192
|
+
/app\/api\/.*\/route\.ts$/,
|
|
193
|
+
/app\/api\/.*\/route\.js$/,
|
|
194
|
+
/pages\/api\/.*\.ts$/,
|
|
195
|
+
/pages\/api\/.*\.js$/,
|
|
196
|
+
/src\/routes\/.*\.ts$/,
|
|
197
|
+
/src\/routes\/.*\.js$/
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
// Parse file structure to find routes
|
|
201
|
+
const lines = fileStructure.split('\n');
|
|
202
|
+
for (const line of lines) {
|
|
203
|
+
for (const pattern of routePatterns) {
|
|
204
|
+
const match = line.match(/[\w\/\-\.]+/);
|
|
205
|
+
if (match && pattern.test(match[0])) {
|
|
206
|
+
priorityFiles.push(match[0]);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Read files
|
|
212
|
+
for (const filePath of priorityFiles) {
|
|
213
|
+
try {
|
|
214
|
+
const fullPath = path.join(projectPath, filePath);
|
|
215
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
216
|
+
keyFiles[filePath] = content.substring(0, 3000); // Limit size
|
|
217
|
+
} catch (e) {
|
|
218
|
+
// File doesn't exist, skip
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return keyFiles;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Call mcfast API for project analysis
|
|
227
|
+
*/
|
|
228
|
+
async function callProjectAnalyzeAPI(projectInfo, apiKey, force) {
|
|
229
|
+
const url = `${API_URL}/project/analyze`;
|
|
230
|
+
|
|
231
|
+
console.error('[project_analyze] Calling API:', url);
|
|
232
|
+
|
|
233
|
+
const response = await fetch(url, {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
headers: {
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
238
|
+
'X-MCP-Client': 'mcfast-mcp-v4'
|
|
239
|
+
},
|
|
240
|
+
body: JSON.stringify({
|
|
241
|
+
projectPath: projectInfo.projectPath,
|
|
242
|
+
packageJson: projectInfo.packageJson,
|
|
243
|
+
fileStructure: projectInfo.fileStructure,
|
|
244
|
+
keyFiles: projectInfo.keyFiles,
|
|
245
|
+
forceReanalyze: force
|
|
246
|
+
})
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (!response.ok) {
|
|
250
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
251
|
+
throw new Error(error.error || `API error: ${response.status}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return response.json();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Update MEMORY.md with analysis results
|
|
259
|
+
*/
|
|
260
|
+
async function updateMemoryWithAnalysis(projectPath, analysis) {
|
|
261
|
+
try {
|
|
262
|
+
const memoryPath = path.join(projectPath, '.mcfast', 'MEMORY.md');
|
|
263
|
+
|
|
264
|
+
// Read existing memory or create new
|
|
265
|
+
let content = '';
|
|
266
|
+
try {
|
|
267
|
+
content = await fs.readFile(memoryPath, 'utf-8');
|
|
268
|
+
} catch (e) {
|
|
269
|
+
content = getDefaultMemoryTemplate();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Build project context section
|
|
273
|
+
const projectContext = buildProjectContextSection(analysis);
|
|
274
|
+
|
|
275
|
+
// Update Project Context section
|
|
276
|
+
content = updateSection(content, 'Project Context', projectContext);
|
|
277
|
+
|
|
278
|
+
// Update timestamp
|
|
279
|
+
content = content.replace(
|
|
280
|
+
/\*Last updated:.*\*/,
|
|
281
|
+
`*Last updated: ${new Date().toISOString()}*`
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Ensure directory exists
|
|
285
|
+
await fs.mkdir(path.dirname(memoryPath), { recursive: true });
|
|
286
|
+
await fs.writeFile(memoryPath, content);
|
|
287
|
+
|
|
288
|
+
console.error('[project_analyze] Updated MEMORY.md');
|
|
289
|
+
return true;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error('[project_analyze] Failed to update MEMORY.md:', error.message);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Build Project Context section content
|
|
298
|
+
*/
|
|
299
|
+
function buildProjectContextSection(analysis) {
|
|
300
|
+
const lines = [];
|
|
301
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
302
|
+
|
|
303
|
+
// Project overview
|
|
304
|
+
if (analysis.projectOverview) {
|
|
305
|
+
const { name, description, purpose, type } = analysis.projectOverview;
|
|
306
|
+
lines.push(`- **${timestamp}** Project: **${name || 'Unknown'}**`);
|
|
307
|
+
if (type) lines.push(` - Type: ${type}`);
|
|
308
|
+
if (description) lines.push(` - Description: ${description}`);
|
|
309
|
+
if (purpose) lines.push(` - Purpose: ${purpose}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Technologies
|
|
313
|
+
if (analysis.technologies) {
|
|
314
|
+
const { languages, frameworks, databases } = analysis.technologies;
|
|
315
|
+
if (languages?.length) lines.push(` - Languages: ${languages.join(', ')}`);
|
|
316
|
+
if (frameworks?.length) lines.push(` - Frameworks: ${frameworks.join(', ')}`);
|
|
317
|
+
if (databases?.length) lines.push(` - Databases: ${databases.join(', ')}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// API Endpoints
|
|
321
|
+
if (analysis.apiEndpoints?.length > 0) {
|
|
322
|
+
lines.push('');
|
|
323
|
+
lines.push(`- **API Endpoints** (${analysis.apiEndpoints.length} found):`);
|
|
324
|
+
analysis.apiEndpoints.slice(0, 10).forEach(ep => {
|
|
325
|
+
lines.push(` - ${ep.method || 'GET'} ${ep.path}: ${ep.description || 'N/A'}`);
|
|
326
|
+
});
|
|
327
|
+
if (analysis.apiEndpoints.length > 10) {
|
|
328
|
+
lines.push(` - ... and ${analysis.apiEndpoints.length - 10} more`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Main features
|
|
333
|
+
if (analysis.mainFeatures?.length > 0) {
|
|
334
|
+
lines.push('');
|
|
335
|
+
lines.push(`- **Main Features**:`);
|
|
336
|
+
analysis.mainFeatures.slice(0, 5).forEach(f => {
|
|
337
|
+
lines.push(` - ${f.name}: ${f.description || 'N/A'}`);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Entry points
|
|
342
|
+
if (analysis.entryPoints?.length > 0) {
|
|
343
|
+
lines.push('');
|
|
344
|
+
lines.push(`- **Entry Points**: ${analysis.entryPoints.join(', ')}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return lines.join('\n');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Update a section in markdown content
|
|
352
|
+
*/
|
|
353
|
+
function updateSection(content, sectionName, newContent) {
|
|
354
|
+
const sectionRegex = new RegExp(`(## ${sectionName}\\n[\\s\\S]*?)(?=\\n## |$)`, 's');
|
|
355
|
+
const match = content.match(sectionRegex);
|
|
356
|
+
|
|
357
|
+
if (match) {
|
|
358
|
+
// Update existing section
|
|
359
|
+
return content.replace(
|
|
360
|
+
sectionRegex,
|
|
361
|
+
`## ${sectionName}\n\n${newContent}\n\n`
|
|
362
|
+
);
|
|
363
|
+
} else {
|
|
364
|
+
// Add new section before the footer
|
|
365
|
+
const newSection = `\n## ${sectionName}\n\n${newContent}\n\n`;
|
|
366
|
+
return content.replace(
|
|
367
|
+
/(---\n\*Last updated:.*\*)/,
|
|
368
|
+
`${newSection}$1`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get default memory template
|
|
375
|
+
*/
|
|
376
|
+
function getDefaultMemoryTemplate() {
|
|
377
|
+
return `# Long-term Memory
|
|
378
|
+
|
|
379
|
+
This file contains curated, persistent knowledge.
|
|
380
|
+
|
|
381
|
+
## User Preferences
|
|
382
|
+
|
|
383
|
+
<!-- Add user preferences here -->
|
|
384
|
+
|
|
385
|
+
## Important Decisions
|
|
386
|
+
|
|
387
|
+
<!-- Add important decisions here -->
|
|
388
|
+
|
|
389
|
+
## Key Contacts
|
|
390
|
+
|
|
391
|
+
<!-- Add key contacts here -->
|
|
392
|
+
|
|
393
|
+
## Project Context
|
|
394
|
+
|
|
395
|
+
<!-- Add project context here -->
|
|
396
|
+
|
|
397
|
+
## Lessons Learned
|
|
398
|
+
|
|
399
|
+
<!-- Add lessons learned here -->
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
*Last updated: ${new Date().toISOString()}*
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Format analysis result for display
|
|
408
|
+
*/
|
|
409
|
+
function formatAnalysisResult(analysis, latencyMs, memoryUpdated) {
|
|
410
|
+
let output = `🔍 Project Analysis Results\n`;
|
|
411
|
+
output += `─`.repeat(50) + `\n\n`;
|
|
412
|
+
|
|
413
|
+
// Project Overview
|
|
414
|
+
if (analysis.projectOverview) {
|
|
415
|
+
const { name, description, purpose, type } = analysis.projectOverview;
|
|
416
|
+
output += `📦 **Project Overview**\n`;
|
|
417
|
+
output += ` Name: ${name || 'Unknown'}\n`;
|
|
418
|
+
output += ` Type: ${type || 'Unknown'}\n`;
|
|
419
|
+
if (description) output += ` Description: ${description}\n`;
|
|
420
|
+
if (purpose) output += ` Purpose: ${purpose}\n`;
|
|
421
|
+
output += `\n`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Technologies
|
|
425
|
+
if (analysis.technologies) {
|
|
426
|
+
const { languages, frameworks, databases, dependencies } = analysis.technologies;
|
|
427
|
+
output += `🛠️ **Technologies**\n`;
|
|
428
|
+
if (languages?.length) output += ` Languages: ${languages.join(', ')}\n`;
|
|
429
|
+
if (frameworks?.length) output += ` Frameworks: ${frameworks.join(', ')}\n`;
|
|
430
|
+
if (databases?.length) output += ` Databases: ${databases.join(', ')}\n`;
|
|
431
|
+
if (dependencies?.main?.length) {
|
|
432
|
+
output += ` Main Deps: ${dependencies.main.slice(0, 10).join(', ')}\n`;
|
|
433
|
+
}
|
|
434
|
+
output += `\n`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// API Endpoints
|
|
438
|
+
if (analysis.apiEndpoints?.length > 0) {
|
|
439
|
+
output += `🔌 **API Endpoints** (${analysis.apiEndpoints.length} found)\n`;
|
|
440
|
+
analysis.apiEndpoints.slice(0, 15).forEach(ep => {
|
|
441
|
+
const method = (ep.method || 'GET').padEnd(6);
|
|
442
|
+
output += ` ${method} ${ep.path}\n`;
|
|
443
|
+
if (ep.description) {
|
|
444
|
+
output += ` → ${ep.description}\n`;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
if (analysis.apiEndpoints.length > 15) {
|
|
448
|
+
output += ` ... and ${analysis.apiEndpoints.length - 15} more\n`;
|
|
449
|
+
}
|
|
450
|
+
output += `\n`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Main Features
|
|
454
|
+
if (analysis.mainFeatures?.length > 0) {
|
|
455
|
+
output += `✨ **Main Features**\n`;
|
|
456
|
+
analysis.mainFeatures.forEach((f, i) => {
|
|
457
|
+
output += ` ${i + 1}. ${f.name}`;
|
|
458
|
+
if (f.description) output += ` - ${f.description}`;
|
|
459
|
+
output += `\n`;
|
|
460
|
+
});
|
|
461
|
+
output += `\n`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Entry Points
|
|
465
|
+
if (analysis.entryPoints?.length > 0) {
|
|
466
|
+
output += `🚀 **Entry Points**: ${analysis.entryPoints.join(', ')}\n\n`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Recommendations
|
|
470
|
+
if (analysis.recommendations?.length > 0) {
|
|
471
|
+
output += `💡 **Recommendations**\n`;
|
|
472
|
+
analysis.recommendations.forEach(r => {
|
|
473
|
+
output += ` • ${r}\n`;
|
|
474
|
+
});
|
|
475
|
+
output += `\n`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Footer
|
|
479
|
+
output += `─`.repeat(50) + `\n`;
|
|
480
|
+
output += `⏱️ Analysis time: ${latencyMs}ms\n`;
|
|
481
|
+
if (memoryUpdated) {
|
|
482
|
+
output += `📝 MEMORY.md updated with project context\n`;
|
|
483
|
+
}
|
|
484
|
+
if (analysis.isFallback) {
|
|
485
|
+
output += `⚠️ Analysis was generated locally (Mercury unavailable)\n`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return output;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export default { execute };
|
package/src/utils/audit-queue.js
CHANGED
|
@@ -16,6 +16,7 @@ export class AuditQueue {
|
|
|
16
16
|
|
|
17
17
|
// Start flush timer
|
|
18
18
|
this.flushTimer = setInterval(() => this.flush(), this.flushInterval);
|
|
19
|
+
this.flushTimer.unref(); // Prevent timer from keeping process alive
|
|
19
20
|
|
|
20
21
|
// Flush on process exit
|
|
21
22
|
process.on('beforeExit', () => this.flushSync());
|