archtracker-mcp 0.3.1 → 0.4.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/index.d.ts CHANGED
@@ -60,7 +60,9 @@ interface ArchContext {
60
60
  }>;
61
61
  }
62
62
 
63
- type LanguageId = "javascript" | "python" | "rust" | "go" | "java" | "c-cpp" | "c-sharp" | "ruby" | "php" | "swift" | "kotlin" | "dart" | "scala";
63
+ /** Single source of truth for supported language IDs */
64
+ declare const LANGUAGE_IDS: readonly ["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"];
65
+ type LanguageId = (typeof LANGUAGE_IDS)[number];
64
66
 
65
67
  /** Options for the dependency analyzer */
66
68
  interface AnalyzeOptions {
@@ -68,19 +70,12 @@ interface AnalyzeOptions {
68
70
  exclude?: string[];
69
71
  /** Maximum recursion depth (0 = unlimited) */
70
72
  maxDepth?: number;
71
- /** Path to tsconfig.json (auto-detected if omitted) */
72
- tsConfigPath?: string;
73
- /** Include type-only imports (import type {...}) — JS/TS only */
74
- includeTypeOnly?: boolean;
75
73
  /** Target language (auto-detected if omitted) */
76
74
  language?: LanguageId;
77
75
  }
78
76
  /**
79
- * Analyze project dependencies.
80
- *
81
- * For JavaScript/TypeScript: uses dependency-cruiser (AST-based).
82
- * For other languages: uses regex-based import extraction.
83
- * Language is auto-detected from project marker files if not specified.
77
+ * Analyze project dependencies using regex-based import extraction.
78
+ * Supports all 13 languages. Language is auto-detected if not specified.
84
79
  */
85
80
  declare function analyzeProject(rootDir: string, options?: AnalyzeOptions): Promise<DependencyGraph>;
86
81
  /** Custom error class for analyzer failures */
package/dist/index.js CHANGED
@@ -2,125 +2,12 @@
2
2
  var SCHEMA_VERSION = "1.0";
3
3
 
4
4
  // src/analyzer/analyze.ts
5
- import { resolve as resolve4 } from "path";
6
-
7
- // src/analyzer/engines/dependency-cruiser.ts
8
- import { resolve } from "path";
9
- import { cruise } from "dependency-cruiser";
10
- var DEFAULT_EXCLUDE = [
11
- "node_modules",
12
- "\\.d\\.ts$",
13
- "dist",
14
- "build",
15
- "coverage",
16
- "\\.archtracker"
17
- ];
18
- var DependencyCruiserEngine = class {
19
- async analyze(rootDir, options = {}) {
20
- const {
21
- exclude = [],
22
- maxDepth = 0,
23
- tsConfigPath,
24
- includeTypeOnly = true
25
- } = options;
26
- const absRootDir = resolve(rootDir);
27
- const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
28
- const excludePattern = allExclude.join("|");
29
- const cruiseOptions = {
30
- baseDir: absRootDir,
31
- exclude: { path: excludePattern },
32
- doNotFollow: { path: "node_modules" },
33
- maxDepth,
34
- tsPreCompilationDeps: includeTypeOnly ? true : false,
35
- combinedDependencies: false
36
- };
37
- if (tsConfigPath) {
38
- cruiseOptions.tsConfig = { fileName: tsConfigPath };
39
- }
40
- let result;
41
- try {
42
- result = await cruise(["."], cruiseOptions);
43
- } catch (error) {
44
- const message = error instanceof Error ? error.message : String(error);
45
- throw new Error(`dependency-cruiser failed: ${message}`, {
46
- cause: error
47
- });
48
- }
49
- if (result.exitCode !== 0 && !result.output) {
50
- throw new Error(`Analysis exited with code ${result.exitCode}`);
51
- }
52
- const cruiseResult = result.output;
53
- return this.buildGraph(absRootDir, cruiseResult);
54
- }
55
- buildGraph(rootDir, cruiseResult) {
56
- const files = {};
57
- const edges = [];
58
- const circularSet = /* @__PURE__ */ new Set();
59
- const circularDependencies = [];
60
- for (const mod of cruiseResult.modules) {
61
- if (this.isExternalModule(mod)) continue;
62
- files[mod.source] = {
63
- path: mod.source,
64
- exists: !mod.couldNotResolve,
65
- dependencies: [],
66
- dependents: []
67
- };
68
- }
69
- for (const mod of cruiseResult.modules) {
70
- for (const dep of mod.dependencies) {
71
- if (dep.couldNotResolve || dep.coreModule) continue;
72
- if (!files[mod.source] || this.isExternalDep(dep)) continue;
73
- const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
74
- edges.push({ source: mod.source, target: dep.resolved, type: edgeType });
75
- if (files[mod.source]) {
76
- files[mod.source].dependencies.push(dep.resolved);
77
- }
78
- if (files[dep.resolved]) {
79
- files[dep.resolved].dependents.push(mod.source);
80
- }
81
- if (dep.circular && dep.cycle) {
82
- const cyclePath = dep.cycle.map((c) => c.name);
83
- const cycleKey = [...cyclePath].sort().join("\u2192");
84
- if (!circularSet.has(cycleKey)) {
85
- circularSet.add(cycleKey);
86
- circularDependencies.push({ cycle: cyclePath });
87
- }
88
- }
89
- }
90
- }
91
- return {
92
- rootDir,
93
- files,
94
- edges,
95
- circularDependencies,
96
- totalFiles: Object.keys(files).length,
97
- totalEdges: edges.length
98
- };
99
- }
100
- isExternalModule(mod) {
101
- if (mod.coreModule) return true;
102
- const depTypes = mod.dependencyTypes ?? [];
103
- if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
104
- return isExternalPath(mod.source);
105
- }
106
- isExternalDep(dep) {
107
- if (dep.coreModule) return true;
108
- if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core"))
109
- return true;
110
- return isExternalPath(dep.resolved);
111
- }
112
- };
113
- function isExternalPath(source) {
114
- if (source.startsWith("@")) return true;
115
- if (!source.includes("/") && !source.includes("\\") && !source.includes("."))
116
- return true;
117
- if (source.startsWith("node:")) return true;
118
- return false;
119
- }
5
+ import { resolve as resolve3 } from "path";
6
+ import { stat as stat3 } from "fs/promises";
120
7
 
121
8
  // src/analyzer/engines/regex-engine.ts
122
9
  import { readdir, readFile } from "fs/promises";
123
- import { join, relative, resolve as resolve2 } from "path";
10
+ import { join, relative, resolve } from "path";
124
11
 
125
12
  // src/analyzer/engines/cycle.ts
126
13
  function detectCycles(edges) {
@@ -678,7 +565,7 @@ var RegexEngine = class {
678
565
  this.config = config;
679
566
  }
680
567
  async analyze(rootDir, options = {}) {
681
- const absRootDir = resolve2(rootDir);
568
+ const absRootDir = resolve(rootDir);
682
569
  const excludePatterns = [
683
570
  ...this.config.defaultExclude ?? [],
684
571
  ...options.exclude ?? [],
@@ -925,7 +812,7 @@ async function scanExtensions(dir, counts, maxDepth, currentDepth) {
925
812
 
926
813
  // src/analyzer/engines/languages.ts
927
814
  import { readFileSync } from "fs";
928
- import { join as join3, dirname, resolve as resolve3 } from "path";
815
+ import { join as join3, dirname, resolve as resolve2 } from "path";
929
816
  var python = {
930
817
  id: "python",
931
818
  extensions: [".py"],
@@ -1189,7 +1076,7 @@ var cCpp = {
1189
1076
  { regex: /^#include\s+"([^"]+)"/gm }
1190
1077
  ],
1191
1078
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1192
- const fromSource = resolve3(dirname(sourceFile), importPath);
1079
+ const fromSource = resolve2(dirname(sourceFile), importPath);
1193
1080
  if (projectFiles.has(fromSource)) return fromSource;
1194
1081
  const fromRoot = join3(rootDir, importPath);
1195
1082
  if (projectFiles.has(fromRoot)) return fromRoot;
@@ -1213,7 +1100,7 @@ var ruby = {
1213
1100
  ],
1214
1101
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1215
1102
  const withExt = importPath.endsWith(".rb") ? importPath : importPath + ".rb";
1216
- const fromSource = resolve3(dirname(sourceFile), withExt);
1103
+ const fromSource = resolve2(dirname(sourceFile), withExt);
1217
1104
  if (projectFiles.has(fromSource)) return fromSource;
1218
1105
  const fromRoot = join3(rootDir, withExt);
1219
1106
  if (projectFiles.has(fromRoot)) return fromRoot;
@@ -1236,7 +1123,7 @@ var php = {
1236
1123
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1237
1124
  if (importPath.includes("/") || importPath.endsWith(".php")) {
1238
1125
  const withExt = importPath.endsWith(".php") ? importPath : importPath + ".php";
1239
- const fromSource = resolve3(dirname(sourceFile), withExt);
1126
+ const fromSource = resolve2(dirname(sourceFile), withExt);
1240
1127
  if (projectFiles.has(fromSource)) return fromSource;
1241
1128
  const fromRoot2 = join3(rootDir, withExt);
1242
1129
  if (projectFiles.has(fromRoot2)) return fromRoot2;
@@ -1356,7 +1243,7 @@ var dart = {
1356
1243
  if (projectFiles.has(full)) return full;
1357
1244
  return null;
1358
1245
  }
1359
- const resolved = resolve3(dirname(sourceFile), importPath);
1246
+ const resolved = resolve2(dirname(sourceFile), importPath);
1360
1247
  if (projectFiles.has(resolved)) return resolved;
1361
1248
  return null;
1362
1249
  },
@@ -1420,9 +1307,47 @@ var scala = {
1420
1307
  },
1421
1308
  defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
1422
1309
  };
1310
+ var javascript = {
1311
+ id: "javascript",
1312
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1313
+ commentStyle: "c-style",
1314
+ importPatterns: [
1315
+ // ES6: import [type] [stuff from] "path"
1316
+ { regex: /import\s+(?:type\s+)?(?:[\w*{}\s,]+\s+from\s+)?["']([^"']+)["']/g },
1317
+ // Dynamic: import("path")
1318
+ { regex: /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g },
1319
+ // Re-export: export [type] { stuff } from "path" / export * from "path"
1320
+ { regex: /export\s+(?:type\s+)?(?:\{[^}]*\}|\*(?:\s+as\s+\w+)?)\s+from\s+["']([^"']+)["']/g },
1321
+ // CommonJS: require("path")
1322
+ { regex: /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g }
1323
+ ],
1324
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1325
+ if (importPath.startsWith("node:")) return null;
1326
+ if (!importPath.startsWith(".")) return null;
1327
+ const resolved = resolve2(dirname(sourceFile), importPath);
1328
+ if (projectFiles.has(resolved)) return resolved;
1329
+ if (resolved.endsWith(".js")) {
1330
+ const tsPath = resolved.slice(0, -3) + ".ts";
1331
+ if (projectFiles.has(tsPath)) return tsPath;
1332
+ const tsxPath = resolved.slice(0, -3) + ".tsx";
1333
+ if (projectFiles.has(tsxPath)) return tsxPath;
1334
+ }
1335
+ if (resolved.endsWith(".jsx")) {
1336
+ const tsxPath = resolved.slice(0, -4) + ".tsx";
1337
+ if (projectFiles.has(tsxPath)) return tsxPath;
1338
+ }
1339
+ for (const ext of [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]) {
1340
+ if (projectFiles.has(resolved + ext)) return resolved + ext;
1341
+ }
1342
+ for (const idx of ["/index.ts", "/index.tsx", "/index.js", "/index.jsx"]) {
1343
+ if (projectFiles.has(resolved + idx)) return resolved + idx;
1344
+ }
1345
+ return null;
1346
+ },
1347
+ defaultExclude: ["node_modules", "\\.d\\.ts$", "dist", "build", "coverage"]
1348
+ };
1423
1349
  var LANGUAGE_CONFIGS = {
1424
- javascript: null,
1425
- // handled by DependencyCruiserEngine
1350
+ javascript,
1426
1351
  python,
1427
1352
  rust,
1428
1353
  go,
@@ -1442,12 +1367,18 @@ function getLanguageConfig(id) {
1442
1367
 
1443
1368
  // src/analyzer/analyze.ts
1444
1369
  async function analyzeProject(rootDir, options = {}) {
1445
- const absRootDir = resolve4(rootDir);
1446
- const language = options.language ?? await detectLanguage(absRootDir);
1370
+ const absRootDir = resolve3(rootDir);
1447
1371
  try {
1448
- if (language === "javascript") {
1449
- return await new DependencyCruiserEngine().analyze(absRootDir, options);
1372
+ const s = await stat3(absRootDir);
1373
+ if (!s.isDirectory()) {
1374
+ throw new AnalyzerError(`Not a directory: ${absRootDir}`);
1450
1375
  }
1376
+ } catch (error) {
1377
+ if (error instanceof AnalyzerError) throw error;
1378
+ throw new AnalyzerError(`Directory not found: ${absRootDir}`, { cause: error });
1379
+ }
1380
+ const language = options.language ?? await detectLanguage(absRootDir);
1381
+ try {
1451
1382
  const config = getLanguageConfig(language);
1452
1383
  if (!config) {
1453
1384
  throw new AnalyzerError(`No analyzer config for language: ${language}`);