@sashabogi/argus-mcp 1.2.1 → 1.2.3

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/dist/mcp.mjs CHANGED
@@ -79,6 +79,10 @@ function validateConfig(config2) {
79
79
  return errors;
80
80
  }
81
81
 
82
+ // src/core/enhanced-snapshot.ts
83
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
84
+ import { join as join3, dirname, extname as extname2 } from "path";
85
+
82
86
  // src/core/snapshot.ts
83
87
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
84
88
  import { join as join2, relative, extname } from "path";
@@ -203,8 +207,253 @@ function createSnapshot(projectPath, outputPath, options = {}) {
203
207
  };
204
208
  }
205
209
 
210
+ // src/core/enhanced-snapshot.ts
211
+ function parseImports(content, filePath) {
212
+ const imports = [];
213
+ const lines = content.split("\n");
214
+ const patterns = [
215
+ // import { a, b } from 'module'
216
+ /import\s+(?:type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g,
217
+ // import * as name from 'module'
218
+ /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
219
+ // import defaultExport from 'module'
220
+ /import\s+(?:type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/g,
221
+ // import 'module' (side-effect)
222
+ /import\s+['"]([^'"]+)['"]/g,
223
+ // require('module')
224
+ /(?:const|let|var)\s+(?:{([^}]+)}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
225
+ ];
226
+ for (const line of lines) {
227
+ const trimmed = line.trim();
228
+ if (!trimmed.startsWith("import") && !trimmed.includes("require(")) continue;
229
+ let match = /import\s+(type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
230
+ if (match) {
231
+ const isType = !!match[1];
232
+ const symbols = match[2].split(",").map((s) => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
233
+ const target = match[3];
234
+ imports.push({
235
+ source: filePath,
236
+ target,
237
+ symbols,
238
+ isDefault: false,
239
+ isType
240
+ });
241
+ continue;
242
+ }
243
+ match = /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
244
+ if (match) {
245
+ imports.push({
246
+ source: filePath,
247
+ target: match[2],
248
+ symbols: ["*"],
249
+ isDefault: false,
250
+ isType: false
251
+ });
252
+ continue;
253
+ }
254
+ match = /import\s+(type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
255
+ if (match && !trimmed.includes("{")) {
256
+ imports.push({
257
+ source: filePath,
258
+ target: match[3],
259
+ symbols: [match[2]],
260
+ isDefault: true,
261
+ isType: !!match[1]
262
+ });
263
+ continue;
264
+ }
265
+ match = /^import\s+['"]([^'"]+)['"]/.exec(trimmed);
266
+ if (match) {
267
+ imports.push({
268
+ source: filePath,
269
+ target: match[1],
270
+ symbols: [],
271
+ isDefault: false,
272
+ isType: false
273
+ });
274
+ }
275
+ }
276
+ return imports;
277
+ }
278
+ function parseExports(content, filePath) {
279
+ const exports = [];
280
+ const lines = content.split("\n");
281
+ for (let i = 0; i < lines.length; i++) {
282
+ const line = lines[i];
283
+ const trimmed = line.trim();
284
+ let match = /export\s+(?:async\s+)?function\s+(\w+)\s*(\([^)]*\))/.exec(trimmed);
285
+ if (match) {
286
+ exports.push({
287
+ file: filePath,
288
+ symbol: match[1],
289
+ type: "function",
290
+ signature: `function ${match[1]}${match[2]}`,
291
+ line: i + 1
292
+ });
293
+ continue;
294
+ }
295
+ match = /export\s+class\s+(\w+)/.exec(trimmed);
296
+ if (match) {
297
+ exports.push({
298
+ file: filePath,
299
+ symbol: match[1],
300
+ type: "class",
301
+ line: i + 1
302
+ });
303
+ continue;
304
+ }
305
+ match = /export\s+(const|let|var)\s+(\w+)/.exec(trimmed);
306
+ if (match) {
307
+ exports.push({
308
+ file: filePath,
309
+ symbol: match[2],
310
+ type: match[1],
311
+ line: i + 1
312
+ });
313
+ continue;
314
+ }
315
+ match = /export\s+(type|interface)\s+(\w+)/.exec(trimmed);
316
+ if (match) {
317
+ exports.push({
318
+ file: filePath,
319
+ symbol: match[2],
320
+ type: match[1],
321
+ line: i + 1
322
+ });
323
+ continue;
324
+ }
325
+ match = /export\s+enum\s+(\w+)/.exec(trimmed);
326
+ if (match) {
327
+ exports.push({
328
+ file: filePath,
329
+ symbol: match[1],
330
+ type: "enum",
331
+ line: i + 1
332
+ });
333
+ continue;
334
+ }
335
+ if (/export\s+default/.test(trimmed)) {
336
+ match = /export\s+default\s+(?:function\s+)?(\w+)?/.exec(trimmed);
337
+ exports.push({
338
+ file: filePath,
339
+ symbol: match?.[1] || "default",
340
+ type: "default",
341
+ line: i + 1
342
+ });
343
+ }
344
+ }
345
+ return exports;
346
+ }
347
+ function resolveImportPath(importPath, fromFile, projectFiles) {
348
+ if (!importPath.startsWith(".")) return void 0;
349
+ const fromDir = dirname(fromFile);
350
+ let resolved = join3(fromDir, importPath);
351
+ const extensions = [".ts", ".tsx", ".js", ".jsx", "", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
352
+ for (const ext of extensions) {
353
+ const candidate = resolved + ext;
354
+ if (projectFiles.includes(candidate) || projectFiles.includes("./" + candidate)) {
355
+ return candidate;
356
+ }
357
+ }
358
+ return void 0;
359
+ }
360
+ function createEnhancedSnapshot(projectPath, outputPath, options = {}) {
361
+ const baseResult = createSnapshot(projectPath, outputPath, options);
362
+ const allImports = [];
363
+ const allExports = [];
364
+ const fileIndex = {};
365
+ const projectFiles = baseResult.files.map((f) => "./" + f);
366
+ for (const relPath of baseResult.files) {
367
+ const fullPath = join3(projectPath, relPath);
368
+ const ext = extname2(relPath).toLowerCase();
369
+ if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
370
+ continue;
371
+ }
372
+ try {
373
+ const content = readFileSync3(fullPath, "utf-8");
374
+ const imports = parseImports(content, relPath);
375
+ const exports = parseExports(content, relPath);
376
+ for (const imp of imports) {
377
+ imp.resolved = resolveImportPath(imp.target, relPath, projectFiles);
378
+ }
379
+ allImports.push(...imports);
380
+ allExports.push(...exports);
381
+ fileIndex[relPath] = {
382
+ path: relPath,
383
+ imports,
384
+ exports,
385
+ size: content.length,
386
+ lines: content.split("\n").length
387
+ };
388
+ } catch {
389
+ }
390
+ }
391
+ const importGraph = {};
392
+ for (const imp of allImports) {
393
+ if (imp.resolved) {
394
+ if (!importGraph[imp.source]) importGraph[imp.source] = [];
395
+ if (!importGraph[imp.source].includes(imp.resolved)) {
396
+ importGraph[imp.source].push(imp.resolved);
397
+ }
398
+ }
399
+ }
400
+ const exportGraph = {};
401
+ for (const imp of allImports) {
402
+ if (imp.resolved) {
403
+ if (!exportGraph[imp.resolved]) exportGraph[imp.resolved] = [];
404
+ if (!exportGraph[imp.resolved].includes(imp.source)) {
405
+ exportGraph[imp.resolved].push(imp.source);
406
+ }
407
+ }
408
+ }
409
+ const symbolIndex = {};
410
+ for (const exp of allExports) {
411
+ if (!symbolIndex[exp.symbol]) symbolIndex[exp.symbol] = [];
412
+ if (!symbolIndex[exp.symbol].includes(exp.file)) {
413
+ symbolIndex[exp.symbol].push(exp.file);
414
+ }
415
+ }
416
+ const metadataSection = `
417
+
418
+ ================================================================================
419
+ METADATA: IMPORT GRAPH
420
+ ================================================================================
421
+ ${Object.entries(importGraph).map(([file, imports]) => `${file}:
422
+ ${imports.map((i) => ` \u2192 ${i}`).join("\n")}`).join("\n\n")}
423
+
424
+ ================================================================================
425
+ METADATA: EXPORT INDEX
426
+ ================================================================================
427
+ ${Object.entries(symbolIndex).map(([symbol, files]) => `${symbol}: ${files.join(", ")}`).join("\n")}
428
+
429
+ ================================================================================
430
+ METADATA: FILE EXPORTS
431
+ ================================================================================
432
+ ${allExports.map((e) => `${e.file}:${e.line} - ${e.type} ${e.symbol}${e.signature ? ` ${e.signature}` : ""}`).join("\n")}
433
+
434
+ ================================================================================
435
+ METADATA: WHO IMPORTS WHOM
436
+ ================================================================================
437
+ ${Object.entries(exportGraph).map(([file, importers]) => `${file} is imported by:
438
+ ${importers.map((i) => ` \u2190 ${i}`).join("\n")}`).join("\n\n")}
439
+ `;
440
+ const existingContent = readFileSync3(outputPath, "utf-8");
441
+ writeFileSync3(outputPath, existingContent + metadataSection);
442
+ return {
443
+ ...baseResult,
444
+ metadata: {
445
+ imports: allImports,
446
+ exports: allExports,
447
+ fileIndex,
448
+ importGraph,
449
+ exportGraph,
450
+ symbolIndex
451
+ }
452
+ };
453
+ }
454
+
206
455
  // src/core/engine.ts
207
- import { readFileSync as readFileSync3 } from "fs";
456
+ import { readFileSync as readFileSync4 } from "fs";
208
457
 
209
458
  // src/core/prompts.ts
210
459
  var NUCLEUS_COMMANDS = `
@@ -562,7 +811,7 @@ async function analyze(provider2, documentPath, query, options = {}) {
562
811
  onProgress
563
812
  } = options;
564
813
  const dynamicLimit = Math.min(getTurnLimit(query), maxTurns);
565
- const content = readFileSync3(documentPath, "utf-8");
814
+ const content = readFileSync4(documentPath, "utf-8");
566
815
  const fileCount = (content.match(/^FILE:/gm) || []).length;
567
816
  const lineCount = content.split("\n").length;
568
817
  const bindings = /* @__PURE__ */ new Map();
@@ -676,7 +925,7 @@ ${truncatedResult}`;
676
925
  };
677
926
  }
678
927
  function searchDocument(documentPath, pattern, options = {}) {
679
- const content = readFileSync3(documentPath, "utf-8");
928
+ const content = readFileSync4(documentPath, "utf-8");
680
929
  const flags = options.caseInsensitive ? "gi" : "g";
681
930
  const regex = new RegExp(pattern, flags);
682
931
  const lines = content.split("\n");
@@ -960,9 +1209,9 @@ function createProviderByType(type, config2) {
960
1209
  }
961
1210
 
962
1211
  // src/mcp.ts
963
- import { existsSync as existsSync3, statSync as statSync2, mkdtempSync, unlinkSync, readFileSync as readFileSync4 } from "fs";
1212
+ import { existsSync as existsSync4, statSync as statSync2, mkdtempSync, unlinkSync, readFileSync as readFileSync5 } from "fs";
964
1213
  import { tmpdir } from "os";
965
- import { join as join3, resolve } from "path";
1214
+ import { join as join4, resolve } from "path";
966
1215
  var TOOLS = [
967
1216
  {
968
1217
  name: "find_importers",
@@ -973,7 +1222,7 @@ Use when you need to know:
973
1222
  - Who uses this function/component?
974
1223
  - Impact analysis before refactoring
975
1224
 
976
- Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
1225
+ Snapshots are enhanced by default and include this metadata.`,
977
1226
  inputSchema: {
978
1227
  type: "object",
979
1228
  properties: {
@@ -998,7 +1247,7 @@ Use when you need to know:
998
1247
  - Which file exports this component?
999
1248
  - Find the source of a type
1000
1249
 
1001
- Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
1250
+ Snapshots are enhanced by default and include this metadata.`,
1002
1251
  inputSchema: {
1003
1252
  type: "object",
1004
1253
  properties: {
@@ -1023,7 +1272,7 @@ Use when you need to understand:
1023
1272
  - What modules need to be loaded?
1024
1273
  - Trace the dependency chain
1025
1274
 
1026
- Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
1275
+ Snapshots are enhanced by default and include this metadata.`,
1027
1276
  inputSchema: {
1028
1277
  type: "object",
1029
1278
  properties: {
@@ -1112,9 +1361,10 @@ Returns matching lines with line numbers - much faster than grep across many fil
1112
1361
  },
1113
1362
  {
1114
1363
  name: "create_snapshot",
1115
- description: `Create a codebase snapshot for analysis. Run this ONCE per project, then use the snapshot for all queries.
1364
+ description: `Create an enhanced codebase snapshot for analysis. Run this ONCE per project, then use the snapshot for all queries.
1116
1365
 
1117
1366
  The snapshot compiles all source files into a single optimized file that survives context compaction.
1367
+ Includes structural metadata (import graph, exports index) for zero-cost dependency queries.
1118
1368
  Store at .argus/snapshot.txt so other tools can find it.
1119
1369
 
1120
1370
  Run this when:
@@ -1202,10 +1452,10 @@ async function handleToolCall(name, args) {
1202
1452
  case "find_importers": {
1203
1453
  const path = resolve(args.path);
1204
1454
  const target = args.target;
1205
- if (!existsSync3(path)) {
1455
+ if (!existsSync4(path)) {
1206
1456
  throw new Error(`File not found: ${path}`);
1207
1457
  }
1208
- const content = readFileSync4(path, "utf-8");
1458
+ const content = readFileSync5(path, "utf-8");
1209
1459
  const metadata = parseSnapshotMetadata(content);
1210
1460
  if (!metadata) {
1211
1461
  throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
@@ -1236,10 +1486,10 @@ async function handleToolCall(name, args) {
1236
1486
  case "find_symbol": {
1237
1487
  const path = resolve(args.path);
1238
1488
  const symbol = args.symbol;
1239
- if (!existsSync3(path)) {
1489
+ if (!existsSync4(path)) {
1240
1490
  throw new Error(`File not found: ${path}`);
1241
1491
  }
1242
- const content = readFileSync4(path, "utf-8");
1492
+ const content = readFileSync5(path, "utf-8");
1243
1493
  const metadata = parseSnapshotMetadata(content);
1244
1494
  if (!metadata) {
1245
1495
  throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
@@ -1256,10 +1506,10 @@ async function handleToolCall(name, args) {
1256
1506
  case "get_file_deps": {
1257
1507
  const path = resolve(args.path);
1258
1508
  const file = args.file;
1259
- if (!existsSync3(path)) {
1509
+ if (!existsSync4(path)) {
1260
1510
  throw new Error(`File not found: ${path}`);
1261
1511
  }
1262
- const content = readFileSync4(path, "utf-8");
1512
+ const content = readFileSync5(path, "utf-8");
1263
1513
  const metadata = parseSnapshotMetadata(content);
1264
1514
  if (!metadata) {
1265
1515
  throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
@@ -1286,16 +1536,16 @@ async function handleToolCall(name, args) {
1286
1536
  const path = resolve(args.path);
1287
1537
  const query = args.query;
1288
1538
  const maxTurns = args.maxTurns || 15;
1289
- if (!existsSync3(path)) {
1539
+ if (!existsSync4(path)) {
1290
1540
  throw new Error(`Path not found: ${path}`);
1291
1541
  }
1292
1542
  let snapshotPath = path;
1293
1543
  let tempSnapshot = false;
1294
1544
  const stats = statSync2(path);
1295
1545
  if (stats.isDirectory()) {
1296
- const tempDir = mkdtempSync(join3(tmpdir(), "argus-"));
1297
- snapshotPath = join3(tempDir, "snapshot.txt");
1298
- const result = createSnapshot(path, snapshotPath, {
1546
+ const tempDir = mkdtempSync(join4(tmpdir(), "argus-"));
1547
+ snapshotPath = join4(tempDir, "snapshot.txt");
1548
+ createEnhancedSnapshot(path, snapshotPath, {
1299
1549
  extensions: config.defaults.snapshotExtensions,
1300
1550
  excludePatterns: config.defaults.excludePatterns
1301
1551
  });
@@ -1310,7 +1560,7 @@ async function handleToolCall(name, args) {
1310
1560
  commands: result.commands
1311
1561
  };
1312
1562
  } finally {
1313
- if (tempSnapshot && existsSync3(snapshotPath)) {
1563
+ if (tempSnapshot && existsSync4(snapshotPath)) {
1314
1564
  unlinkSync(snapshotPath);
1315
1565
  }
1316
1566
  }
@@ -1320,7 +1570,7 @@ async function handleToolCall(name, args) {
1320
1570
  const pattern = args.pattern;
1321
1571
  const caseInsensitive = args.caseInsensitive || false;
1322
1572
  const maxResults = args.maxResults || 50;
1323
- if (!existsSync3(path)) {
1573
+ if (!existsSync4(path)) {
1324
1574
  throw new Error(`File not found: ${path}`);
1325
1575
  }
1326
1576
  const matches = searchDocument(path, pattern, { caseInsensitive, maxResults });
@@ -1335,12 +1585,12 @@ async function handleToolCall(name, args) {
1335
1585
  }
1336
1586
  case "create_snapshot": {
1337
1587
  const path = resolve(args.path);
1338
- const outputPath = args.outputPath ? resolve(args.outputPath) : join3(tmpdir(), `argus-snapshot-${Date.now()}.txt`);
1588
+ const outputPath = args.outputPath ? resolve(args.outputPath) : join4(tmpdir(), `argus-snapshot-${Date.now()}.txt`);
1339
1589
  const extensions = args.extensions || config.defaults.snapshotExtensions;
1340
- if (!existsSync3(path)) {
1590
+ if (!existsSync4(path)) {
1341
1591
  throw new Error(`Path not found: ${path}`);
1342
1592
  }
1343
- const result = createSnapshot(path, outputPath, {
1593
+ const result = createEnhancedSnapshot(path, outputPath, {
1344
1594
  extensions,
1345
1595
  excludePatterns: config.defaults.excludePatterns
1346
1596
  });
@@ -1348,7 +1598,13 @@ async function handleToolCall(name, args) {
1348
1598
  outputPath: result.outputPath,
1349
1599
  fileCount: result.fileCount,
1350
1600
  totalLines: result.totalLines,
1351
- totalSize: result.totalSize
1601
+ totalSize: result.totalSize,
1602
+ enhanced: true,
1603
+ metadata: "metadata" in result ? {
1604
+ imports: result.metadata.imports.length,
1605
+ exports: result.metadata.exports.length,
1606
+ symbols: Object.keys(result.metadata.symbolIndex).length
1607
+ } : void 0
1352
1608
  };
1353
1609
  }
1354
1610
  default: