archtracker-mcp 0.2.1 → 0.3.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/index.js CHANGED
@@ -179,12 +179,50 @@ function stripComments(content, style) {
179
179
  return stripRuby(content);
180
180
  case "php":
181
181
  return stripPhp(content);
182
+ default: {
183
+ const _exhaustive = style;
184
+ throw new Error(`Unknown comment style: ${_exhaustive}`);
185
+ }
182
186
  }
183
187
  }
184
188
  function stripCStyle(content) {
185
189
  let result = "";
186
190
  let i = 0;
187
191
  while (i < content.length) {
192
+ if (content[i] === "r" && i + 1 < content.length) {
193
+ let hashes = 0;
194
+ let j = i + 1;
195
+ while (j < content.length && content[j] === "#") {
196
+ hashes++;
197
+ j++;
198
+ }
199
+ if (j < content.length && content[j] === '"') {
200
+ for (let k = i; k <= j; k++) {
201
+ result += " ";
202
+ }
203
+ i = j + 1;
204
+ while (i < content.length) {
205
+ if (content[i] === '"') {
206
+ let matchHashes = 0;
207
+ let m = i + 1;
208
+ while (m < content.length && content[m] === "#" && matchHashes < hashes) {
209
+ matchHashes++;
210
+ m++;
211
+ }
212
+ if (matchHashes === hashes) {
213
+ for (let k = i; k < m; k++) {
214
+ result += " ";
215
+ }
216
+ i = m;
217
+ break;
218
+ }
219
+ }
220
+ result += content[i] === "\n" ? "\n" : " ";
221
+ i++;
222
+ }
223
+ continue;
224
+ }
225
+ }
188
226
  if (content[i] === "/" && content[i + 1] === "/") {
189
227
  while (i < content.length && content[i] !== "\n") {
190
228
  result += " ";
@@ -227,6 +265,24 @@ function stripCStyle(content) {
227
265
  result += content[i];
228
266
  i++;
229
267
  }
268
+ } else if (content[i] === "'") {
269
+ result += content[i];
270
+ i++;
271
+ while (i < content.length && content[i] !== "'") {
272
+ if (content[i] === "\\" && i + 1 < content.length) {
273
+ result += content[i];
274
+ i++;
275
+ result += content[i];
276
+ i++;
277
+ } else {
278
+ result += content[i];
279
+ i++;
280
+ }
281
+ }
282
+ if (i < content.length) {
283
+ result += content[i];
284
+ i++;
285
+ }
230
286
  } else if (content[i] === "`") {
231
287
  result += " ";
232
288
  i++;
@@ -295,31 +351,62 @@ function stripPython(content) {
295
351
  let result = "";
296
352
  let i = 0;
297
353
  while (i < content.length) {
298
- if (content[i] === '"' && content[i + 1] === '"' && content[i + 2] === '"' || content[i] === "'" && content[i + 1] === "'" && content[i + 2] === "'") {
299
- const quote = content[i];
300
- const tripleQuote = quote + quote + quote;
354
+ let prefixLen = 0;
355
+ let isRaw = false;
356
+ if (i < content.length) {
357
+ const c0 = content[i];
358
+ const c1 = i + 1 < content.length ? content[i + 1] : "";
359
+ const c2 = i + 2 < content.length ? content[i + 2] : "";
360
+ if ((c0 === "r" || c0 === "R" || c0 === "b" || c0 === "B" || c0 === "f" || c0 === "F") && (c1 === "r" || c1 === "R" || c1 === "b" || c1 === "B" || c1 === "f" || c1 === "F") && (c2 === '"' || c2 === "'")) {
361
+ const pair = (c0 + c1).toLowerCase();
362
+ if (pair === "rb" || pair === "br" || pair === "fr" || pair === "rf") {
363
+ prefixLen = 2;
364
+ isRaw = pair.includes("r");
365
+ }
366
+ }
367
+ if (prefixLen === 0 && (c0 === "r" || c0 === "R" || c0 === "b" || c0 === "B" || c0 === "f" || c0 === "F") && (c1 === '"' || c1 === "'")) {
368
+ prefixLen = 1;
369
+ isRaw = c0 === "r" || c0 === "R";
370
+ }
371
+ }
372
+ const quoteStart = i + prefixLen;
373
+ if (quoteStart + 2 < content.length && (content[quoteStart] === '"' || content[quoteStart] === "'") && content[quoteStart + 1] === content[quoteStart] && content[quoteStart + 2] === content[quoteStart]) {
374
+ const quote = content[quoteStart];
375
+ for (let k = 0; k < prefixLen; k++) {
376
+ result += " ";
377
+ }
301
378
  result += " ";
302
- i += 3;
379
+ i = quoteStart + 3;
303
380
  while (i < content.length) {
304
381
  if (content[i] === quote && content[i + 1] === quote && content[i + 2] === quote) {
305
382
  result += " ";
306
383
  i += 3;
307
384
  break;
308
385
  }
309
- result += content[i] === "\n" ? "\n" : " ";
310
- i++;
386
+ if (!isRaw && content[i] === "\\" && i + 1 < content.length) {
387
+ result += " ";
388
+ i++;
389
+ result += content[i] === "\n" ? "\n" : " ";
390
+ i++;
391
+ } else {
392
+ result += content[i] === "\n" ? "\n" : " ";
393
+ i++;
394
+ }
311
395
  }
312
- } else if (content[i] === "#") {
396
+ } else if (prefixLen === 0 && content[i] === "#") {
313
397
  while (i < content.length && content[i] !== "\n") {
314
398
  result += " ";
315
399
  i++;
316
400
  }
317
- } else if (content[i] === '"' || content[i] === "'") {
318
- const quote = content[i];
319
- result += content[i];
320
- i++;
401
+ } else if (quoteStart < content.length && (content[quoteStart] === '"' || content[quoteStart] === "'") && (prefixLen > 0 || content[i] === '"' || content[i] === "'")) {
402
+ const quote = content[quoteStart];
403
+ for (let k = i; k < quoteStart; k++) {
404
+ result += content[k];
405
+ }
406
+ result += content[quoteStart];
407
+ i = quoteStart + 1;
321
408
  while (i < content.length && content[i] !== quote) {
322
- if (content[i] === "\\" && i + 1 < content.length) {
409
+ if (!isRaw && content[i] === "\\" && i + 1 < content.length) {
323
410
  result += content[i++];
324
411
  result += content[i++];
325
412
  } else if (content[i] === "\n") {
@@ -346,13 +433,13 @@ function stripRuby(content) {
346
433
  const result = [];
347
434
  let inBlock = false;
348
435
  for (const line of lines) {
349
- if (!inBlock && line.startsWith("=begin")) {
436
+ if (!inBlock && /^=begin(\s|$)/.test(line)) {
350
437
  inBlock = true;
351
438
  result.push(" ".repeat(line.length));
352
439
  continue;
353
440
  }
354
441
  if (inBlock) {
355
- if (line.startsWith("=end")) {
442
+ if (/^=end(\s|$)/.test(line)) {
356
443
  inBlock = false;
357
444
  }
358
445
  result.push(" ".repeat(line.length));
@@ -367,18 +454,8 @@ function stripRuby(content) {
367
454
  } else if (line[i] === '"') {
368
455
  processed += line[i];
369
456
  i++;
370
- while (i < line.length && line[i] !== '"') {
371
- if (line[i] === "\\" && i + 1 < line.length) {
372
- processed += line[i++];
373
- processed += line[i++];
374
- } else {
375
- processed += line[i++];
376
- }
377
- }
378
- if (i < line.length) {
379
- processed += line[i];
380
- i++;
381
- }
457
+ processed = scanRubyDoubleQuotedString(line, i, processed);
458
+ i = scanRubyDoubleQuotedStringIndex(line, i);
382
459
  } else if (line[i] === "'") {
383
460
  processed += line[i];
384
461
  i++;
@@ -403,10 +480,141 @@ function stripRuby(content) {
403
480
  }
404
481
  return result.join("\n");
405
482
  }
483
+ function scanRubyDoubleQuotedString(line, startI, processed) {
484
+ let i = startI;
485
+ while (i < line.length && line[i] !== '"') {
486
+ if (line[i] === "\\" && i + 1 < line.length) {
487
+ processed += line[i++];
488
+ processed += line[i++];
489
+ } else if (line[i] === "#" && i + 1 < line.length && line[i + 1] === "{") {
490
+ processed += line[i++];
491
+ processed += line[i++];
492
+ let depth = 1;
493
+ while (i < line.length && depth > 0) {
494
+ if (line[i] === "{") {
495
+ depth++;
496
+ processed += line[i++];
497
+ } else if (line[i] === "}") {
498
+ depth--;
499
+ processed += line[i++];
500
+ } else if (line[i] === "\\" && i + 1 < line.length) {
501
+ processed += line[i++];
502
+ processed += line[i++];
503
+ } else {
504
+ processed += line[i++];
505
+ }
506
+ }
507
+ } else {
508
+ processed += line[i++];
509
+ }
510
+ }
511
+ if (i < line.length) {
512
+ processed += line[i];
513
+ }
514
+ return processed;
515
+ }
516
+ function scanRubyDoubleQuotedStringIndex(line, startI) {
517
+ let i = startI;
518
+ while (i < line.length && line[i] !== '"') {
519
+ if (line[i] === "\\" && i + 1 < line.length) {
520
+ i += 2;
521
+ } else if (line[i] === "#" && i + 1 < line.length && line[i + 1] === "{") {
522
+ i += 2;
523
+ let depth = 1;
524
+ while (i < line.length && depth > 0) {
525
+ if (line[i] === "{") {
526
+ depth++;
527
+ i++;
528
+ } else if (line[i] === "}") {
529
+ depth--;
530
+ i++;
531
+ } else if (line[i] === "\\" && i + 1 < line.length) {
532
+ i += 2;
533
+ } else {
534
+ i++;
535
+ }
536
+ }
537
+ } else {
538
+ i++;
539
+ }
540
+ }
541
+ if (i < line.length) {
542
+ i++;
543
+ }
544
+ return i;
545
+ }
406
546
  function stripPhp(content) {
407
547
  let result = "";
408
548
  let i = 0;
409
549
  while (i < content.length) {
550
+ if (content[i] === "<" && content[i + 1] === "<" && content[i + 2] === "<") {
551
+ const heredocStart = i;
552
+ let j = i + 3;
553
+ let isNowdoc = false;
554
+ if (j < content.length && content[j] === "'") {
555
+ isNowdoc = true;
556
+ j++;
557
+ }
558
+ const identStart = j;
559
+ while (j < content.length && /[A-Za-z0-9_]/.test(content[j])) {
560
+ j++;
561
+ }
562
+ const identifier = content.slice(identStart, j);
563
+ if (identifier.length > 0) {
564
+ if (isNowdoc && j < content.length && content[j] === "'") {
565
+ j++;
566
+ }
567
+ let validHeredoc = false;
568
+ let lineEnd = j;
569
+ if (lineEnd < content.length && content[lineEnd] === "\n") {
570
+ validHeredoc = true;
571
+ }
572
+ if (validHeredoc) {
573
+ for (let k = heredocStart; k <= lineEnd; k++) {
574
+ if (content[k] === "\n") {
575
+ result += "\n";
576
+ } else {
577
+ result += content[k];
578
+ }
579
+ }
580
+ i = lineEnd + 1;
581
+ let found = false;
582
+ while (i < content.length && !found) {
583
+ const lineStart = i;
584
+ let lineEndIdx = i;
585
+ while (lineEndIdx < content.length && content[lineEndIdx] !== "\n") {
586
+ lineEndIdx++;
587
+ }
588
+ const currentLine = content.slice(lineStart, lineEndIdx);
589
+ const trimmedLine = currentLine.trimStart();
590
+ if (trimmedLine === identifier || trimmedLine === identifier + ";" || trimmedLine === identifier + "," || trimmedLine === identifier + ");" || trimmedLine === identifier + ")" || trimmedLine.startsWith(identifier + ";") || trimmedLine === identifier) {
591
+ for (let k = lineStart; k < lineEndIdx; k++) {
592
+ result += content[k];
593
+ }
594
+ i = lineEndIdx;
595
+ if (i < content.length && content[i] === "\n") {
596
+ result += "\n";
597
+ i++;
598
+ }
599
+ found = true;
600
+ } else {
601
+ for (let k = lineStart; k < lineEndIdx; k++) {
602
+ result += " ";
603
+ }
604
+ i = lineEndIdx;
605
+ if (i < content.length && content[i] === "\n") {
606
+ result += "\n";
607
+ i++;
608
+ }
609
+ }
610
+ }
611
+ continue;
612
+ }
613
+ }
614
+ result += content[i];
615
+ i++;
616
+ continue;
617
+ }
410
618
  if (content[i] === "/" && content[i + 1] === "/" || content[i] === "#") {
411
619
  while (i < content.length && content[i] !== "\n") {
412
620
  result += " ";
@@ -479,6 +687,7 @@ var RegexEngine = class {
479
687
  "\\.archtracker"
480
688
  ].map((p) => new RegExp(p));
481
689
  const projectFiles = await this.collectFiles(
690
+ absRootDir,
482
691
  absRootDir,
483
692
  excludePatterns,
484
693
  options.maxDepth ?? 0
@@ -502,6 +711,7 @@ var RegexEngine = class {
502
711
  try {
503
712
  content = await readFile(filePath, "utf-8");
504
713
  } catch {
714
+ if (files[relSource]) files[relSource].exists = false;
505
715
  continue;
506
716
  }
507
717
  const stripped = stripComments(content, this.config.commentStyle);
@@ -545,7 +755,8 @@ var RegexEngine = class {
545
755
  }
546
756
  const imports = [];
547
757
  for (const pattern of this.config.importPatterns) {
548
- const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
758
+ const flags = pattern.regex.flags.includes("g") ? pattern.regex.flags : pattern.regex.flags + "g";
759
+ const regex = new RegExp(pattern.regex.source, flags);
549
760
  let match;
550
761
  while ((match = regex.exec(content)) !== null) {
551
762
  if (match[1]) {
@@ -555,7 +766,7 @@ var RegexEngine = class {
555
766
  }
556
767
  return imports;
557
768
  }
558
- async collectFiles(dir, excludePatterns, maxDepth, currentDepth = 0) {
769
+ async collectFiles(dir, absRootDir, excludePatterns, maxDepth, currentDepth = 0) {
559
770
  if (maxDepth > 0 && currentDepth >= maxDepth) return [];
560
771
  const results = [];
561
772
  let entries;
@@ -566,7 +777,7 @@ var RegexEngine = class {
566
777
  }
567
778
  for (const entry of entries) {
568
779
  const fullPath = join(dir, entry.name);
569
- const relPath = relative(dir, fullPath);
780
+ const relPath = relative(absRootDir, fullPath);
570
781
  if (excludePatterns.some(
571
782
  (p) => p.test(entry.name) || p.test(relPath) || p.test(fullPath)
572
783
  )) {
@@ -576,6 +787,7 @@ var RegexEngine = class {
576
787
  if (entry.name.startsWith(".")) continue;
577
788
  const sub = await this.collectFiles(
578
789
  fullPath,
790
+ absRootDir,
579
791
  excludePatterns,
580
792
  maxDepth,
581
793
  currentDepth + 1
@@ -608,14 +820,21 @@ var MARKERS = [
608
820
  { file: "pom.xml", language: "java" },
609
821
  { file: "build.gradle", language: "java" },
610
822
  { file: "build.gradle.kts", language: "kotlin" },
823
+ { file: "build.sbt", language: "scala" },
824
+ { file: "build.sc", language: "scala" },
611
825
  { file: "Package.swift", language: "swift" },
612
826
  { file: "Gemfile", language: "ruby" },
613
827
  { file: "composer.json", language: "php" },
828
+ { file: "pubspec.yaml", language: "dart" },
614
829
  { file: "CMakeLists.txt", language: "c-cpp" },
615
830
  { file: "Makefile", language: "c-cpp" },
616
831
  { file: "package.json", language: "javascript" },
617
832
  { file: "tsconfig.json", language: "javascript" }
618
833
  ];
834
+ var EXT_MARKERS = [
835
+ [".sln", "c-sharp"],
836
+ [".csproj", "c-sharp"]
837
+ ];
619
838
  var EXT_MAP = {
620
839
  ".ts": "javascript",
621
840
  ".tsx": "javascript",
@@ -637,7 +856,11 @@ var EXT_MAP = {
637
856
  ".php": "php",
638
857
  ".swift": "swift",
639
858
  ".kt": "kotlin",
640
- ".kts": "kotlin"
859
+ ".kts": "kotlin",
860
+ ".cs": "c-sharp",
861
+ ".dart": "dart",
862
+ ".scala": "scala",
863
+ ".sc": "scala"
641
864
  };
642
865
  async function detectLanguage(rootDir) {
643
866
  for (const marker of MARKERS) {
@@ -649,6 +872,16 @@ async function detectLanguage(rootDir) {
649
872
  } catch {
650
873
  }
651
874
  }
875
+ try {
876
+ const topEntries = await readdir2(rootDir, { withFileTypes: true });
877
+ for (const entry of topEntries) {
878
+ if (!entry.isFile()) continue;
879
+ for (const [ext, lang] of EXT_MARKERS) {
880
+ if (entry.name.endsWith(ext)) return lang;
881
+ }
882
+ }
883
+ } catch {
884
+ }
652
885
  const counts = /* @__PURE__ */ new Map();
653
886
  try {
654
887
  await scanExtensions(rootDir, counts, 2, 0);
@@ -701,10 +934,27 @@ var python = {
701
934
  commentStyle: "python",
702
935
  importPatterns: [
703
936
  // from package.module import something
704
- { regex: /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm },
705
- // import package.module
706
- { regex: /^import\s+([\w.]+)/gm }
937
+ { regex: /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm }
938
+ // import package.module (handled by extractImports for multi-module case)
707
939
  ],
940
+ // Bug #1 fix: custom extractImports to handle `import a, b, c`
941
+ extractImports(content) {
942
+ const imports = [];
943
+ const fromRegex = /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm;
944
+ let match;
945
+ while ((match = fromRegex.exec(content)) !== null) {
946
+ imports.push(match[1]);
947
+ }
948
+ const importRegex = /^import\s+([\w.]+(?:\s*,\s*[\w.]+)*)/gm;
949
+ while ((match = importRegex.exec(content)) !== null) {
950
+ const modules = match[1].split(",");
951
+ for (const mod of modules) {
952
+ const trimmed = mod.trim();
953
+ if (trimmed) imports.push(trimmed);
954
+ }
955
+ }
956
+ return imports;
957
+ },
708
958
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
709
959
  if (importPath.startsWith(".")) {
710
960
  const dots = importPath.match(/^\.+/)?.[0].length ?? 1;
@@ -741,6 +991,16 @@ var rust = {
741
991
  const body = match[1].trim();
742
992
  extractRustUsePaths(body, "", imports);
743
993
  }
994
+ const useSuperRegex = /\buse\s+super::([\s\S]*?);/gm;
995
+ while ((match = useSuperRegex.exec(content)) !== null) {
996
+ const body = match[1].trim();
997
+ extractRustUsePaths(body, "", imports, "super");
998
+ }
999
+ const useSelfRegex = /\buse\s+self::([\s\S]*?);/gm;
1000
+ while ((match = useSelfRegex.exec(content)) !== null) {
1001
+ const body = match[1].trim();
1002
+ extractRustUsePaths(body, "", imports, "self");
1003
+ }
744
1004
  return imports;
745
1005
  },
746
1006
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
@@ -753,6 +1013,30 @@ var rust = {
753
1013
  if (projectFiles.has(asDir)) return asDir;
754
1014
  return null;
755
1015
  }
1016
+ if (importPath.startsWith("super::")) {
1017
+ const parentDir = dirname(dirname(sourceFile));
1018
+ const segments2 = importPath.slice("super::".length).split("::");
1019
+ for (let i = segments2.length; i > 0; i--) {
1020
+ const path = segments2.slice(0, i).join("/");
1021
+ const asFile = join3(parentDir, path + ".rs");
1022
+ if (projectFiles.has(asFile)) return asFile;
1023
+ const asDir = join3(parentDir, path, "mod.rs");
1024
+ if (projectFiles.has(asDir)) return asDir;
1025
+ }
1026
+ return null;
1027
+ }
1028
+ if (importPath.startsWith("self::")) {
1029
+ const selfDir = dirname(sourceFile);
1030
+ const segments2 = importPath.slice("self::".length).split("::");
1031
+ for (let i = segments2.length; i > 0; i--) {
1032
+ const path = segments2.slice(0, i).join("/");
1033
+ const asFile = join3(selfDir, path + ".rs");
1034
+ if (projectFiles.has(asFile)) return asFile;
1035
+ const asDir = join3(selfDir, path, "mod.rs");
1036
+ if (projectFiles.has(asDir)) return asDir;
1037
+ }
1038
+ return null;
1039
+ }
756
1040
  const segments = importPath.split("::");
757
1041
  for (let i = segments.length; i > 0; i--) {
758
1042
  const path = segments.slice(0, i).join("/");
@@ -765,11 +1049,14 @@ var rust = {
765
1049
  },
766
1050
  defaultExclude: ["target"]
767
1051
  };
768
- function extractRustUsePaths(body, prefix, results) {
1052
+ function extractRustUsePaths(body, prefix, results, rootPrefix) {
769
1053
  const trimmed = body.trim();
770
1054
  const braceStart = trimmed.indexOf("{");
771
1055
  if (braceStart === -1) {
772
- const path = prefix ? `${prefix}::${trimmed}` : trimmed;
1056
+ let path = prefix ? `${prefix}::${trimmed}` : trimmed;
1057
+ if (rootPrefix) {
1058
+ path = `${rootPrefix}::${path}`;
1059
+ }
773
1060
  if (path && !path.includes("{")) {
774
1061
  results.push(path);
775
1062
  }
@@ -798,9 +1085,10 @@ function extractRustUsePaths(body, prefix, results) {
798
1085
  for (const item of items) {
799
1086
  const cleaned = item.trim();
800
1087
  if (cleaned === "self") {
801
- results.push(fullPrefix);
1088
+ const selfPath = rootPrefix ? `${rootPrefix}::${fullPrefix}` : fullPrefix;
1089
+ results.push(selfPath);
802
1090
  } else if (cleaned) {
803
- extractRustUsePaths(cleaned, fullPrefix, results);
1091
+ extractRustUsePaths(cleaned, fullPrefix, results, rootPrefix);
804
1092
  }
805
1093
  }
806
1094
  }
@@ -874,14 +1162,21 @@ var java = {
874
1162
  extensions: [".java"],
875
1163
  commentStyle: "c-style",
876
1164
  importPatterns: [
877
- // import com.example.ClassName;
878
- { regex: /^import\s+(?:static\s+)?([\w.]+);/gm }
1165
+ // Bug #5 fix: import com.example.ClassName; and import com.example.*; (wildcard)
1166
+ // Bug #6: static imports also captured here
1167
+ { regex: /^import\s+(?:static\s+)?([\w.*]+);/gm }
879
1168
  ],
880
1169
  resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
881
- const filePath = importPath.replace(/\./g, "/") + ".java";
882
- for (const srcRoot of ["", "src/main/java/", "src/", "app/src/main/java/"]) {
883
- const full = join3(rootDir, srcRoot, filePath);
884
- if (projectFiles.has(full)) return full;
1170
+ if (importPath.endsWith(".*")) {
1171
+ return null;
1172
+ }
1173
+ const segments = importPath.split(".");
1174
+ for (let i = segments.length; i > 0; i--) {
1175
+ const filePath = segments.slice(0, i).join("/") + ".java";
1176
+ for (const srcRoot of ["", "src/main/java/", "src/", "app/src/main/java/"]) {
1177
+ const full = join3(rootDir, srcRoot, filePath);
1178
+ if (projectFiles.has(full)) return full;
1179
+ }
885
1180
  }
886
1181
  return null;
887
1182
  },
@@ -937,8 +1232,8 @@ var php = {
937
1232
  importPatterns: [
938
1233
  // require/include/require_once/include_once 'path'
939
1234
  { regex: /\b(?:require|include)(?:_once)?\s+['"]([^'"]+)['"]/gm },
940
- // use Namespace\Class (PSR-4 style)
941
- { regex: /^use\s+([\w\\]+)/gm }
1235
+ // Bug #9 fix: use Namespace\Class skip `function` and `const` keywords
1236
+ { regex: /^use\s+(?:function\s+|const\s+)?([\w\\]+)/gm }
942
1237
  ],
943
1238
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
944
1239
  if (importPath.includes("/") || importPath.endsWith(".php")) {
@@ -963,8 +1258,8 @@ var swift = {
963
1258
  extensions: [".swift"],
964
1259
  commentStyle: "c-style",
965
1260
  importPatterns: [
966
- // import ModuleName (for cross-module dependencies)
967
- { regex: /^import\s+(?:class\s+|struct\s+|enum\s+|protocol\s+|func\s+|var\s+|let\s+|typealias\s+)?(\w+)/gm }
1261
+ // Bug #10 fix: import ModuleName and @testable import ModuleName
1262
+ { regex: /^(?:@testable\s+)?import\s+(?:class\s+|struct\s+|enum\s+|protocol\s+|func\s+|var\s+|let\s+|typealias\s+)?(\w+)/gm }
968
1263
  ],
969
1264
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
970
1265
  const spmDir = join3(rootDir, "Sources", importPath);
@@ -980,11 +1275,18 @@ var kotlin = {
980
1275
  extensions: [".kt", ".kts"],
981
1276
  commentStyle: "c-style",
982
1277
  importPatterns: [
983
- // import com.example.ClassName
984
- { regex: /^import\s+([\w.]+)/gm }
1278
+ // Bug #7/#8 fix: import com.example.ClassName and import com.example.*
1279
+ { regex: /^import\s+([\w.*]+)/gm }
985
1280
  ],
986
1281
  resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
987
- const filePath = importPath.replace(/\./g, "/");
1282
+ if (importPath.endsWith(".*")) {
1283
+ return null;
1284
+ }
1285
+ let cleanPath = importPath;
1286
+ if (cleanPath.endsWith(".")) {
1287
+ cleanPath = cleanPath.slice(0, -1);
1288
+ }
1289
+ const filePath = cleanPath.replace(/\./g, "/");
988
1290
  for (const ext of [".kt", ".kts"]) {
989
1291
  for (const srcRoot of [
990
1292
  "",
@@ -1002,6 +1304,124 @@ var kotlin = {
1002
1304
  },
1003
1305
  defaultExclude: ["build", "\\.gradle", "\\.idea"]
1004
1306
  };
1307
+ var cSharp = {
1308
+ id: "c-sharp",
1309
+ extensions: [".cs"],
1310
+ commentStyle: "c-style",
1311
+ importPatterns: [
1312
+ // using Namespace; and using Namespace.SubNamespace;
1313
+ // using static Namespace.Class;
1314
+ // Skip: using Alias = Namespace.Class; (captured but resolved same way)
1315
+ { regex: /^using\s+(?:static\s+)?([\w.]+)\s*;/gm }
1316
+ ],
1317
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
1318
+ const segments = importPath.split(".");
1319
+ for (let i = segments.length; i > 0; i--) {
1320
+ const filePath = segments.slice(0, i).join("/") + ".cs";
1321
+ for (const srcRoot of ["", "src/", "lib/"]) {
1322
+ const full = join3(rootDir, srcRoot, filePath);
1323
+ if (projectFiles.has(full)) return full;
1324
+ }
1325
+ }
1326
+ for (let start = 0; start < segments.length; start++) {
1327
+ const dirPath = segments.slice(start).join("/");
1328
+ for (const srcRoot of ["", "src/", "lib/"]) {
1329
+ const prefix = join3(rootDir, srcRoot, dirPath) + "/";
1330
+ for (const f of projectFiles) {
1331
+ if (f.startsWith(prefix) && f.endsWith(".cs")) return f;
1332
+ }
1333
+ }
1334
+ }
1335
+ return null;
1336
+ },
1337
+ defaultExclude: ["bin", "obj", "\\.vs", "packages", "TestResults"]
1338
+ };
1339
+ var dart = {
1340
+ id: "dart",
1341
+ extensions: [".dart"],
1342
+ commentStyle: "c-style",
1343
+ importPatterns: [
1344
+ // import 'package:pkg/file.dart'; or import 'relative/path.dart';
1345
+ { regex: /^import\s+['"]([^'"]+)['"]/gm },
1346
+ // export 'file.dart'; (re-exports create dependencies too)
1347
+ { regex: /^export\s+['"]([^'"]+)['"]/gm }
1348
+ ],
1349
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1350
+ if (importPath.startsWith("dart:")) return null;
1351
+ if (importPath.startsWith("package:")) {
1352
+ const ownPackage = dartPackageName(rootDir);
1353
+ if (!ownPackage) return null;
1354
+ const prefix = `package:${ownPackage}/`;
1355
+ if (!importPath.startsWith(prefix)) return null;
1356
+ const relPath = importPath.slice(prefix.length);
1357
+ const full = join3(rootDir, "lib", relPath);
1358
+ if (projectFiles.has(full)) return full;
1359
+ return null;
1360
+ }
1361
+ const resolved = resolve3(dirname(sourceFile), importPath);
1362
+ if (projectFiles.has(resolved)) return resolved;
1363
+ return null;
1364
+ },
1365
+ defaultExclude: ["\\.dart_tool", "build", "\\.packages"]
1366
+ };
1367
+ var dartPackageCache = /* @__PURE__ */ new Map();
1368
+ function dartPackageName(rootDir) {
1369
+ if (dartPackageCache.has(rootDir)) return dartPackageCache.get(rootDir);
1370
+ try {
1371
+ const content = readFileSync(join3(rootDir, "pubspec.yaml"), "utf-8");
1372
+ const match = content.match(/^name:\s*(\S+)/m);
1373
+ const name = match ? match[1] : null;
1374
+ dartPackageCache.set(rootDir, name);
1375
+ return name;
1376
+ } catch {
1377
+ dartPackageCache.set(rootDir, null);
1378
+ return null;
1379
+ }
1380
+ }
1381
+ var scala = {
1382
+ id: "scala",
1383
+ extensions: [".scala", ".sc"],
1384
+ commentStyle: "c-style",
1385
+ importPatterns: [],
1386
+ // handled by extractImports for grouped syntax
1387
+ extractImports(content) {
1388
+ const imports = [];
1389
+ const importRegex = /\bimport\s+([\w.]+(?:\.\{[^}]+\}|\.\w+|\._))/gm;
1390
+ let match;
1391
+ while ((match = importRegex.exec(content)) !== null) {
1392
+ const full = match[1];
1393
+ const braceMatch = full.match(/^([\w.]+)\.\{([^}]+)\}$/);
1394
+ if (braceMatch) {
1395
+ const prefix = braceMatch[1];
1396
+ const items = braceMatch[2].split(",");
1397
+ for (const item of items) {
1398
+ const trimmed = item.trim().split(/\s+/)[0];
1399
+ if (trimmed === "_") continue;
1400
+ imports.push(`${prefix}.${trimmed}`);
1401
+ }
1402
+ } else if (full.endsWith("._")) {
1403
+ continue;
1404
+ } else {
1405
+ imports.push(full);
1406
+ }
1407
+ }
1408
+ return imports;
1409
+ },
1410
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
1411
+ const segments = importPath.split(".");
1412
+ for (let i = segments.length; i > 0; i--) {
1413
+ const filePath = segments.slice(0, i).join("/");
1414
+ for (const ext of [".scala", ".sc"]) {
1415
+ for (const srcRoot of ["", "src/main/scala/", "src/", "app/"]) {
1416
+ const full = join3(rootDir, srcRoot, filePath + ext);
1417
+ if (projectFiles.has(full)) return full;
1418
+ }
1419
+ }
1420
+ }
1421
+ return null;
1422
+ },
1423
+ defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
1424
+ };
1005
1425
  var LANGUAGE_CONFIGS = {
1006
1426
  javascript: null,
1007
1427
  // handled by DependencyCruiserEngine
@@ -1010,10 +1430,13 @@ var LANGUAGE_CONFIGS = {
1010
1430
  go,
1011
1431
  java,
1012
1432
  "c-cpp": cCpp,
1433
+ "c-sharp": cSharp,
1013
1434
  ruby,
1014
1435
  php,
1015
1436
  swift,
1016
- kotlin
1437
+ kotlin,
1438
+ dart,
1439
+ scala
1017
1440
  };
1018
1441
  function getLanguageConfig(id) {
1019
1442
  return LANGUAGE_CONFIGS[id] ?? null;
@@ -1571,7 +1994,7 @@ server.tool(
1571
1994
  targetDir: z2.string().default("src").describe("Target directory path (default: src)"),
1572
1995
  exclude: z2.array(z2.string()).optional().describe("Array of regex patterns to exclude (e.g. ['test', 'mock'])"),
1573
1996
  maxDepth: z2.number().int().min(0).optional().describe("Max analysis depth (0 = unlimited)"),
1574
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
1997
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
1575
1998
  },
1576
1999
  async ({ targetDir, exclude, maxDepth, language }) => {
1577
2000
  try {
@@ -1601,7 +2024,7 @@ server.tool(
1601
2024
  topN: z2.number().int().min(1).max(50).optional().describe("Number of top items to show in each section (default: 10)"),
1602
2025
  saveSnapshot: z2.boolean().optional().describe("Also save a snapshot after analysis (default: false)"),
1603
2026
  projectRoot: z2.string().default(".").describe("Project root (needed only when saveSnapshot is true)"),
1604
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
2027
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
1605
2028
  },
1606
2029
  async ({ targetDir, exclude, topN, saveSnapshot: doSave, projectRoot, language }) => {
1607
2030
  try {
@@ -1628,7 +2051,7 @@ server.tool(
1628
2051
  {
1629
2052
  targetDir: z2.string().default("src").describe("Target directory path"),
1630
2053
  projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)"),
1631
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
2054
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
1632
2055
  },
1633
2056
  async ({ targetDir, projectRoot, language }) => {
1634
2057
  try {
@@ -1658,7 +2081,7 @@ server.tool(
1658
2081
  {
1659
2082
  targetDir: z2.string().default("src").describe("Target directory path"),
1660
2083
  projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)"),
1661
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
2084
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
1662
2085
  },
1663
2086
  async ({ targetDir, projectRoot, language }) => {
1664
2087
  try {
@@ -1696,7 +2119,7 @@ server.tool(
1696
2119
  {
1697
2120
  targetDir: z2.string().default("src").describe("Target directory path"),
1698
2121
  projectRoot: z2.string().default(".").describe("Project root"),
1699
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
2122
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
1700
2123
  },
1701
2124
  async ({ targetDir, projectRoot, language }) => {
1702
2125
  try {
@@ -1756,7 +2179,7 @@ server.tool(
1756
2179
  targetDir: z2.string().default("src").describe("Target directory path"),
1757
2180
  projectRoot: z2.string().default(".").describe("Project root"),
1758
2181
  limit: z2.number().int().min(1).max(50).optional().describe("Max results (default: 10)"),
1759
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
2182
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
1760
2183
  },
1761
2184
  async ({ query, mode, targetDir, projectRoot, limit, language }) => {
1762
2185
  try {