@sashabogi/argus-mcp 1.2.2 → 2.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/dist/mcp.mjs CHANGED
@@ -1,9 +1,219 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // node_modules/tsup/assets/esm_shims.js
13
+ var init_esm_shims = __esm({
14
+ "node_modules/tsup/assets/esm_shims.js"() {
15
+ "use strict";
16
+ }
17
+ });
18
+
19
+ // src/core/semantic-search.ts
20
+ var semantic_search_exports = {};
21
+ __export(semantic_search_exports, {
22
+ SemanticIndex: () => SemanticIndex
23
+ });
24
+ import Database from "better-sqlite3";
25
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
26
+ import { dirname as dirname2 } from "path";
27
+ var SemanticIndex;
28
+ var init_semantic_search = __esm({
29
+ "src/core/semantic-search.ts"() {
30
+ "use strict";
31
+ init_esm_shims();
32
+ SemanticIndex = class {
33
+ db;
34
+ initialized = false;
35
+ constructor(dbPath) {
36
+ const dir = dirname2(dbPath);
37
+ if (!existsSync4(dir)) {
38
+ mkdirSync2(dir, { recursive: true });
39
+ }
40
+ this.db = new Database(dbPath);
41
+ this.initialize();
42
+ }
43
+ initialize() {
44
+ if (this.initialized) return;
45
+ this.db.exec(`
46
+ CREATE VIRTUAL TABLE IF NOT EXISTS code_index USING fts5(
47
+ file,
48
+ symbol,
49
+ content,
50
+ type,
51
+ tokenize='porter unicode61'
52
+ );
53
+ `);
54
+ this.db.exec(`
55
+ CREATE TABLE IF NOT EXISTS index_metadata (
56
+ key TEXT PRIMARY KEY,
57
+ value TEXT
58
+ );
59
+ `);
60
+ this.initialized = true;
61
+ }
62
+ /**
63
+ * Clear the index and rebuild from scratch
64
+ */
65
+ clear() {
66
+ this.db.exec("DELETE FROM code_index");
67
+ }
68
+ /**
69
+ * Index a file's symbols and content
70
+ */
71
+ indexFile(file, symbols) {
72
+ const insert = this.db.prepare(`
73
+ INSERT INTO code_index (file, symbol, content, type)
74
+ VALUES (?, ?, ?, ?)
75
+ `);
76
+ const tx = this.db.transaction(() => {
77
+ for (const sym of symbols) {
78
+ insert.run(file, sym.name, sym.content, sym.type);
79
+ }
80
+ });
81
+ tx();
82
+ }
83
+ /**
84
+ * Index content from a snapshot file
85
+ */
86
+ indexFromSnapshot(snapshotPath) {
87
+ const content = readFileSync5(snapshotPath, "utf-8");
88
+ this.clear();
89
+ let filesIndexed = 0;
90
+ let symbolsIndexed = 0;
91
+ const fileRegex = /^FILE: \.\/(.+)$/gm;
92
+ const files = [];
93
+ let match;
94
+ while ((match = fileRegex.exec(content)) !== null) {
95
+ if (files.length > 0) {
96
+ files[files.length - 1].end = match.index;
97
+ }
98
+ files.push({ path: match[1], start: match.index, end: content.length });
99
+ }
100
+ const metadataStart = content.indexOf("\nMETADATA:");
101
+ if (metadataStart !== -1 && files.length > 0) {
102
+ files[files.length - 1].end = metadataStart;
103
+ }
104
+ for (const file of files) {
105
+ const fileContent = content.slice(file.start, file.end);
106
+ const lines = fileContent.split("\n").slice(2);
107
+ const symbols = [];
108
+ for (let i = 0; i < lines.length; i++) {
109
+ const line = lines[i];
110
+ const funcMatch = line.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
111
+ if (funcMatch) {
112
+ symbols.push({
113
+ name: funcMatch[1],
114
+ content: lines.slice(i, Math.min(i + 10, lines.length)).join("\n"),
115
+ type: "function"
116
+ });
117
+ }
118
+ const arrowMatch = line.match(/(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/);
119
+ if (arrowMatch) {
120
+ symbols.push({
121
+ name: arrowMatch[1],
122
+ content: lines.slice(i, Math.min(i + 10, lines.length)).join("\n"),
123
+ type: "function"
124
+ });
125
+ }
126
+ const classMatch = line.match(/(?:export\s+)?class\s+(\w+)/);
127
+ if (classMatch) {
128
+ symbols.push({
129
+ name: classMatch[1],
130
+ content: lines.slice(i, Math.min(i + 15, lines.length)).join("\n"),
131
+ type: "class"
132
+ });
133
+ }
134
+ const typeMatch = line.match(/(?:export\s+)?(?:type|interface)\s+(\w+)/);
135
+ if (typeMatch) {
136
+ symbols.push({
137
+ name: typeMatch[1],
138
+ content: lines.slice(i, Math.min(i + 10, lines.length)).join("\n"),
139
+ type: "type"
140
+ });
141
+ }
142
+ const constMatch = line.match(/(?:export\s+)?const\s+(\w+)\s*=\s*(?![^(]*=>)/);
143
+ if (constMatch && !arrowMatch) {
144
+ symbols.push({
145
+ name: constMatch[1],
146
+ content: lines.slice(i, Math.min(i + 5, lines.length)).join("\n"),
147
+ type: "const"
148
+ });
149
+ }
150
+ }
151
+ if (symbols.length > 0) {
152
+ this.indexFile(file.path, symbols);
153
+ filesIndexed++;
154
+ symbolsIndexed += symbols.length;
155
+ }
156
+ }
157
+ this.db.prepare(`
158
+ INSERT OR REPLACE INTO index_metadata (key, value) VALUES (?, ?)
159
+ `).run("last_indexed", (/* @__PURE__ */ new Date()).toISOString());
160
+ this.db.prepare(`
161
+ INSERT OR REPLACE INTO index_metadata (key, value) VALUES (?, ?)
162
+ `).run("snapshot_path", snapshotPath);
163
+ return { filesIndexed, symbolsIndexed };
164
+ }
165
+ /**
166
+ * Search the index
167
+ */
168
+ search(query, limit = 20) {
169
+ const ftsQuery = query.split(/\s+/).map((term) => `${term}*`).join(" ");
170
+ try {
171
+ const stmt = this.db.prepare(`
172
+ SELECT file, symbol, content, type, rank
173
+ FROM code_index
174
+ WHERE code_index MATCH ?
175
+ ORDER BY rank
176
+ LIMIT ?
177
+ `);
178
+ return stmt.all(ftsQuery, limit);
179
+ } catch {
180
+ const stmt = this.db.prepare(`
181
+ SELECT file, symbol, content, type, 0 as rank
182
+ FROM code_index
183
+ WHERE symbol LIKE ? OR content LIKE ?
184
+ ORDER BY symbol
185
+ LIMIT ?
186
+ `);
187
+ const likePattern = `%${query}%`;
188
+ return stmt.all(likePattern, likePattern, limit);
189
+ }
190
+ }
191
+ /**
192
+ * Get index statistics
193
+ */
194
+ getStats() {
195
+ const countResult = this.db.prepare("SELECT COUNT(*) as count FROM code_index").get();
196
+ const lastIndexed = this.db.prepare("SELECT value FROM index_metadata WHERE key = 'last_indexed'").get();
197
+ const snapshotPath = this.db.prepare("SELECT value FROM index_metadata WHERE key = 'snapshot_path'").get();
198
+ return {
199
+ totalSymbols: countResult.count,
200
+ lastIndexed: lastIndexed?.value || null,
201
+ snapshotPath: snapshotPath?.value || null
202
+ };
203
+ }
204
+ close() {
205
+ this.db.close();
206
+ }
207
+ };
208
+ }
209
+ });
2
210
 
3
211
  // src/mcp.ts
212
+ init_esm_shims();
4
213
  import { createInterface } from "readline";
5
214
 
6
215
  // src/core/config.ts
216
+ init_esm_shims();
7
217
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
218
  import { homedir } from "os";
9
219
  import { join } from "path";
@@ -79,7 +289,14 @@ function validateConfig(config2) {
79
289
  return errors;
80
290
  }
81
291
 
292
+ // src/core/enhanced-snapshot.ts
293
+ init_esm_shims();
294
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
295
+ import { join as join3, dirname, extname as extname2, basename } from "path";
296
+ import { execSync } from "child_process";
297
+
82
298
  // src/core/snapshot.ts
299
+ init_esm_shims();
83
300
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
84
301
  import { join as join2, relative, extname } from "path";
85
302
  var DEFAULT_OPTIONS = {
@@ -203,10 +420,404 @@ function createSnapshot(projectPath, outputPath, options = {}) {
203
420
  };
204
421
  }
205
422
 
423
+ // src/core/enhanced-snapshot.ts
424
+ function parseImports(content, filePath) {
425
+ const imports = [];
426
+ const lines = content.split("\n");
427
+ const patterns = [
428
+ // import { a, b } from 'module'
429
+ /import\s+(?:type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g,
430
+ // import * as name from 'module'
431
+ /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
432
+ // import defaultExport from 'module'
433
+ /import\s+(?:type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/g,
434
+ // import 'module' (side-effect)
435
+ /import\s+['"]([^'"]+)['"]/g,
436
+ // require('module')
437
+ /(?:const|let|var)\s+(?:{([^}]+)}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
438
+ ];
439
+ for (const line of lines) {
440
+ const trimmed = line.trim();
441
+ if (!trimmed.startsWith("import") && !trimmed.includes("require(")) continue;
442
+ let match = /import\s+(type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
443
+ if (match) {
444
+ const isType = !!match[1];
445
+ const symbols = match[2].split(",").map((s) => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
446
+ const target = match[3];
447
+ imports.push({
448
+ source: filePath,
449
+ target,
450
+ symbols,
451
+ isDefault: false,
452
+ isType
453
+ });
454
+ continue;
455
+ }
456
+ match = /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
457
+ if (match) {
458
+ imports.push({
459
+ source: filePath,
460
+ target: match[2],
461
+ symbols: ["*"],
462
+ isDefault: false,
463
+ isType: false
464
+ });
465
+ continue;
466
+ }
467
+ match = /import\s+(type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/.exec(trimmed);
468
+ if (match && !trimmed.includes("{")) {
469
+ imports.push({
470
+ source: filePath,
471
+ target: match[3],
472
+ symbols: [match[2]],
473
+ isDefault: true,
474
+ isType: !!match[1]
475
+ });
476
+ continue;
477
+ }
478
+ match = /^import\s+['"]([^'"]+)['"]/.exec(trimmed);
479
+ if (match) {
480
+ imports.push({
481
+ source: filePath,
482
+ target: match[1],
483
+ symbols: [],
484
+ isDefault: false,
485
+ isType: false
486
+ });
487
+ }
488
+ }
489
+ return imports;
490
+ }
491
+ function parseExports(content, filePath) {
492
+ const exports = [];
493
+ const lines = content.split("\n");
494
+ for (let i = 0; i < lines.length; i++) {
495
+ const line = lines[i];
496
+ const trimmed = line.trim();
497
+ let match = /export\s+(?:async\s+)?function\s+(\w+)\s*(\([^)]*\))/.exec(trimmed);
498
+ if (match) {
499
+ exports.push({
500
+ file: filePath,
501
+ symbol: match[1],
502
+ type: "function",
503
+ signature: `function ${match[1]}${match[2]}`,
504
+ line: i + 1
505
+ });
506
+ continue;
507
+ }
508
+ match = /export\s+class\s+(\w+)/.exec(trimmed);
509
+ if (match) {
510
+ exports.push({
511
+ file: filePath,
512
+ symbol: match[1],
513
+ type: "class",
514
+ line: i + 1
515
+ });
516
+ continue;
517
+ }
518
+ match = /export\s+(const|let|var)\s+(\w+)/.exec(trimmed);
519
+ if (match) {
520
+ exports.push({
521
+ file: filePath,
522
+ symbol: match[2],
523
+ type: match[1],
524
+ line: i + 1
525
+ });
526
+ continue;
527
+ }
528
+ match = /export\s+(type|interface)\s+(\w+)/.exec(trimmed);
529
+ if (match) {
530
+ exports.push({
531
+ file: filePath,
532
+ symbol: match[2],
533
+ type: match[1],
534
+ line: i + 1
535
+ });
536
+ continue;
537
+ }
538
+ match = /export\s+enum\s+(\w+)/.exec(trimmed);
539
+ if (match) {
540
+ exports.push({
541
+ file: filePath,
542
+ symbol: match[1],
543
+ type: "enum",
544
+ line: i + 1
545
+ });
546
+ continue;
547
+ }
548
+ if (/export\s+default/.test(trimmed)) {
549
+ match = /export\s+default\s+(?:function\s+)?(\w+)?/.exec(trimmed);
550
+ exports.push({
551
+ file: filePath,
552
+ symbol: match?.[1] || "default",
553
+ type: "default",
554
+ line: i + 1
555
+ });
556
+ }
557
+ }
558
+ return exports;
559
+ }
560
+ function calculateComplexity(content) {
561
+ const patterns = [
562
+ /\bif\s*\(/g,
563
+ /\belse\s+if\s*\(/g,
564
+ /\bwhile\s*\(/g,
565
+ /\bfor\s*\(/g,
566
+ /\bcase\s+/g,
567
+ /\?\s*.*\s*:/g,
568
+ /\&\&/g,
569
+ /\|\|/g,
570
+ /\bcatch\s*\(/g
571
+ ];
572
+ let complexity = 1;
573
+ for (const pattern of patterns) {
574
+ const matches = content.match(pattern);
575
+ if (matches) complexity += matches.length;
576
+ }
577
+ return complexity;
578
+ }
579
+ function getComplexityLevel(score) {
580
+ if (score <= 10) return "low";
581
+ if (score <= 20) return "medium";
582
+ return "high";
583
+ }
584
+ function mapTestFiles(files) {
585
+ const testMap = {};
586
+ const testPatterns = [
587
+ // Same directory patterns
588
+ (src) => src.replace(/\.tsx?$/, ".test.ts"),
589
+ (src) => src.replace(/\.tsx?$/, ".test.tsx"),
590
+ (src) => src.replace(/\.tsx?$/, ".spec.ts"),
591
+ (src) => src.replace(/\.tsx?$/, ".spec.tsx"),
592
+ (src) => src.replace(/\.jsx?$/, ".test.js"),
593
+ (src) => src.replace(/\.jsx?$/, ".test.jsx"),
594
+ (src) => src.replace(/\.jsx?$/, ".spec.js"),
595
+ (src) => src.replace(/\.jsx?$/, ".spec.jsx"),
596
+ // __tests__ directory pattern
597
+ (src) => {
598
+ const dir = dirname(src);
599
+ const base = basename(src).replace(/\.(tsx?|jsx?)$/, "");
600
+ return join3(dir, "__tests__", `${base}.test.ts`);
601
+ },
602
+ (src) => {
603
+ const dir = dirname(src);
604
+ const base = basename(src).replace(/\.(tsx?|jsx?)$/, "");
605
+ return join3(dir, "__tests__", `${base}.test.tsx`);
606
+ },
607
+ // test/ directory pattern
608
+ (src) => src.replace(/^src\//, "test/").replace(/\.(tsx?|jsx?)$/, ".test.ts"),
609
+ (src) => src.replace(/^src\//, "tests/").replace(/\.(tsx?|jsx?)$/, ".test.ts")
610
+ ];
611
+ const fileSet = new Set(files);
612
+ for (const file of files) {
613
+ if (file.includes(".test.") || file.includes(".spec.") || file.includes("__tests__")) continue;
614
+ if (!/\.(tsx?|jsx?)$/.test(file)) continue;
615
+ const tests = [];
616
+ for (const pattern of testPatterns) {
617
+ const testPath = pattern(file);
618
+ if (testPath !== file && fileSet.has(testPath)) {
619
+ tests.push(testPath);
620
+ }
621
+ }
622
+ if (tests.length > 0) {
623
+ testMap[file] = [...new Set(tests)];
624
+ }
625
+ }
626
+ return testMap;
627
+ }
628
+ function getRecentChanges(projectPath) {
629
+ try {
630
+ execSync("git rev-parse --git-dir", { cwd: projectPath, encoding: "utf-8", stdio: "pipe" });
631
+ const output = execSync(
632
+ 'git log --since="7 days ago" --name-only --format="COMMIT_AUTHOR:%an" --diff-filter=ACMR',
633
+ { cwd: projectPath, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024, stdio: "pipe" }
634
+ );
635
+ if (!output.trim()) return [];
636
+ const fileStats = {};
637
+ let currentAuthor = "";
638
+ let currentCommitId = 0;
639
+ for (const line of output.split("\n")) {
640
+ const trimmed = line.trim();
641
+ if (!trimmed) {
642
+ currentCommitId++;
643
+ continue;
644
+ }
645
+ if (trimmed.startsWith("COMMIT_AUTHOR:")) {
646
+ currentAuthor = trimmed.replace("COMMIT_AUTHOR:", "");
647
+ continue;
648
+ }
649
+ const file = trimmed;
650
+ if (!fileStats[file]) {
651
+ fileStats[file] = { commits: /* @__PURE__ */ new Set(), authors: /* @__PURE__ */ new Set() };
652
+ }
653
+ fileStats[file].commits.add(`${currentCommitId}`);
654
+ if (currentAuthor) {
655
+ fileStats[file].authors.add(currentAuthor);
656
+ }
657
+ }
658
+ const result = Object.entries(fileStats).map(([file, stats]) => ({
659
+ file,
660
+ commits: stats.commits.size,
661
+ authors: stats.authors.size
662
+ })).sort((a, b) => b.commits - a.commits);
663
+ return result;
664
+ } catch {
665
+ return null;
666
+ }
667
+ }
668
+ function resolveImportPath(importPath, fromFile, projectFiles) {
669
+ if (!importPath.startsWith(".")) return void 0;
670
+ const fromDir = dirname(fromFile);
671
+ let resolved = join3(fromDir, importPath);
672
+ const extensions = [".ts", ".tsx", ".js", ".jsx", "", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
673
+ for (const ext of extensions) {
674
+ const candidate = resolved + ext;
675
+ if (projectFiles.includes(candidate) || projectFiles.includes("./" + candidate)) {
676
+ return candidate;
677
+ }
678
+ }
679
+ return void 0;
680
+ }
681
+ function createEnhancedSnapshot(projectPath, outputPath, options = {}) {
682
+ const baseResult = createSnapshot(projectPath, outputPath, options);
683
+ const allImports = [];
684
+ const allExports = [];
685
+ const fileIndex = {};
686
+ const projectFiles = baseResult.files.map((f) => "./" + f);
687
+ for (const relPath of baseResult.files) {
688
+ const fullPath = join3(projectPath, relPath);
689
+ const ext = extname2(relPath).toLowerCase();
690
+ if (![".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
691
+ continue;
692
+ }
693
+ try {
694
+ const content = readFileSync3(fullPath, "utf-8");
695
+ const imports = parseImports(content, relPath);
696
+ const exports = parseExports(content, relPath);
697
+ for (const imp of imports) {
698
+ imp.resolved = resolveImportPath(imp.target, relPath, projectFiles);
699
+ }
700
+ allImports.push(...imports);
701
+ allExports.push(...exports);
702
+ fileIndex[relPath] = {
703
+ path: relPath,
704
+ imports,
705
+ exports,
706
+ size: content.length,
707
+ lines: content.split("\n").length
708
+ };
709
+ } catch {
710
+ }
711
+ }
712
+ const importGraph = {};
713
+ for (const imp of allImports) {
714
+ if (imp.resolved) {
715
+ if (!importGraph[imp.source]) importGraph[imp.source] = [];
716
+ if (!importGraph[imp.source].includes(imp.resolved)) {
717
+ importGraph[imp.source].push(imp.resolved);
718
+ }
719
+ }
720
+ }
721
+ const exportGraph = {};
722
+ for (const imp of allImports) {
723
+ if (imp.resolved) {
724
+ if (!exportGraph[imp.resolved]) exportGraph[imp.resolved] = [];
725
+ if (!exportGraph[imp.resolved].includes(imp.source)) {
726
+ exportGraph[imp.resolved].push(imp.source);
727
+ }
728
+ }
729
+ }
730
+ const symbolIndex = {};
731
+ for (const exp of allExports) {
732
+ if (!symbolIndex[exp.symbol]) symbolIndex[exp.symbol] = [];
733
+ if (!symbolIndex[exp.symbol].includes(exp.file)) {
734
+ symbolIndex[exp.symbol].push(exp.file);
735
+ }
736
+ }
737
+ const complexityScores = [];
738
+ for (const [relPath, metadata] of Object.entries(fileIndex)) {
739
+ const fullPath = join3(projectPath, relPath);
740
+ try {
741
+ const content = readFileSync3(fullPath, "utf-8");
742
+ const score = calculateComplexity(content);
743
+ complexityScores.push({
744
+ file: relPath,
745
+ score,
746
+ level: getComplexityLevel(score)
747
+ });
748
+ } catch {
749
+ }
750
+ }
751
+ complexityScores.sort((a, b) => b.score - a.score);
752
+ const testFileMap = mapTestFiles(baseResult.files);
753
+ const recentChanges = getRecentChanges(projectPath);
754
+ const metadataSection = `
755
+
756
+ ================================================================================
757
+ METADATA: IMPORT GRAPH
758
+ ================================================================================
759
+ ${Object.entries(importGraph).map(([file, imports]) => `${file}:
760
+ ${imports.map((i) => ` \u2192 ${i}`).join("\n")}`).join("\n\n")}
761
+
762
+ ================================================================================
763
+ METADATA: EXPORT INDEX
764
+ ================================================================================
765
+ ${Object.entries(symbolIndex).map(([symbol, files]) => `${symbol}: ${files.join(", ")}`).join("\n")}
766
+
767
+ ================================================================================
768
+ METADATA: FILE EXPORTS
769
+ ================================================================================
770
+ ${allExports.map((e) => `${e.file}:${e.line} - ${e.type} ${e.symbol}${e.signature ? ` ${e.signature}` : ""}`).join("\n")}
771
+
772
+ ================================================================================
773
+ METADATA: WHO IMPORTS WHOM
774
+ ================================================================================
775
+ ${Object.entries(exportGraph).map(([file, importers]) => `${file} is imported by:
776
+ ${importers.map((i) => ` \u2190 ${i}`).join("\n")}`).join("\n\n")}
777
+
778
+ ================================================================================
779
+ METADATA: COMPLEXITY SCORES
780
+ ================================================================================
781
+ ${complexityScores.map((c) => `${c.file}: ${c.score} (${c.level})`).join("\n")}
782
+
783
+ ================================================================================
784
+ METADATA: TEST COVERAGE MAP
785
+ ================================================================================
786
+ ${Object.entries(testFileMap).length > 0 ? Object.entries(testFileMap).map(([src, tests]) => `${src} -> ${tests.join(", ")}`).join("\n") : "(no test file mappings found)"}
787
+ ${baseResult.files.filter(
788
+ (f) => /\.(tsx?|jsx?)$/.test(f) && !f.includes(".test.") && !f.includes(".spec.") && !f.includes("__tests__") && !testFileMap[f]
789
+ ).map((f) => `${f} -> (no tests)`).join("\n")}
790
+ ${recentChanges !== null ? `
791
+
792
+ ================================================================================
793
+ METADATA: RECENT CHANGES (last 7 days)
794
+ ================================================================================
795
+ ${recentChanges.length > 0 ? recentChanges.map((c) => `${c.file}: ${c.commits} commit${c.commits !== 1 ? "s" : ""}, ${c.authors} author${c.authors !== 1 ? "s" : ""}`).join("\n") : "(no changes in the last 7 days)"}` : ""}
796
+ `;
797
+ const existingContent = readFileSync3(outputPath, "utf-8");
798
+ writeFileSync3(outputPath, existingContent + metadataSection);
799
+ return {
800
+ ...baseResult,
801
+ metadata: {
802
+ imports: allImports,
803
+ exports: allExports,
804
+ fileIndex,
805
+ importGraph,
806
+ exportGraph,
807
+ symbolIndex,
808
+ complexityScores,
809
+ testFileMap,
810
+ recentChanges
811
+ }
812
+ };
813
+ }
814
+
206
815
  // src/core/engine.ts
207
- import { readFileSync as readFileSync3 } from "fs";
816
+ init_esm_shims();
817
+ import { readFileSync as readFileSync4 } from "fs";
208
818
 
209
819
  // src/core/prompts.ts
820
+ init_esm_shims();
210
821
  var NUCLEUS_COMMANDS = `
211
822
  COMMANDS (output ONE per turn):
212
823
  (grep "pattern") - Find lines matching regex
@@ -562,7 +1173,7 @@ async function analyze(provider2, documentPath, query, options = {}) {
562
1173
  onProgress
563
1174
  } = options;
564
1175
  const dynamicLimit = Math.min(getTurnLimit(query), maxTurns);
565
- const content = readFileSync3(documentPath, "utf-8");
1176
+ const content = readFileSync4(documentPath, "utf-8");
566
1177
  const fileCount = (content.match(/^FILE:/gm) || []).length;
567
1178
  const lineCount = content.split("\n").length;
568
1179
  const bindings = /* @__PURE__ */ new Map();
@@ -676,7 +1287,7 @@ ${truncatedResult}`;
676
1287
  };
677
1288
  }
678
1289
  function searchDocument(documentPath, pattern, options = {}) {
679
- const content = readFileSync3(documentPath, "utf-8");
1290
+ const content = readFileSync4(documentPath, "utf-8");
680
1291
  const flags = options.caseInsensitive ? "gi" : "g";
681
1292
  const regex = new RegExp(pattern, flags);
682
1293
  const lines = content.split("\n");
@@ -703,7 +1314,11 @@ function searchDocument(documentPath, pattern, options = {}) {
703
1314
  return matches;
704
1315
  }
705
1316
 
1317
+ // src/providers/index.ts
1318
+ init_esm_shims();
1319
+
706
1320
  // src/providers/openai-compatible.ts
1321
+ init_esm_shims();
707
1322
  var OpenAICompatibleProvider = class {
708
1323
  name;
709
1324
  config;
@@ -787,6 +1402,7 @@ function createDeepSeekProvider(config2) {
787
1402
  }
788
1403
 
789
1404
  // src/providers/ollama.ts
1405
+ init_esm_shims();
790
1406
  var OllamaProvider = class {
791
1407
  name = "Ollama";
792
1408
  config;
@@ -865,6 +1481,7 @@ function createOllamaProvider(config2) {
865
1481
  }
866
1482
 
867
1483
  // src/providers/anthropic.ts
1484
+ init_esm_shims();
868
1485
  var AnthropicProvider = class {
869
1486
  name = "Anthropic";
870
1487
  config;
@@ -960,10 +1577,150 @@ function createProviderByType(type, config2) {
960
1577
  }
961
1578
 
962
1579
  // src/mcp.ts
963
- import { existsSync as existsSync3, statSync as statSync2, mkdtempSync, unlinkSync, readFileSync as readFileSync4 } from "fs";
1580
+ import { existsSync as existsSync5, statSync as statSync2, mkdtempSync, unlinkSync, readFileSync as readFileSync6 } from "fs";
964
1581
  import { tmpdir } from "os";
965
- import { join as join3, resolve } from "path";
1582
+ import { join as join4, resolve } from "path";
1583
+ var DEFAULT_FIND_FILES_LIMIT = 100;
1584
+ var MAX_FIND_FILES_LIMIT = 500;
1585
+ var DEFAULT_SEARCH_RESULTS = 50;
1586
+ var MAX_SEARCH_RESULTS = 200;
1587
+ var MAX_PATTERN_LENGTH = 500;
1588
+ var MAX_WILDCARDS = 20;
1589
+ var WORKER_URL = process.env.ARGUS_WORKER_URL || "http://localhost:37778";
1590
+ var workerAvailable = false;
1591
+ async function checkWorkerHealth() {
1592
+ try {
1593
+ const controller = new AbortController();
1594
+ const timeout = setTimeout(() => controller.abort(), 1e3);
1595
+ const response = await fetch(`${WORKER_URL}/health`, {
1596
+ signal: controller.signal
1597
+ });
1598
+ clearTimeout(timeout);
1599
+ return response.ok;
1600
+ } catch {
1601
+ return false;
1602
+ }
1603
+ }
1604
+ checkWorkerHealth().then((available) => {
1605
+ workerAvailable = available;
1606
+ });
966
1607
  var TOOLS = [
1608
+ {
1609
+ name: "__ARGUS_GUIDE",
1610
+ description: `ARGUS CODEBASE INTELLIGENCE - Follow this workflow for codebase questions:
1611
+
1612
+ STEP 1: Check for snapshot
1613
+ - Look for .argus/snapshot.txt in the project root
1614
+ - If missing, use create_snapshot first (saves to .argus/snapshot.txt)
1615
+ - Snapshots survive context compaction - create once, use forever
1616
+
1617
+ STEP 2: Use zero-cost tools first (NO AI tokens consumed)
1618
+ - search_codebase: Fast regex search, returns file:line:content
1619
+ - find_symbol: Locate where functions/types/classes are exported
1620
+ - find_importers: Find all files that depend on a given file
1621
+ - get_file_deps: See what modules a file imports
1622
+ - get_context: Get lines of code around a specific location
1623
+
1624
+ STEP 3: Use AI analysis only when zero-cost tools are insufficient
1625
+ - analyze_codebase: Deep reasoning across entire codebase (~500 tokens)
1626
+ - Use for architecture questions, pattern finding, complex relationships
1627
+
1628
+ EFFICIENCY MATRIX:
1629
+ | Question Type | Tool | Token Cost |
1630
+ |---------------------------|-------------------------|------------|
1631
+ | "Where is X defined?" | find_symbol | 0 |
1632
+ | "What uses this file?" | find_importers | 0 |
1633
+ | "Find all TODO comments" | search_codebase | 0 |
1634
+ | "Show context around L42" | get_context | 0 |
1635
+ | "How does auth work?" | analyze_codebase | ~500 |
1636
+
1637
+ SNAPSHOT FRESHNESS:
1638
+ - Snapshots don't auto-update (yet)
1639
+ - Re-run create_snapshot if files have changed significantly
1640
+ - Check snapshot timestamp in header to assess freshness`,
1641
+ inputSchema: {
1642
+ type: "object",
1643
+ properties: {},
1644
+ required: []
1645
+ }
1646
+ },
1647
+ {
1648
+ name: "get_context",
1649
+ description: `Get lines of code around a specific location. Zero AI cost.
1650
+
1651
+ Use AFTER search_codebase when you need more context around a match.
1652
+ Much more efficient than reading the entire file.
1653
+
1654
+ Example workflow:
1655
+ 1. search_codebase("handleAuth") -> finds src/auth.ts:42
1656
+ 2. get_context(file="src/auth.ts", line=42, before=10, after=20)
1657
+
1658
+ Returns the surrounding code with proper line numbers.`,
1659
+ inputSchema: {
1660
+ type: "object",
1661
+ properties: {
1662
+ path: {
1663
+ type: "string",
1664
+ description: "Path to the snapshot file (.argus/snapshot.txt)"
1665
+ },
1666
+ file: {
1667
+ type: "string",
1668
+ description: 'File path within the snapshot (e.g., "src/auth.ts")'
1669
+ },
1670
+ line: {
1671
+ type: "number",
1672
+ description: "Center line number to get context around"
1673
+ },
1674
+ before: {
1675
+ type: "number",
1676
+ description: "Lines to include before the target line (default: 10)"
1677
+ },
1678
+ after: {
1679
+ type: "number",
1680
+ description: "Lines to include after the target line (default: 10)"
1681
+ }
1682
+ },
1683
+ required: ["path", "file", "line"]
1684
+ }
1685
+ },
1686
+ {
1687
+ name: "find_files",
1688
+ description: `Find files matching a glob pattern. Ultra-low cost (~10 tokens per result).
1689
+
1690
+ Use for:
1691
+ - "What files are in src/components?"
1692
+ - "Find all test files"
1693
+ - "List files named auth*"
1694
+
1695
+ Patterns:
1696
+ - * matches any characters except /
1697
+ - ** matches any characters including /
1698
+ - ? matches single character
1699
+
1700
+ Returns file paths only - use get_context or search_codebase for content.`,
1701
+ inputSchema: {
1702
+ type: "object",
1703
+ properties: {
1704
+ path: {
1705
+ type: "string",
1706
+ description: "Path to the snapshot file (.argus/snapshot.txt)"
1707
+ },
1708
+ pattern: {
1709
+ type: "string",
1710
+ description: 'Glob pattern (e.g., "*.test.ts", "src/**/*.tsx", "**/*auth*")'
1711
+ },
1712
+ caseInsensitive: {
1713
+ type: "boolean",
1714
+ description: "Case-insensitive matching (default: true)"
1715
+ },
1716
+ limit: {
1717
+ type: "number",
1718
+ description: "Maximum results (default: 100, max: 500)"
1719
+ }
1720
+ },
1721
+ required: ["path", "pattern"]
1722
+ }
1723
+ },
967
1724
  {
968
1725
  name: "find_importers",
969
1726
  description: `Find all files that import a given file or module. Zero AI cost.
@@ -973,7 +1730,7 @@ Use when you need to know:
973
1730
  - Who uses this function/component?
974
1731
  - Impact analysis before refactoring
975
1732
 
976
- Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
1733
+ Snapshots are enhanced by default and include this metadata.`,
977
1734
  inputSchema: {
978
1735
  type: "object",
979
1736
  properties: {
@@ -998,7 +1755,7 @@ Use when you need to know:
998
1755
  - Which file exports this component?
999
1756
  - Find the source of a type
1000
1757
 
1001
- Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
1758
+ Snapshots are enhanced by default and include this metadata.`,
1002
1759
  inputSchema: {
1003
1760
  type: "object",
1004
1761
  properties: {
@@ -1023,7 +1780,7 @@ Use when you need to understand:
1023
1780
  - What modules need to be loaded?
1024
1781
  - Trace the dependency chain
1025
1782
 
1026
- Requires an enhanced snapshot with metadata (created with --enhanced flag).`,
1783
+ Snapshots are enhanced by default and include this metadata.`,
1027
1784
  inputSchema: {
1028
1785
  type: "object",
1029
1786
  properties: {
@@ -1100,11 +1857,19 @@ Returns matching lines with line numbers - much faster than grep across many fil
1100
1857
  },
1101
1858
  caseInsensitive: {
1102
1859
  type: "boolean",
1103
- description: "Whether to ignore case (default: false)"
1860
+ description: "Whether to ignore case (default: true)"
1104
1861
  },
1105
1862
  maxResults: {
1106
1863
  type: "number",
1107
1864
  description: "Maximum results to return (default: 50)"
1865
+ },
1866
+ offset: {
1867
+ type: "number",
1868
+ description: "Skip first N results for pagination (default: 0)"
1869
+ },
1870
+ contextChars: {
1871
+ type: "number",
1872
+ description: "Characters of context around match (default: 0 = full line)"
1108
1873
  }
1109
1874
  },
1110
1875
  required: ["path", "pattern"]
@@ -1112,9 +1877,10 @@ Returns matching lines with line numbers - much faster than grep across many fil
1112
1877
  },
1113
1878
  {
1114
1879
  name: "create_snapshot",
1115
- description: `Create a codebase snapshot for analysis. Run this ONCE per project, then use the snapshot for all queries.
1880
+ description: `Create an enhanced codebase snapshot for analysis. Run this ONCE per project, then use the snapshot for all queries.
1116
1881
 
1117
1882
  The snapshot compiles all source files into a single optimized file that survives context compaction.
1883
+ Includes structural metadata (import graph, exports index) for zero-cost dependency queries.
1118
1884
  Store at .argus/snapshot.txt so other tools can find it.
1119
1885
 
1120
1886
  Run this when:
@@ -1140,6 +1906,38 @@ Run this when:
1140
1906
  },
1141
1907
  required: ["path"]
1142
1908
  }
1909
+ },
1910
+ {
1911
+ name: "semantic_search",
1912
+ description: `Search code using natural language. Uses FTS5 full-text search.
1913
+
1914
+ More flexible than regex search - finds related concepts and partial matches.
1915
+
1916
+ Examples:
1917
+ - "authentication middleware"
1918
+ - "database connection"
1919
+ - "error handling"
1920
+
1921
+ Returns symbols (functions, classes, types) with snippets of their content.
1922
+ Requires an index - will auto-create from snapshot on first use.`,
1923
+ inputSchema: {
1924
+ type: "object",
1925
+ properties: {
1926
+ path: {
1927
+ type: "string",
1928
+ description: "Path to the project directory (must have .argus/snapshot.txt)"
1929
+ },
1930
+ query: {
1931
+ type: "string",
1932
+ description: "Natural language query or code terms"
1933
+ },
1934
+ limit: {
1935
+ type: "number",
1936
+ description: "Maximum results (default: 20)"
1937
+ }
1938
+ },
1939
+ required: ["path", "query"]
1940
+ }
1143
1941
  }
1144
1942
  ];
1145
1943
  var config;
@@ -1197,15 +1995,73 @@ function parseSnapshotMetadata(content) {
1197
1995
  }
1198
1996
  return { importGraph, exportGraph, symbolIndex, exports };
1199
1997
  }
1998
+ async function searchWithWorker(snapshotPath, pattern, options) {
1999
+ if (!workerAvailable) return null;
2000
+ try {
2001
+ await fetch(`${WORKER_URL}/snapshot/load`, {
2002
+ method: "POST",
2003
+ headers: { "Content-Type": "application/json" },
2004
+ body: JSON.stringify({ path: snapshotPath })
2005
+ });
2006
+ const response = await fetch(`${WORKER_URL}/search`, {
2007
+ method: "POST",
2008
+ headers: { "Content-Type": "application/json" },
2009
+ body: JSON.stringify({ path: snapshotPath, pattern, options })
2010
+ });
2011
+ if (response.ok) {
2012
+ return await response.json();
2013
+ }
2014
+ } catch {
2015
+ }
2016
+ return null;
2017
+ }
1200
2018
  async function handleToolCall(name, args) {
1201
2019
  switch (name) {
2020
+ case "find_files": {
2021
+ const snapshotPath = resolve(args.path);
2022
+ const pattern = args.pattern;
2023
+ const caseInsensitive = args.caseInsensitive !== false;
2024
+ const limit = Math.min(args.limit || DEFAULT_FIND_FILES_LIMIT, MAX_FIND_FILES_LIMIT);
2025
+ if (!pattern || pattern.trim() === "") {
2026
+ throw new Error("Pattern cannot be empty");
2027
+ }
2028
+ if (pattern.length > MAX_PATTERN_LENGTH) {
2029
+ throw new Error(`Pattern too long (max ${MAX_PATTERN_LENGTH} characters)`);
2030
+ }
2031
+ const starCount = (pattern.match(/\*/g) || []).length;
2032
+ if (starCount > MAX_WILDCARDS) {
2033
+ throw new Error(`Too many wildcards in pattern (max ${MAX_WILDCARDS})`);
2034
+ }
2035
+ if (!existsSync5(snapshotPath)) {
2036
+ throw new Error(`Snapshot not found: ${snapshotPath}. Run 'argus snapshot' to create one.`);
2037
+ }
2038
+ const content = readFileSync6(snapshotPath, "utf-8");
2039
+ const fileRegex = /^FILE: \.\/(.+)$/gm;
2040
+ const files = [];
2041
+ let match;
2042
+ while ((match = fileRegex.exec(content)) !== null) {
2043
+ files.push(match[1]);
2044
+ }
2045
+ let regexPattern = pattern.replace(/[.+^${}()|[\]\\-]/g, "\\$&").replace(/\*\*/g, "<<<GLOBSTAR>>>").replace(/\*/g, "[^/]*?").replace(/<<<GLOBSTAR>>>/g, ".*?").replace(/\?/g, ".");
2046
+ const flags = caseInsensitive ? "i" : "";
2047
+ const regex = new RegExp(`^${regexPattern}$`, flags);
2048
+ const matching = files.filter((f) => regex.test(f));
2049
+ const limited = matching.slice(0, limit).sort();
2050
+ return {
2051
+ pattern,
2052
+ files: limited,
2053
+ count: limited.length,
2054
+ totalMatching: matching.length,
2055
+ hasMore: matching.length > limit
2056
+ };
2057
+ }
1202
2058
  case "find_importers": {
1203
2059
  const path = resolve(args.path);
1204
2060
  const target = args.target;
1205
- if (!existsSync3(path)) {
2061
+ if (!existsSync5(path)) {
1206
2062
  throw new Error(`File not found: ${path}`);
1207
2063
  }
1208
- const content = readFileSync4(path, "utf-8");
2064
+ const content = readFileSync6(path, "utf-8");
1209
2065
  const metadata = parseSnapshotMetadata(content);
1210
2066
  if (!metadata) {
1211
2067
  throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
@@ -1236,10 +2092,10 @@ async function handleToolCall(name, args) {
1236
2092
  case "find_symbol": {
1237
2093
  const path = resolve(args.path);
1238
2094
  const symbol = args.symbol;
1239
- if (!existsSync3(path)) {
2095
+ if (!existsSync5(path)) {
1240
2096
  throw new Error(`File not found: ${path}`);
1241
2097
  }
1242
- const content = readFileSync4(path, "utf-8");
2098
+ const content = readFileSync6(path, "utf-8");
1243
2099
  const metadata = parseSnapshotMetadata(content);
1244
2100
  if (!metadata) {
1245
2101
  throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
@@ -1256,10 +2112,10 @@ async function handleToolCall(name, args) {
1256
2112
  case "get_file_deps": {
1257
2113
  const path = resolve(args.path);
1258
2114
  const file = args.file;
1259
- if (!existsSync3(path)) {
2115
+ if (!existsSync5(path)) {
1260
2116
  throw new Error(`File not found: ${path}`);
1261
2117
  }
1262
- const content = readFileSync4(path, "utf-8");
2118
+ const content = readFileSync6(path, "utf-8");
1263
2119
  const metadata = parseSnapshotMetadata(content);
1264
2120
  if (!metadata) {
1265
2121
  throw new Error("This snapshot does not have metadata. Create with: argus snapshot --enhanced");
@@ -1286,16 +2142,16 @@ async function handleToolCall(name, args) {
1286
2142
  const path = resolve(args.path);
1287
2143
  const query = args.query;
1288
2144
  const maxTurns = args.maxTurns || 15;
1289
- if (!existsSync3(path)) {
2145
+ if (!existsSync5(path)) {
1290
2146
  throw new Error(`Path not found: ${path}`);
1291
2147
  }
1292
2148
  let snapshotPath = path;
1293
2149
  let tempSnapshot = false;
1294
2150
  const stats = statSync2(path);
1295
2151
  if (stats.isDirectory()) {
1296
- const tempDir = mkdtempSync(join3(tmpdir(), "argus-"));
1297
- snapshotPath = join3(tempDir, "snapshot.txt");
1298
- const result = createSnapshot(path, snapshotPath, {
2152
+ const tempDir = mkdtempSync(join4(tmpdir(), "argus-"));
2153
+ snapshotPath = join4(tempDir, "snapshot.txt");
2154
+ createEnhancedSnapshot(path, snapshotPath, {
1299
2155
  extensions: config.defaults.snapshotExtensions,
1300
2156
  excludePatterns: config.defaults.excludePatterns
1301
2157
  });
@@ -1310,7 +2166,7 @@ async function handleToolCall(name, args) {
1310
2166
  commands: result.commands
1311
2167
  };
1312
2168
  } finally {
1313
- if (tempSnapshot && existsSync3(snapshotPath)) {
2169
+ if (tempSnapshot && existsSync5(snapshotPath)) {
1314
2170
  unlinkSync(snapshotPath);
1315
2171
  }
1316
2172
  }
@@ -1318,29 +2174,159 @@ async function handleToolCall(name, args) {
1318
2174
  case "search_codebase": {
1319
2175
  const path = resolve(args.path);
1320
2176
  const pattern = args.pattern;
1321
- const caseInsensitive = args.caseInsensitive || false;
1322
- const maxResults = args.maxResults || 50;
1323
- if (!existsSync3(path)) {
1324
- throw new Error(`File not found: ${path}`);
2177
+ const caseInsensitive = args.caseInsensitive !== false;
2178
+ const maxResults = Math.min(args.maxResults || DEFAULT_SEARCH_RESULTS, MAX_SEARCH_RESULTS);
2179
+ const offset = args.offset || 0;
2180
+ const contextChars = args.contextChars || 0;
2181
+ if (!pattern || pattern.trim() === "") {
2182
+ throw new Error("Pattern cannot be empty");
1325
2183
  }
1326
- const matches = searchDocument(path, pattern, { caseInsensitive, maxResults });
1327
- return {
1328
- count: matches.length,
1329
- matches: matches.map((m) => ({
2184
+ if (offset < 0 || !Number.isInteger(offset)) {
2185
+ throw new Error("Offset must be a non-negative integer");
2186
+ }
2187
+ if (contextChars < 0) {
2188
+ throw new Error("contextChars must be non-negative");
2189
+ }
2190
+ if (!existsSync5(path)) {
2191
+ throw new Error(`Snapshot not found: ${path}. Run 'argus snapshot' to create one.`);
2192
+ }
2193
+ const fetchLimit = offset + maxResults + 1;
2194
+ if (workerAvailable) {
2195
+ const workerResult = await searchWithWorker(path, pattern, {
2196
+ caseInsensitive,
2197
+ maxResults: fetchLimit,
2198
+ offset: 0
2199
+ });
2200
+ if (workerResult) {
2201
+ const hasMore2 = workerResult.matches.length === fetchLimit;
2202
+ const pageMatches2 = workerResult.matches.slice(offset, offset + maxResults);
2203
+ const formattedMatches2 = pageMatches2.map((m) => {
2204
+ let displayLine = m.line;
2205
+ if (contextChars > 0 && displayLine.length > contextChars) {
2206
+ const matchStart = displayLine.indexOf(m.match);
2207
+ if (matchStart !== -1) {
2208
+ const matchEnd = matchStart + m.match.length;
2209
+ const matchCenter = Math.floor((matchStart + matchEnd) / 2);
2210
+ const halfContext = Math.floor(contextChars / 2);
2211
+ let start = Math.max(0, matchCenter - halfContext);
2212
+ let end = start + contextChars;
2213
+ if (end > displayLine.length) {
2214
+ end = displayLine.length;
2215
+ start = Math.max(0, end - contextChars);
2216
+ }
2217
+ const prefix = start > 0 ? "..." : "";
2218
+ const suffix = end < displayLine.length ? "..." : "";
2219
+ displayLine = prefix + displayLine.slice(start, end) + suffix;
2220
+ }
2221
+ }
2222
+ return { lineNum: m.lineNum, line: displayLine, match: m.match };
2223
+ });
2224
+ const response2 = {
2225
+ count: formattedMatches2.length,
2226
+ matches: formattedMatches2,
2227
+ _source: "worker"
2228
+ // Debug: show source
2229
+ };
2230
+ if (offset > 0 || hasMore2) {
2231
+ response2.offset = offset;
2232
+ response2.hasMore = hasMore2;
2233
+ response2.totalFound = hasMore2 ? `${offset + maxResults}+` : String(offset + formattedMatches2.length);
2234
+ if (hasMore2) {
2235
+ response2.nextOffset = offset + maxResults;
2236
+ }
2237
+ }
2238
+ return response2;
2239
+ }
2240
+ }
2241
+ const allMatches = searchDocument(path, pattern, {
2242
+ caseInsensitive,
2243
+ maxResults: fetchLimit
2244
+ });
2245
+ const hasMore = allMatches.length === fetchLimit;
2246
+ const pageMatches = allMatches.slice(offset, offset + maxResults);
2247
+ const formattedMatches = pageMatches.map((m) => {
2248
+ let displayLine = m.line.trim();
2249
+ if (contextChars > 0 && displayLine.length > contextChars) {
2250
+ const matchStart = displayLine.indexOf(m.match);
2251
+ if (matchStart !== -1) {
2252
+ const matchEnd = matchStart + m.match.length;
2253
+ const matchCenter = Math.floor((matchStart + matchEnd) / 2);
2254
+ const halfContext = Math.floor(contextChars / 2);
2255
+ let start = Math.max(0, matchCenter - halfContext);
2256
+ let end = start + contextChars;
2257
+ if (end > displayLine.length) {
2258
+ end = displayLine.length;
2259
+ start = Math.max(0, end - contextChars);
2260
+ }
2261
+ const prefix = start > 0 ? "..." : "";
2262
+ const suffix = end < displayLine.length ? "..." : "";
2263
+ displayLine = prefix + displayLine.slice(start, end) + suffix;
2264
+ }
2265
+ }
2266
+ return {
1330
2267
  lineNum: m.lineNum,
1331
- line: m.line.trim(),
2268
+ line: displayLine,
1332
2269
  match: m.match
1333
- }))
2270
+ };
2271
+ });
2272
+ const response = {
2273
+ count: formattedMatches.length,
2274
+ matches: formattedMatches
1334
2275
  };
2276
+ if (offset > 0 || hasMore) {
2277
+ response.offset = offset;
2278
+ response.hasMore = hasMore;
2279
+ response.totalFound = hasMore ? `${offset + maxResults}+` : String(offset + formattedMatches.length);
2280
+ if (hasMore) {
2281
+ response.nextOffset = offset + maxResults;
2282
+ }
2283
+ }
2284
+ return response;
2285
+ }
2286
+ case "semantic_search": {
2287
+ const projectPath = resolve(args.path);
2288
+ const query = args.query;
2289
+ const limit = args.limit || 20;
2290
+ if (!query || query.trim() === "") {
2291
+ throw new Error("Query cannot be empty");
2292
+ }
2293
+ const snapshotPath = join4(projectPath, ".argus", "snapshot.txt");
2294
+ const indexPath = join4(projectPath, ".argus", "search.db");
2295
+ if (!existsSync5(snapshotPath)) {
2296
+ throw new Error(`Snapshot not found: ${snapshotPath}. Run 'argus snapshot' first.`);
2297
+ }
2298
+ const { SemanticIndex: SemanticIndex2 } = await Promise.resolve().then(() => (init_semantic_search(), semantic_search_exports));
2299
+ const index = new SemanticIndex2(indexPath);
2300
+ try {
2301
+ const stats = index.getStats();
2302
+ const snapshotMtime = statSync2(snapshotPath).mtimeMs;
2303
+ const needsReindex = !stats.lastIndexed || new Date(stats.lastIndexed).getTime() < snapshotMtime || stats.snapshotPath !== snapshotPath;
2304
+ if (needsReindex) {
2305
+ index.indexFromSnapshot(snapshotPath);
2306
+ }
2307
+ const results = index.search(query, limit);
2308
+ return {
2309
+ query,
2310
+ count: results.length,
2311
+ results: results.map((r) => ({
2312
+ file: r.file,
2313
+ symbol: r.symbol,
2314
+ type: r.type,
2315
+ snippet: r.content.split("\n").slice(0, 5).join("\n")
2316
+ }))
2317
+ };
2318
+ } finally {
2319
+ index.close();
2320
+ }
1335
2321
  }
1336
2322
  case "create_snapshot": {
1337
2323
  const path = resolve(args.path);
1338
- const outputPath = args.outputPath ? resolve(args.outputPath) : join3(tmpdir(), `argus-snapshot-${Date.now()}.txt`);
2324
+ const outputPath = args.outputPath ? resolve(args.outputPath) : join4(tmpdir(), `argus-snapshot-${Date.now()}.txt`);
1339
2325
  const extensions = args.extensions || config.defaults.snapshotExtensions;
1340
- if (!existsSync3(path)) {
2326
+ if (!existsSync5(path)) {
1341
2327
  throw new Error(`Path not found: ${path}`);
1342
2328
  }
1343
- const result = createSnapshot(path, outputPath, {
2329
+ const result = createEnhancedSnapshot(path, outputPath, {
1344
2330
  extensions,
1345
2331
  excludePatterns: config.defaults.excludePatterns
1346
2332
  });
@@ -1348,7 +2334,66 @@ async function handleToolCall(name, args) {
1348
2334
  outputPath: result.outputPath,
1349
2335
  fileCount: result.fileCount,
1350
2336
  totalLines: result.totalLines,
1351
- totalSize: result.totalSize
2337
+ totalSize: result.totalSize,
2338
+ enhanced: true,
2339
+ metadata: "metadata" in result ? {
2340
+ imports: result.metadata.imports.length,
2341
+ exports: result.metadata.exports.length,
2342
+ symbols: Object.keys(result.metadata.symbolIndex).length
2343
+ } : void 0
2344
+ };
2345
+ }
2346
+ case "__ARGUS_GUIDE": {
2347
+ return {
2348
+ message: "This is a documentation tool. Read the description for Argus usage patterns.",
2349
+ tools: TOOLS.map((t) => ({ name: t.name, purpose: t.description.split("\n")[0] })),
2350
+ recommendation: "Start with search_codebase for most queries. Use analyze_codebase only for complex architecture questions."
2351
+ };
2352
+ }
2353
+ case "get_context": {
2354
+ const snapshotPath = resolve(args.path);
2355
+ const targetFile = args.file;
2356
+ const targetLine = args.line;
2357
+ const beforeLines = args.before || 10;
2358
+ const afterLines = args.after || 10;
2359
+ if (!existsSync5(snapshotPath)) {
2360
+ throw new Error(`Snapshot not found: ${snapshotPath}`);
2361
+ }
2362
+ const content = readFileSync6(snapshotPath, "utf-8");
2363
+ const normalizedTarget = targetFile.replace(/^\.\//, "");
2364
+ const fileMarkerVariants = [
2365
+ `FILE: ./${normalizedTarget}`,
2366
+ `FILE: ${normalizedTarget}`
2367
+ ];
2368
+ let fileStart = -1;
2369
+ for (const marker of fileMarkerVariants) {
2370
+ fileStart = content.indexOf(marker);
2371
+ if (fileStart !== -1) break;
2372
+ }
2373
+ if (fileStart === -1) {
2374
+ throw new Error(`File not found in snapshot: ${targetFile}`);
2375
+ }
2376
+ const nextFileStart = content.indexOf("\nFILE:", fileStart + 1);
2377
+ const metadataStart = content.indexOf("\nMETADATA:", fileStart);
2378
+ const fileEnd = Math.min(
2379
+ nextFileStart === -1 ? Infinity : nextFileStart,
2380
+ metadataStart === -1 ? Infinity : metadataStart
2381
+ );
2382
+ const fileContent = content.slice(fileStart, fileEnd === Infinity ? void 0 : fileEnd);
2383
+ const fileLines = fileContent.split("\n").slice(2);
2384
+ const startLine = Math.max(0, targetLine - beforeLines - 1);
2385
+ const endLine = Math.min(fileLines.length, targetLine + afterLines);
2386
+ const contextLines = fileLines.slice(startLine, endLine).map((line, idx) => {
2387
+ const lineNum = startLine + idx + 1;
2388
+ const marker = lineNum === targetLine ? ">>>" : " ";
2389
+ return `${marker} ${lineNum.toString().padStart(4)}: ${line}`;
2390
+ });
2391
+ return {
2392
+ file: targetFile,
2393
+ targetLine,
2394
+ range: { start: startLine + 1, end: endLine },
2395
+ content: contextLines.join("\n"),
2396
+ totalLines: fileLines.length
1352
2397
  };
1353
2398
  }
1354
2399
  default: