archtracker-mcp 0.3.2 → 0.4.1

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
@@ -1,128 +1,17 @@
1
+ #!/usr/bin/env node
2
+
1
3
  // src/mcp/index.ts
2
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
6
  import { z as z2 } from "zod";
5
7
 
6
8
  // src/analyzer/analyze.ts
7
- import { resolve as resolve4 } from "path";
8
-
9
- // src/analyzer/engines/dependency-cruiser.ts
10
- import { resolve } from "path";
11
- import { cruise } from "dependency-cruiser";
12
- var DEFAULT_EXCLUDE = [
13
- "node_modules",
14
- "\\.d\\.ts$",
15
- "dist",
16
- "build",
17
- "coverage",
18
- "\\.archtracker"
19
- ];
20
- var DependencyCruiserEngine = class {
21
- async analyze(rootDir, options = {}) {
22
- const {
23
- exclude = [],
24
- maxDepth = 0,
25
- tsConfigPath,
26
- includeTypeOnly = true
27
- } = options;
28
- const absRootDir = resolve(rootDir);
29
- const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
30
- const excludePattern = allExclude.join("|");
31
- const cruiseOptions = {
32
- baseDir: absRootDir,
33
- exclude: { path: excludePattern },
34
- doNotFollow: { path: "node_modules" },
35
- maxDepth,
36
- tsPreCompilationDeps: includeTypeOnly ? true : false,
37
- combinedDependencies: false
38
- };
39
- if (tsConfigPath) {
40
- cruiseOptions.tsConfig = { fileName: tsConfigPath };
41
- }
42
- let result;
43
- try {
44
- result = await cruise(["."], cruiseOptions);
45
- } catch (error) {
46
- const message = error instanceof Error ? error.message : String(error);
47
- throw new Error(`dependency-cruiser failed: ${message}`, {
48
- cause: error
49
- });
50
- }
51
- if (result.exitCode !== 0 && !result.output) {
52
- throw new Error(`Analysis exited with code ${result.exitCode}`);
53
- }
54
- const cruiseResult = result.output;
55
- return this.buildGraph(absRootDir, cruiseResult);
56
- }
57
- buildGraph(rootDir, cruiseResult) {
58
- const files = {};
59
- const edges = [];
60
- const circularSet = /* @__PURE__ */ new Set();
61
- const circularDependencies = [];
62
- for (const mod of cruiseResult.modules) {
63
- if (this.isExternalModule(mod)) continue;
64
- files[mod.source] = {
65
- path: mod.source,
66
- exists: !mod.couldNotResolve,
67
- dependencies: [],
68
- dependents: []
69
- };
70
- }
71
- for (const mod of cruiseResult.modules) {
72
- for (const dep of mod.dependencies) {
73
- if (dep.couldNotResolve || dep.coreModule) continue;
74
- if (!files[mod.source] || this.isExternalDep(dep)) continue;
75
- const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
76
- edges.push({ source: mod.source, target: dep.resolved, type: edgeType });
77
- if (files[mod.source]) {
78
- files[mod.source].dependencies.push(dep.resolved);
79
- }
80
- if (files[dep.resolved]) {
81
- files[dep.resolved].dependents.push(mod.source);
82
- }
83
- if (dep.circular && dep.cycle) {
84
- const cyclePath = dep.cycle.map((c) => c.name);
85
- const cycleKey = [...cyclePath].sort().join("\u2192");
86
- if (!circularSet.has(cycleKey)) {
87
- circularSet.add(cycleKey);
88
- circularDependencies.push({ cycle: cyclePath });
89
- }
90
- }
91
- }
92
- }
93
- return {
94
- rootDir,
95
- files,
96
- edges,
97
- circularDependencies,
98
- totalFiles: Object.keys(files).length,
99
- totalEdges: edges.length
100
- };
101
- }
102
- isExternalModule(mod) {
103
- if (mod.coreModule) return true;
104
- const depTypes = mod.dependencyTypes ?? [];
105
- if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
106
- return isExternalPath(mod.source);
107
- }
108
- isExternalDep(dep) {
109
- if (dep.coreModule) return true;
110
- if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core"))
111
- return true;
112
- return isExternalPath(dep.resolved);
113
- }
114
- };
115
- function isExternalPath(source) {
116
- if (source.startsWith("@")) return true;
117
- if (!source.includes("/") && !source.includes("\\") && !source.includes("."))
118
- return true;
119
- if (source.startsWith("node:")) return true;
120
- return false;
121
- }
9
+ import { resolve as resolve3 } from "path";
10
+ import { stat as stat3 } from "fs/promises";
122
11
 
