@su-record/vibe 0.1.2 → 0.1.4
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/README.md +13 -6
- package/bin/vibe +20 -2
- package/package.json +5 -6
- package/scripts/install-mcp.js +31 -5
- package/mcp/dist/__tests__/complexity.test.js +0 -126
- package/mcp/dist/__tests__/memory.test.js +0 -120
- package/mcp/dist/__tests__/python-dart-complexity.test.js +0 -146
- package/mcp/dist/index.js +0 -230
- package/mcp/dist/lib/ContextCompressor.js +0 -305
- package/mcp/dist/lib/MemoryManager.js +0 -334
- package/mcp/dist/lib/ProjectCache.js +0 -126
- package/mcp/dist/lib/PythonParser.js +0 -241
- package/mcp/dist/tools/browser/browserPool.js +0 -76
- package/mcp/dist/tools/browser/browserUtils.js +0 -135
- package/mcp/dist/tools/browser/inspectNetworkRequests.js +0 -140
- package/mcp/dist/tools/browser/monitorConsoleLogs.js +0 -97
- package/mcp/dist/tools/convention/analyzeComplexity.js +0 -248
- package/mcp/dist/tools/convention/applyQualityRules.js +0 -102
- package/mcp/dist/tools/convention/checkCouplingCohesion.js +0 -233
- package/mcp/dist/tools/convention/complexityMetrics.js +0 -133
- package/mcp/dist/tools/convention/dartComplexity.js +0 -117
- package/mcp/dist/tools/convention/getCodingGuide.js +0 -64
- package/mcp/dist/tools/convention/languageDetector.js +0 -50
- package/mcp/dist/tools/convention/pythonComplexity.js +0 -109
- package/mcp/dist/tools/convention/suggestImprovements.js +0 -257
- package/mcp/dist/tools/convention/validateCodeQuality.js +0 -177
- package/mcp/dist/tools/memory/autoSaveContext.js +0 -79
- package/mcp/dist/tools/memory/database.js +0 -123
- package/mcp/dist/tools/memory/deleteMemory.js +0 -39
- package/mcp/dist/tools/memory/listMemories.js +0 -38
- package/mcp/dist/tools/memory/memoryConfig.js +0 -27
- package/mcp/dist/tools/memory/memorySQLite.js +0 -138
- package/mcp/dist/tools/memory/memoryUtils.js +0 -34
- package/mcp/dist/tools/memory/migrate.js +0 -113
- package/mcp/dist/tools/memory/prioritizeMemory.js +0 -109
- package/mcp/dist/tools/memory/recallMemory.js +0 -40
- package/mcp/dist/tools/memory/restoreSessionContext.js +0 -69
- package/mcp/dist/tools/memory/saveMemory.js +0 -34
- package/mcp/dist/tools/memory/searchMemories.js +0 -37
- package/mcp/dist/tools/memory/startSession.js +0 -100
- package/mcp/dist/tools/memory/updateMemory.js +0 -46
- package/mcp/dist/tools/planning/analyzeRequirements.js +0 -166
- package/mcp/dist/tools/planning/createUserStories.js +0 -119
- package/mcp/dist/tools/planning/featureRoadmap.js +0 -202
- package/mcp/dist/tools/planning/generatePrd.js +0 -156
- package/mcp/dist/tools/prompt/analyzePrompt.js +0 -145
- package/mcp/dist/tools/prompt/enhancePrompt.js +0 -105
- package/mcp/dist/tools/semantic/findReferences.js +0 -195
- package/mcp/dist/tools/semantic/findSymbol.js +0 -200
- package/mcp/dist/tools/thinking/analyzeProblem.js +0 -50
- package/mcp/dist/tools/thinking/breakDownProblem.js +0 -140
- package/mcp/dist/tools/thinking/createThinkingChain.js +0 -39
- package/mcp/dist/tools/thinking/formatAsPlan.js +0 -73
- package/mcp/dist/tools/thinking/stepByStepAnalysis.js +0 -58
- package/mcp/dist/tools/thinking/thinkAloudProcess.js +0 -75
- package/mcp/dist/tools/time/getCurrentTime.js +0 -61
- package/mcp/dist/tools/ui/previewUiAscii.js +0 -232
- package/mcp/dist/types/tool.js +0 -2
- package/mcp/package.json +0 -53
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
// SQLite-based memory management system
|
|
2
|
-
// Replaces JSON file storage with proper database
|
|
3
|
-
import Database from 'better-sqlite3';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
export class MemoryManager {
|
|
6
|
-
db;
|
|
7
|
-
static instance = null;
|
|
8
|
-
dbPath;
|
|
9
|
-
recallStmt = null;
|
|
10
|
-
saveStmt = null;
|
|
11
|
-
recallSelectStmt = null;
|
|
12
|
-
recallUpdateStmt = null;
|
|
13
|
-
constructor(customDbPath) {
|
|
14
|
-
if (customDbPath) {
|
|
15
|
-
this.dbPath = customDbPath;
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
const memoryDir = path.join(process.cwd(), 'memories');
|
|
19
|
-
this.dbPath = path.join(memoryDir, 'memories.db');
|
|
20
|
-
// Ensure directory exists synchronously (needed for DB init)
|
|
21
|
-
try {
|
|
22
|
-
require('fs').mkdirSync(memoryDir, { recursive: true });
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
const nodeError = error;
|
|
26
|
-
if (nodeError.code !== 'EEXIST') {
|
|
27
|
-
throw new Error(`Failed to create memory directory: ${nodeError.message}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
this.db = new Database(this.dbPath);
|
|
32
|
-
this.initializeDatabase();
|
|
33
|
-
// Only migrate if using default path (not for tests)
|
|
34
|
-
if (!customDbPath) {
|
|
35
|
-
this.migrateFromJSON();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
static cleanupRegistered = false;
|
|
39
|
-
static getInstance(customDbPath) {
|
|
40
|
-
if (!MemoryManager.instance) {
|
|
41
|
-
MemoryManager.instance = new MemoryManager(customDbPath);
|
|
42
|
-
// Register cleanup handlers only once
|
|
43
|
-
if (!MemoryManager.cleanupRegistered) {
|
|
44
|
-
MemoryManager.cleanupRegistered = true;
|
|
45
|
-
// Increase max listeners to avoid warnings in test environments
|
|
46
|
-
process.setMaxListeners(Math.max(process.getMaxListeners(), 15));
|
|
47
|
-
// Register cleanup on process exit to prevent memory leaks
|
|
48
|
-
const cleanup = () => {
|
|
49
|
-
if (MemoryManager.instance) {
|
|
50
|
-
MemoryManager.instance.close();
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
process.on('exit', cleanup);
|
|
54
|
-
process.on('SIGINT', () => {
|
|
55
|
-
cleanup();
|
|
56
|
-
process.exit(0);
|
|
57
|
-
});
|
|
58
|
-
process.on('SIGTERM', () => {
|
|
59
|
-
cleanup();
|
|
60
|
-
process.exit(0);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return MemoryManager.instance;
|
|
65
|
-
}
|
|
66
|
-
initializeDatabase() {
|
|
67
|
-
// Create memories table
|
|
68
|
-
this.db.exec(`
|
|
69
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
70
|
-
key TEXT PRIMARY KEY,
|
|
71
|
-
value TEXT NOT NULL,
|
|
72
|
-
category TEXT NOT NULL DEFAULT 'general',
|
|
73
|
-
timestamp TEXT NOT NULL,
|
|
74
|
-
lastAccessed TEXT NOT NULL,
|
|
75
|
-
priority INTEGER DEFAULT 0
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
CREATE INDEX IF NOT EXISTS idx_category ON memories(category);
|
|
79
|
-
CREATE INDEX IF NOT EXISTS idx_timestamp ON memories(timestamp);
|
|
80
|
-
CREATE INDEX IF NOT EXISTS idx_priority ON memories(priority);
|
|
81
|
-
CREATE INDEX IF NOT EXISTS idx_lastAccessed ON memories(lastAccessed);
|
|
82
|
-
`);
|
|
83
|
-
// Enable WAL mode for better concurrency
|
|
84
|
-
this.db.pragma('journal_mode = WAL');
|
|
85
|
-
// Pre-compile frequently used statements for performance
|
|
86
|
-
this.initializePreparedStatements();
|
|
87
|
-
}
|
|
88
|
-
initializePreparedStatements() {
|
|
89
|
-
// Pre-compile recall statement
|
|
90
|
-
try {
|
|
91
|
-
this.recallStmt = this.db.prepare(`
|
|
92
|
-
UPDATE memories SET lastAccessed = ?
|
|
93
|
-
WHERE key = ?
|
|
94
|
-
RETURNING *
|
|
95
|
-
`);
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
// RETURNING not supported, pre-compile fallback statements
|
|
99
|
-
this.recallStmt = null;
|
|
100
|
-
this.recallSelectStmt = this.db.prepare(`SELECT * FROM memories WHERE key = ?`);
|
|
101
|
-
this.recallUpdateStmt = this.db.prepare(`UPDATE memories SET lastAccessed = ? WHERE key = ?`);
|
|
102
|
-
}
|
|
103
|
-
// Pre-compile save statement
|
|
104
|
-
this.saveStmt = this.db.prepare(`
|
|
105
|
-
INSERT OR REPLACE INTO memories (key, value, category, timestamp, lastAccessed, priority)
|
|
106
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
107
|
-
`);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Auto-migrate from JSON to SQLite database
|
|
111
|
-
*/
|
|
112
|
-
migrateFromJSON() {
|
|
113
|
-
const jsonPath = this.getJSONPath();
|
|
114
|
-
const memories = this.loadJSONMemories(jsonPath);
|
|
115
|
-
if (memories.length === 0)
|
|
116
|
-
return;
|
|
117
|
-
this.importMemories(memories);
|
|
118
|
-
this.backupAndCleanup(jsonPath, memories.length);
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Get JSON file path
|
|
122
|
-
*/
|
|
123
|
-
getJSONPath() {
|
|
124
|
-
return path.join(path.dirname(this.dbPath), 'memories.json');
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Load memories from JSON file
|
|
128
|
-
*/
|
|
129
|
-
loadJSONMemories(jsonPath) {
|
|
130
|
-
try {
|
|
131
|
-
const jsonData = require('fs').readFileSync(jsonPath, 'utf-8');
|
|
132
|
-
return JSON.parse(jsonData);
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
return [];
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Import memories into SQLite database
|
|
140
|
-
*/
|
|
141
|
-
importMemories(memories) {
|
|
142
|
-
const insert = this.db.prepare(`
|
|
143
|
-
INSERT OR REPLACE INTO memories (key, value, category, timestamp, lastAccessed, priority)
|
|
144
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
145
|
-
`);
|
|
146
|
-
const insertMany = this.db.transaction((items) => {
|
|
147
|
-
for (const item of items) {
|
|
148
|
-
insert.run(item.key, item.value, item.category || 'general', item.timestamp, item.lastAccessed, item.priority || 0);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
insertMany(memories);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Backup JSON file and log migration
|
|
155
|
-
*/
|
|
156
|
-
backupAndCleanup(jsonPath, count) {
|
|
157
|
-
try {
|
|
158
|
-
require('fs').renameSync(jsonPath, `${jsonPath}.backup`);
|
|
159
|
-
// Migration successful - could add logger here
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
// Backup failed but migration completed
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Save or update a memory item
|
|
167
|
-
* @param key - Unique identifier for the memory
|
|
168
|
-
* @param value - Content to store
|
|
169
|
-
* @param category - Category for organization (default: 'general')
|
|
170
|
-
* @param priority - Priority level (default: 0)
|
|
171
|
-
*/
|
|
172
|
-
save(key, value, category = 'general', priority = 0) {
|
|
173
|
-
const timestamp = new Date().toISOString();
|
|
174
|
-
if (this.saveStmt) {
|
|
175
|
-
this.saveStmt.run(key, value, category, timestamp, timestamp, priority);
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
// Fallback if prepared statement not available
|
|
179
|
-
const stmt = this.db.prepare(`
|
|
180
|
-
INSERT OR REPLACE INTO memories (key, value, category, timestamp, lastAccessed, priority)
|
|
181
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
182
|
-
`);
|
|
183
|
-
stmt.run(key, value, category, timestamp, timestamp, priority);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Recall a memory item by key
|
|
188
|
-
* @param key - Memory key to recall
|
|
189
|
-
* @returns Memory item or null if not found
|
|
190
|
-
*/
|
|
191
|
-
recall(key) {
|
|
192
|
-
const timestamp = new Date().toISOString();
|
|
193
|
-
// Use pre-compiled statement if available
|
|
194
|
-
if (this.recallStmt) {
|
|
195
|
-
const result = this.recallStmt.get(timestamp, key);
|
|
196
|
-
return result || null;
|
|
197
|
-
}
|
|
198
|
-
// Fallback for older SQLite versions (using pre-compiled statements)
|
|
199
|
-
if (!this.recallSelectStmt || !this.recallUpdateStmt) {
|
|
200
|
-
throw new Error('Fallback recall statements not initialized');
|
|
201
|
-
}
|
|
202
|
-
const result = this.recallSelectStmt.get(key);
|
|
203
|
-
if (result) {
|
|
204
|
-
this.recallUpdateStmt.run(timestamp, key);
|
|
205
|
-
}
|
|
206
|
-
return result || null;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Delete a memory item
|
|
210
|
-
* @param key - Memory key to delete
|
|
211
|
-
* @returns True if deleted successfully
|
|
212
|
-
*/
|
|
213
|
-
delete(key) {
|
|
214
|
-
const stmt = this.db.prepare(`
|
|
215
|
-
DELETE FROM memories WHERE key = ?
|
|
216
|
-
`);
|
|
217
|
-
const result = stmt.run(key);
|
|
218
|
-
return result.changes > 0;
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Update a memory item's value
|
|
222
|
-
* @param key - Memory key to update
|
|
223
|
-
* @param value - New value
|
|
224
|
-
* @returns True if updated successfully
|
|
225
|
-
*/
|
|
226
|
-
update(key, value) {
|
|
227
|
-
const timestamp = new Date().toISOString();
|
|
228
|
-
const stmt = this.db.prepare(`
|
|
229
|
-
UPDATE memories
|
|
230
|
-
SET value = ?, timestamp = ?, lastAccessed = ?
|
|
231
|
-
WHERE key = ?
|
|
232
|
-
`);
|
|
233
|
-
const result = stmt.run(value, timestamp, timestamp, key);
|
|
234
|
-
return result.changes > 0;
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* List all memories or filter by category
|
|
238
|
-
* @param category - Optional category filter
|
|
239
|
-
* @returns Array of memory items
|
|
240
|
-
*/
|
|
241
|
-
list(category) {
|
|
242
|
-
let stmt;
|
|
243
|
-
if (category) {
|
|
244
|
-
stmt = this.db.prepare(`
|
|
245
|
-
SELECT * FROM memories WHERE category = ?
|
|
246
|
-
ORDER BY priority DESC, timestamp DESC
|
|
247
|
-
`);
|
|
248
|
-
return stmt.all(category);
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
stmt = this.db.prepare(`
|
|
252
|
-
SELECT * FROM memories
|
|
253
|
-
ORDER BY priority DESC, timestamp DESC
|
|
254
|
-
`);
|
|
255
|
-
return stmt.all();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Search memories by keyword
|
|
260
|
-
* @param query - Search query string
|
|
261
|
-
* @returns Array of matching memory items
|
|
262
|
-
*/
|
|
263
|
-
search(query) {
|
|
264
|
-
const stmt = this.db.prepare(`
|
|
265
|
-
SELECT * FROM memories
|
|
266
|
-
WHERE key LIKE ? OR value LIKE ?
|
|
267
|
-
ORDER BY priority DESC, timestamp DESC
|
|
268
|
-
`);
|
|
269
|
-
const pattern = `%${query}%`;
|
|
270
|
-
return stmt.all(pattern, pattern);
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Get memories by priority level
|
|
274
|
-
* @param priority - Priority level to filter
|
|
275
|
-
* @returns Array of memory items with specified priority
|
|
276
|
-
*/
|
|
277
|
-
getByPriority(priority) {
|
|
278
|
-
const stmt = this.db.prepare(`
|
|
279
|
-
SELECT * FROM memories
|
|
280
|
-
WHERE priority = ?
|
|
281
|
-
ORDER BY timestamp DESC
|
|
282
|
-
`);
|
|
283
|
-
return stmt.all(priority);
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Update priority of a memory item
|
|
287
|
-
* @param key - Memory key
|
|
288
|
-
* @param priority - New priority level
|
|
289
|
-
* @returns True if updated successfully
|
|
290
|
-
*/
|
|
291
|
-
setPriority(key, priority) {
|
|
292
|
-
const stmt = this.db.prepare(`
|
|
293
|
-
UPDATE memories SET priority = ? WHERE key = ?
|
|
294
|
-
`);
|
|
295
|
-
const result = stmt.run(priority, key);
|
|
296
|
-
return result.changes > 0;
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Get memory statistics (optimized to single query)
|
|
300
|
-
* @returns Total count and count by category
|
|
301
|
-
*/
|
|
302
|
-
getStats() {
|
|
303
|
-
// Single query with ROLLUP or combined approach
|
|
304
|
-
const categories = this.db.prepare(`
|
|
305
|
-
SELECT category, COUNT(*) as count
|
|
306
|
-
FROM memories
|
|
307
|
-
GROUP BY category
|
|
308
|
-
`).all();
|
|
309
|
-
const byCategory = {};
|
|
310
|
-
let total = 0;
|
|
311
|
-
categories.forEach(cat => {
|
|
312
|
-
byCategory[cat.category] = cat.count;
|
|
313
|
-
total += cat.count;
|
|
314
|
-
});
|
|
315
|
-
return { total, byCategory };
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Close database connection
|
|
319
|
-
*/
|
|
320
|
-
close() {
|
|
321
|
-
if (this.db) {
|
|
322
|
-
this.db.close();
|
|
323
|
-
MemoryManager.instance = null;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Reset singleton instance (useful for testing and cleanup)
|
|
328
|
-
*/
|
|
329
|
-
static resetInstance() {
|
|
330
|
-
if (MemoryManager.instance) {
|
|
331
|
-
MemoryManager.instance.close();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// Project caching utility for ts-morph (v1.3)
|
|
2
|
-
// Implements LRU cache to avoid re-parsing on every request
|
|
3
|
-
import { Project } from 'ts-morph';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
export class ProjectCache {
|
|
6
|
-
static instance = null;
|
|
7
|
-
cache = new Map();
|
|
8
|
-
MAX_CACHE_SIZE = 5;
|
|
9
|
-
CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
10
|
-
MAX_TOTAL_MEMORY_MB = 200; // Max 200MB total cache
|
|
11
|
-
MAX_PROJECT_MEMORY_MB = 100; // Max 100MB per project
|
|
12
|
-
constructor() { }
|
|
13
|
-
static getInstance() {
|
|
14
|
-
if (!ProjectCache.instance) {
|
|
15
|
-
ProjectCache.instance = new ProjectCache();
|
|
16
|
-
}
|
|
17
|
-
return ProjectCache.instance;
|
|
18
|
-
}
|
|
19
|
-
getOrCreate(projectPath) {
|
|
20
|
-
// Normalize path and remove trailing slashes
|
|
21
|
-
let normalizedPath = path.normalize(projectPath);
|
|
22
|
-
if (normalizedPath.endsWith(path.sep) && normalizedPath.length > 1) {
|
|
23
|
-
normalizedPath = normalizedPath.slice(0, -1);
|
|
24
|
-
}
|
|
25
|
-
const now = Date.now();
|
|
26
|
-
// Check if cached and not expired
|
|
27
|
-
const cached = this.cache.get(normalizedPath);
|
|
28
|
-
if (cached && (now - cached.lastAccess) < this.CACHE_TTL) {
|
|
29
|
-
cached.lastAccess = now;
|
|
30
|
-
return cached.project;
|
|
31
|
-
}
|
|
32
|
-
// Remove expired entries
|
|
33
|
-
this.removeExpired();
|
|
34
|
-
// LRU eviction if cache is full
|
|
35
|
-
if (this.cache.size >= this.MAX_CACHE_SIZE) {
|
|
36
|
-
this.evictLRU();
|
|
37
|
-
}
|
|
38
|
-
// Create new project
|
|
39
|
-
const project = new Project({
|
|
40
|
-
useInMemoryFileSystem: false,
|
|
41
|
-
compilerOptions: {
|
|
42
|
-
allowJs: true,
|
|
43
|
-
skipLibCheck: true,
|
|
44
|
-
noEmit: true
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
// Add source files
|
|
48
|
-
const pattern = path.join(normalizedPath, '**/*.{ts,tsx,js,jsx}');
|
|
49
|
-
project.addSourceFilesAtPaths(pattern);
|
|
50
|
-
const sourceFiles = project.getSourceFiles();
|
|
51
|
-
const fileCount = sourceFiles.length;
|
|
52
|
-
// Estimate memory usage (rough: 1MB base + 0.5MB per file)
|
|
53
|
-
const estimatedMemoryMB = 1 + (fileCount * 0.5);
|
|
54
|
-
// Skip caching if project is too large
|
|
55
|
-
if (estimatedMemoryMB > this.MAX_PROJECT_MEMORY_MB) {
|
|
56
|
-
console.warn(`Project ${normalizedPath} is too large (${estimatedMemoryMB}MB, ${fileCount} files) - not caching`);
|
|
57
|
-
return project;
|
|
58
|
-
}
|
|
59
|
-
// Check total cache memory before adding
|
|
60
|
-
const totalMemory = this.getTotalMemoryUsage();
|
|
61
|
-
if (totalMemory + estimatedMemoryMB > this.MAX_TOTAL_MEMORY_MB) {
|
|
62
|
-
// Evict projects until we have enough space
|
|
63
|
-
while (this.getTotalMemoryUsage() + estimatedMemoryMB > this.MAX_TOTAL_MEMORY_MB && this.cache.size > 0) {
|
|
64
|
-
this.evictLRU();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
this.cache.set(normalizedPath, {
|
|
68
|
-
project,
|
|
69
|
-
lastAccess: now,
|
|
70
|
-
fileCount,
|
|
71
|
-
estimatedMemoryMB
|
|
72
|
-
});
|
|
73
|
-
return project;
|
|
74
|
-
}
|
|
75
|
-
invalidate(projectPath) {
|
|
76
|
-
const normalizedPath = path.normalize(projectPath);
|
|
77
|
-
this.cache.delete(normalizedPath);
|
|
78
|
-
}
|
|
79
|
-
clear() {
|
|
80
|
-
this.cache.clear();
|
|
81
|
-
}
|
|
82
|
-
getStats() {
|
|
83
|
-
const now = Date.now();
|
|
84
|
-
const projects = Array.from(this.cache.entries()).map(([path, cached]) => ({
|
|
85
|
-
path,
|
|
86
|
-
files: cached.fileCount,
|
|
87
|
-
memoryMB: cached.estimatedMemoryMB,
|
|
88
|
-
age: Math.floor((now - cached.lastAccess) / 1000) // seconds
|
|
89
|
-
}));
|
|
90
|
-
return {
|
|
91
|
-
size: this.cache.size,
|
|
92
|
-
totalMemoryMB: this.getTotalMemoryUsage(),
|
|
93
|
-
projects
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
getTotalMemoryUsage() {
|
|
97
|
-
let total = 0;
|
|
98
|
-
this.cache.forEach(cached => {
|
|
99
|
-
total += cached.estimatedMemoryMB;
|
|
100
|
-
});
|
|
101
|
-
return total;
|
|
102
|
-
}
|
|
103
|
-
removeExpired() {
|
|
104
|
-
const now = Date.now();
|
|
105
|
-
const toRemove = [];
|
|
106
|
-
this.cache.forEach((cached, path) => {
|
|
107
|
-
if ((now - cached.lastAccess) >= this.CACHE_TTL) {
|
|
108
|
-
toRemove.push(path);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
toRemove.forEach(path => this.cache.delete(path));
|
|
112
|
-
}
|
|
113
|
-
evictLRU() {
|
|
114
|
-
let oldestPath = null;
|
|
115
|
-
let oldestTime = Date.now();
|
|
116
|
-
this.cache.forEach((cached, path) => {
|
|
117
|
-
if (cached.lastAccess < oldestTime) {
|
|
118
|
-
oldestTime = cached.lastAccess;
|
|
119
|
-
oldestPath = path;
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
if (oldestPath) {
|
|
123
|
-
this.cache.delete(oldestPath);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
// Python code parser utility for v1.3
|
|
2
|
-
// Uses Python's ast module via child_process
|
|
3
|
-
import { exec } from 'child_process';
|
|
4
|
-
import { promisify } from 'util';
|
|
5
|
-
import { writeFile, unlink } from 'fs/promises';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import os from 'os';
|
|
8
|
-
const execAsync = promisify(exec);
|
|
9
|
-
export class PythonParser {
|
|
10
|
-
static cleanupRegistered = false;
|
|
11
|
-
static pythonScript = `
|
|
12
|
-
import ast
|
|
13
|
-
import sys
|
|
14
|
-
import json
|
|
15
|
-
|
|
16
|
-
def analyze_code(code):
|
|
17
|
-
try:
|
|
18
|
-
tree = ast.parse(code)
|
|
19
|
-
symbols = []
|
|
20
|
-
|
|
21
|
-
for node in ast.walk(tree):
|
|
22
|
-
if isinstance(node, ast.FunctionDef):
|
|
23
|
-
symbols.append({
|
|
24
|
-
'name': node.name,
|
|
25
|
-
'kind': 'function',
|
|
26
|
-
'line': node.lineno,
|
|
27
|
-
'column': node.col_offset,
|
|
28
|
-
'endLine': node.end_lineno,
|
|
29
|
-
'docstring': ast.get_docstring(node)
|
|
30
|
-
})
|
|
31
|
-
elif isinstance(node, ast.ClassDef):
|
|
32
|
-
symbols.append({
|
|
33
|
-
'name': node.name,
|
|
34
|
-
'kind': 'class',
|
|
35
|
-
'line': node.lineno,
|
|
36
|
-
'column': node.col_offset,
|
|
37
|
-
'endLine': node.end_lineno,
|
|
38
|
-
'docstring': ast.get_docstring(node)
|
|
39
|
-
})
|
|
40
|
-
elif isinstance(node, ast.Assign):
|
|
41
|
-
for target in node.targets:
|
|
42
|
-
if isinstance(target, ast.Name):
|
|
43
|
-
symbols.append({
|
|
44
|
-
'name': target.id,
|
|
45
|
-
'kind': 'variable',
|
|
46
|
-
'line': node.lineno,
|
|
47
|
-
'column': node.col_offset
|
|
48
|
-
})
|
|
49
|
-
elif isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom):
|
|
50
|
-
for alias in node.names:
|
|
51
|
-
symbols.append({
|
|
52
|
-
'name': alias.name,
|
|
53
|
-
'kind': 'import',
|
|
54
|
-
'line': node.lineno,
|
|
55
|
-
'column': node.col_offset
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
return {'success': True, 'symbols': symbols}
|
|
59
|
-
except SyntaxError as e:
|
|
60
|
-
return {'success': False, 'error': str(e)}
|
|
61
|
-
except Exception as e:
|
|
62
|
-
return {'success': False, 'error': str(e)}
|
|
63
|
-
|
|
64
|
-
def calculate_complexity(code):
|
|
65
|
-
try:
|
|
66
|
-
tree = ast.parse(code)
|
|
67
|
-
|
|
68
|
-
def cyclomatic_complexity(node):
|
|
69
|
-
complexity = 1
|
|
70
|
-
for child in ast.walk(node):
|
|
71
|
-
if isinstance(child, (ast.If, ast.For, ast.While, ast.And, ast.Or, ast.ExceptHandler)):
|
|
72
|
-
complexity += 1
|
|
73
|
-
elif isinstance(child, ast.BoolOp):
|
|
74
|
-
complexity += len(child.values) - 1
|
|
75
|
-
return complexity
|
|
76
|
-
|
|
77
|
-
functions = []
|
|
78
|
-
classes = []
|
|
79
|
-
total_complexity = 1
|
|
80
|
-
|
|
81
|
-
for node in ast.walk(tree):
|
|
82
|
-
if isinstance(node, ast.FunctionDef):
|
|
83
|
-
func_complexity = cyclomatic_complexity(node)
|
|
84
|
-
functions.append({
|
|
85
|
-
'name': node.name,
|
|
86
|
-
'complexity': func_complexity,
|
|
87
|
-
'line': node.lineno
|
|
88
|
-
})
|
|
89
|
-
total_complexity += func_complexity
|
|
90
|
-
elif isinstance(node, ast.ClassDef):
|
|
91
|
-
method_count = sum(1 for n in node.body if isinstance(n, ast.FunctionDef))
|
|
92
|
-
classes.append({
|
|
93
|
-
'name': node.name,
|
|
94
|
-
'methods': method_count,
|
|
95
|
-
'line': node.lineno
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
'success': True,
|
|
100
|
-
'cyclomaticComplexity': total_complexity,
|
|
101
|
-
'functions': functions,
|
|
102
|
-
'classes': classes
|
|
103
|
-
}
|
|
104
|
-
except Exception as e:
|
|
105
|
-
return {'success': False, 'error': str(e)}
|
|
106
|
-
|
|
107
|
-
if __name__ == '__main__':
|
|
108
|
-
code = sys.stdin.read()
|
|
109
|
-
action = sys.argv[1] if len(sys.argv) > 1 else 'symbols'
|
|
110
|
-
|
|
111
|
-
if action == 'symbols':
|
|
112
|
-
result = analyze_code(code)
|
|
113
|
-
elif action == 'complexity':
|
|
114
|
-
result = calculate_complexity(code)
|
|
115
|
-
else:
|
|
116
|
-
result = {'success': False, 'error': 'Unknown action'}
|
|
117
|
-
|
|
118
|
-
print(json.dumps(result))
|
|
119
|
-
`;
|
|
120
|
-
// Singleton Python script path to avoid recreating it
|
|
121
|
-
static scriptPath = null;
|
|
122
|
-
/**
|
|
123
|
-
* Register cleanup handlers on first use
|
|
124
|
-
*/
|
|
125
|
-
static registerCleanup() {
|
|
126
|
-
if (this.cleanupRegistered) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
this.cleanupRegistered = true;
|
|
130
|
-
// Cleanup on normal exit
|
|
131
|
-
process.on('exit', () => {
|
|
132
|
-
if (this.scriptPath) {
|
|
133
|
-
try {
|
|
134
|
-
const fs = require('fs');
|
|
135
|
-
fs.unlinkSync(this.scriptPath);
|
|
136
|
-
}
|
|
137
|
-
catch (e) {
|
|
138
|
-
// Ignore errors during cleanup
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
// Cleanup on SIGINT (Ctrl+C)
|
|
143
|
-
process.on('SIGINT', () => {
|
|
144
|
-
this.cleanup().then(() => process.exit(0));
|
|
145
|
-
});
|
|
146
|
-
// Cleanup on SIGTERM
|
|
147
|
-
process.on('SIGTERM', () => {
|
|
148
|
-
this.cleanup().then(() => process.exit(0));
|
|
149
|
-
});
|
|
150
|
-
// Cleanup on uncaught exception
|
|
151
|
-
process.on('uncaughtException', (error) => {
|
|
152
|
-
console.error('Uncaught exception:', error);
|
|
153
|
-
this.cleanup().then(() => process.exit(1));
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Initialize Python script (singleton pattern)
|
|
158
|
-
*/
|
|
159
|
-
static async ensureScriptExists() {
|
|
160
|
-
if (this.scriptPath) {
|
|
161
|
-
return this.scriptPath;
|
|
162
|
-
}
|
|
163
|
-
// Register cleanup handlers on first use
|
|
164
|
-
this.registerCleanup();
|
|
165
|
-
this.scriptPath = path.join(os.tmpdir(), `hi-ai-parser-${process.pid}.py`);
|
|
166
|
-
await writeFile(this.scriptPath, this.pythonScript);
|
|
167
|
-
return this.scriptPath;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Execute Python code analysis with improved memory management
|
|
171
|
-
*/
|
|
172
|
-
static async executePython(code, action) {
|
|
173
|
-
let codePath = null;
|
|
174
|
-
try {
|
|
175
|
-
const scriptPath = await this.ensureScriptExists();
|
|
176
|
-
// Write code to temp file with unique name
|
|
177
|
-
codePath = path.join(os.tmpdir(), `hi-ai-code-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.py`);
|
|
178
|
-
await writeFile(codePath, code);
|
|
179
|
-
// Execute Python script
|
|
180
|
-
const { stdout, stderr } = await execAsync(`python3 "${scriptPath}" ${action} < "${codePath}"`, {
|
|
181
|
-
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
182
|
-
timeout: 30000 // 30 second timeout
|
|
183
|
-
});
|
|
184
|
-
if (stderr && !stderr.includes('DeprecationWarning')) {
|
|
185
|
-
console.error('Python stderr:', stderr);
|
|
186
|
-
}
|
|
187
|
-
const result = JSON.parse(stdout);
|
|
188
|
-
if (!result.success) {
|
|
189
|
-
throw new Error(result.error || `Python ${action} analysis failed`);
|
|
190
|
-
}
|
|
191
|
-
return result;
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
if (error.code === 'ENOENT') {
|
|
195
|
-
throw new Error('Python 3 not found. Please install Python 3 to analyze Python code.');
|
|
196
|
-
}
|
|
197
|
-
throw error;
|
|
198
|
-
}
|
|
199
|
-
finally {
|
|
200
|
-
// Always cleanup code temp file immediately
|
|
201
|
-
if (codePath) {
|
|
202
|
-
await unlink(codePath).catch(() => { });
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
static async findSymbols(code) {
|
|
207
|
-
const result = await this.executePython(code, 'symbols');
|
|
208
|
-
return result.symbols || [];
|
|
209
|
-
}
|
|
210
|
-
static async analyzeComplexity(code) {
|
|
211
|
-
const result = await this.executePython(code, 'complexity');
|
|
212
|
-
return {
|
|
213
|
-
cyclomaticComplexity: result.cyclomaticComplexity || 1,
|
|
214
|
-
functions: result.functions || [],
|
|
215
|
-
classes: result.classes || []
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Cleanup singleton script on process exit
|
|
220
|
-
*/
|
|
221
|
-
static async cleanup() {
|
|
222
|
-
if (this.scriptPath) {
|
|
223
|
-
await unlink(this.scriptPath).catch(() => { });
|
|
224
|
-
this.scriptPath = null;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
static isPythonFile(filePath) {
|
|
228
|
-
return filePath.endsWith('.py');
|
|
229
|
-
}
|
|
230
|
-
static isPythonCode(code) {
|
|
231
|
-
// Heuristic: Check for Python-specific patterns
|
|
232
|
-
const pythonPatterns = [
|
|
233
|
-
/^import\s+\w+/m,
|
|
234
|
-
/^from\s+\w+\s+import/m,
|
|
235
|
-
/^def\s+\w+\s*\(/m,
|
|
236
|
-
/^class\s+\w+/m,
|
|
237
|
-
/^if\s+__name__\s*==\s*['"]__main__['"]/m
|
|
238
|
-
];
|
|
239
|
-
return pythonPatterns.some(pattern => pattern.test(code));
|
|
240
|
-
}
|
|
241
|
-
}
|