123
12
  // src/analyzer/engines/regex-engine.ts
124
13
  import { readdir, readFile } from "fs/promises";
125
- import { join, relative, resolve as resolve2 } from "path";
14
+ import { join, relative, resolve } from "path";
126
15
 
127
16
  // src/analyzer/engines/cycle.ts
128
17
  function detectCycles(edges) {
@@ -680,7 +569,7 @@ var RegexEngine = class {
680
569
  this.config = config;
681
570
  }
682
571
  async analyze(rootDir, options = {}) {
683
- const absRootDir = resolve2(rootDir);
572
+ const absRootDir = resolve(rootDir);
684
573
  const excludePatterns = [
685
574
  ...this.config.defaultExclude ?? [],
686
575
  ...options.exclude ?? [],
@@ -927,7 +816,7 @@ async function scanExtensions(dir, counts, maxDepth, currentDepth) {
927
816
 
928
817
  // src/analyzer/engines/languages.ts
929
818
  import { readFileSync } from "fs";
930
- import { join as join3, dirname, resolve as resolve3 } from "path";
819
+ import { join as join3, dirname, resolve as resolve2 } from "path";
931
820
 
932
821
  // src/analyzer/engines/types.ts
933
822
  var LANGUAGE_IDS = [
@@ -1210,7 +1099,7 @@ var cCpp = {
1210
1099
  { regex: /^#include\s+"([^"]+)"/gm }
1211
1100
  ],
1212
1101
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1213
- const fromSource = resolve3(dirname(sourceFile), importPath);
1102
+ const fromSource = resolve2(dirname(sourceFile), importPath);
1214
1103
  if (projectFiles.has(fromSource)) return fromSource;
1215
1104
  const fromRoot = join3(rootDir, importPath);
1216
1105
  if (projectFiles.has(fromRoot)) return fromRoot;
@@ -1234,7 +1123,7 @@ var ruby = {
1234
1123
  ],
1235
1124
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1236
1125
  const withExt = importPath.endsWith(".rb") ? importPath : importPath + ".rb";
1237
- const fromSource = resolve3(dirname(sourceFile), withExt);
1126
+ const fromSource = resolve2(dirname(sourceFile), withExt);
1238
1127
  if (projectFiles.has(fromSource)) return fromSource;
1239
1128
  const fromRoot = join3(rootDir, withExt);
1240
1129
  if (projectFiles.has(fromRoot)) return fromRoot;
@@ -1257,7 +1146,7 @@ var php = {
1257
1146
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1258
1147
  if (importPath.includes("/") || importPath.endsWith(".php")) {
1259
1148
  const withExt = importPath.endsWith(".php") ? importPath : importPath + ".php";
1260
- const fromSource = resolve3(dirname(sourceFile), withExt);
1149
+ const fromSource = resolve2(dirname(sourceFile), withExt);
1261
1150
  if (projectFiles.has(fromSource)) return fromSource;
1262
1151
  const fromRoot2 = join3(rootDir, withExt);
1263
1152
  if (projectFiles.has(fromRoot2)) return fromRoot2;
@@ -1377,7 +1266,7 @@ var dart = {
1377
1266
  if (projectFiles.has(full)) return full;
1378
1267
  return null;
1379
1268
  }
1380
- const resolved = resolve3(dirname(sourceFile), importPath);
1269
+ const resolved = resolve2(dirname(sourceFile), importPath);
1381
1270
  if (projectFiles.has(resolved)) return resolved;
1382
1271
  return null;
1383
1272
  },
@@ -1441,9 +1330,47 @@ var scala = {
1441
1330
  },
1442
1331
  defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
1443
1332
  };
1333
+ var javascript = {
1334
+ id: "javascript",
1335
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
1336
+ commentStyle: "c-style",
1337
+ importPatterns: [
1338
+ // ES6: import [type] [stuff from] "path"
1339
+ { regex: /import\s+(?:type\s+)?(?:[\w*{}\s,]+\s+from\s+)?["']([^"']+)["']/g },
1340
+ // Dynamic: import("path")
1341
+ { regex: /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g },
1342
+ // Re-export: export [type] { stuff } from "path" / export * from "path"
1343
+ { regex: /export\s+(?:type\s+)?(?:\{[^}]*\}|\*(?:\s+as\s+\w+)?)\s+from\s+["']([^"']+)["']/g },
1344
+ // CommonJS: require("path")
1345
+ { regex: /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g }
1346
+ ],
1347
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1348
+ if (importPath.startsWith("node:")) return null;
1349
+ if (!importPath.startsWith(".")) return null;
1350
+ const resolved = resolve2(dirname(sourceFile), importPath);
1351
+ if (projectFiles.has(resolved)) return resolved;
1352
+ if (resolved.endsWith(".js")) {
1353
+ const tsPath = resolved.slice(0, -3) + ".ts";
1354
+ if (projectFiles.has(tsPath)) return tsPath;
1355
+ const tsxPath = resolved.slice(0, -3) + ".tsx";
1356
+ if (projectFiles.has(tsxPath)) return tsxPath;
1357
+ }
1358
+ if (resolved.endsWith(".jsx")) {
1359
+ const tsxPath = resolved.slice(0, -4) + ".tsx";
1360
+ if (projectFiles.has(tsxPath)) return tsxPath;
1361
+ }
1362
+ for (const ext of [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]) {
1363
+ if (projectFiles.has(resolved + ext)) return resolved + ext;
1364
+ }
1365
+ for (const idx of ["/index.ts", "/index.tsx", "/index.js", "/index.jsx"]) {
1366
+ if (projectFiles.has(resolved + idx)) return resolved + idx;
1367
+ }
1368
+ return null;
1369
+ },
1370
+ defaultExclude: ["node_modules", "\\.d\\.ts$", "dist", "build", "coverage"]
1371
+ };
1444
1372
  var LANGUAGE_CONFIGS = {
1445
- javascript: null,
1446
- // handled by DependencyCruiserEngine
1373
+ javascript,
1447
1374
  python,
1448
1375
  rust,
1449
1376
  go,
@@ -1463,12 +1390,18 @@ function getLanguageConfig(id) {
1463
1390
 
1464
1391
  // src/analyzer/analyze.ts
1465
1392
  async function analyzeProject(rootDir, options = {}) {
1466
- const absRootDir = resolve4(rootDir);
1467
- const language = options.language ?? await detectLanguage(absRootDir);
1393
+ const absRootDir = resolve3(rootDir);
1468
1394
  try {
1469
- if (language === "javascript") {
1470
- return await new DependencyCruiserEngine().analyze(absRootDir, options);
1395
+ const s = await stat3(absRootDir);
1396
+ if (!s.isDirectory()) {
1397
+ throw new AnalyzerError(`Not a directory: ${absRootDir}`);
1471
1398
  }
1399
+ } catch (error) {
1400
+ if (error instanceof AnalyzerError) throw error;
1401
+ throw new AnalyzerError(`Directory not found: ${absRootDir}`, { cause: error });
1402
+ }
1403
+ const language = options.language ?? await detectLanguage(absRootDir);
1404
+ try {
1472
1405
  const config = getLanguageConfig(language);
1473
1406
  if (!config) {
1474
1407
  throw new AnalyzerError(`No analyzer config for language: ${language}`);
@@ -1983,10 +1916,10 @@ function arraysEqual(a, b) {
1983
1916
  }
1984
1917
 
1985
1918
  // src/utils/path-guard.ts
1986
- import { resolve as resolve5 } from "path";
1919
+ import { resolve as resolve4 } from "path";
1987
1920
  function validatePath(inputPath, boundary) {
1988
- const resolved = resolve5(inputPath);
1989
- const root = boundary ? resolve5(boundary) : process.cwd();
1921
+ const resolved = resolve4(inputPath);
1922
+ const root = boundary ? resolve4(boundary) : process.cwd();
1990
1923
  if (!resolved.startsWith(root)) {
1991
1924
  throw new PathTraversalError(
1992
1925
  t("pathGuard.traversal", { input: inputPath, resolved, boundary: root })