archtracker-mcp 0.1.1 → 0.2.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
@@ -1,9 +1,19 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/mcp/index.ts
2
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
11
  import { z as z2 } from "zod";
5
12
 
6
13
  // src/analyzer/analyze.ts
14
+ import { resolve as resolve4 } from "path";
15
+
16
+ // src/analyzer/engines/dependency-cruiser.ts
7
17
  import { resolve } from "path";
8
18
  import { cruise } from "dependency-cruiser";
9
19
  var DEFAULT_EXCLUDE = [
@@ -14,110 +24,630 @@ var DEFAULT_EXCLUDE = [
14
24
  "coverage",
15
25
  "\\.archtracker"
16
26
  ];
17
- async function analyzeProject(rootDir, options = {}) {
18
- const {
19
- exclude = [],
20
- maxDepth = 0,
21
- tsConfigPath,
22
- includeTypeOnly = true
23
- } = options;
24
- const absRootDir = resolve(rootDir);
25
- const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
26
- const excludePattern = allExclude.join("|");
27
- const cruiseOptions = {
28
- baseDir: absRootDir,
29
- exclude: { path: excludePattern },
30
- doNotFollow: { path: "node_modules" },
31
- maxDepth,
32
- tsPreCompilationDeps: includeTypeOnly ? true : false,
33
- combinedDependencies: false
34
- };
35
- if (tsConfigPath) {
36
- cruiseOptions.tsConfig = { fileName: tsConfigPath };
27
+ var DependencyCruiserEngine = class {
28
+ async analyze(rootDir, options = {}) {
29
+ const {
30
+ exclude = [],
31
+ maxDepth = 0,
32
+ tsConfigPath,
33
+ includeTypeOnly = true
34
+ } = options;
35
+ const absRootDir = resolve(rootDir);
36
+ const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
37
+ const excludePattern = allExclude.join("|");
38
+ const cruiseOptions = {
39
+ baseDir: absRootDir,
40
+ exclude: { path: excludePattern },
41
+ doNotFollow: { path: "node_modules" },
42
+ maxDepth,
43
+ tsPreCompilationDeps: includeTypeOnly ? true : false,
44
+ combinedDependencies: false
45
+ };
46
+ if (tsConfigPath) {
47
+ cruiseOptions.tsConfig = { fileName: tsConfigPath };
48
+ }
49
+ let result;
50
+ try {
51
+ result = await cruise(["."], cruiseOptions);
52
+ } catch (error) {
53
+ const message = error instanceof Error ? error.message : String(error);
54
+ throw new Error(`dependency-cruiser failed: ${message}`, {
55
+ cause: error
56
+ });
57
+ }
58
+ if (result.exitCode !== 0 && !result.output) {
59
+ throw new Error(`Analysis exited with code ${result.exitCode}`);
60
+ }
61
+ const cruiseResult = result.output;
62
+ return this.buildGraph(absRootDir, cruiseResult);
37
63
  }
38
- let result;
39
- try {
40
- result = await cruise(["."], cruiseOptions);
41
- } catch (error) {
42
- const message = error instanceof Error ? error.message : String(error);
43
- throw new AnalyzerError(
44
- `dependency-cruiser \u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${message}`,
45
- { cause: error }
46
- );
64
+ buildGraph(rootDir, cruiseResult) {
65
+ const files = {};
66
+ const edges = [];
67
+ const circularSet = /* @__PURE__ */ new Set();
68
+ const circularDependencies = [];
69
+ for (const mod of cruiseResult.modules) {
70
+ if (this.isExternalModule(mod)) continue;
71
+ files[mod.source] = {
72
+ path: mod.source,
73
+ exists: !mod.couldNotResolve,
74
+ dependencies: [],
75
+ dependents: []
76
+ };
77
+ }
78
+ for (const mod of cruiseResult.modules) {
79
+ for (const dep of mod.dependencies) {
80
+ if (dep.couldNotResolve || dep.coreModule) continue;
81
+ if (!files[mod.source] || this.isExternalDep(dep)) continue;
82
+ const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
83
+ edges.push({ source: mod.source, target: dep.resolved, type: edgeType });
84
+ if (files[mod.source]) {
85
+ files[mod.source].dependencies.push(dep.resolved);
86
+ }
87
+ if (files[dep.resolved]) {
88
+ files[dep.resolved].dependents.push(mod.source);
89
+ }
90
+ if (dep.circular && dep.cycle) {
91
+ const cyclePath = dep.cycle.map((c) => c.name);
92
+ const cycleKey = [...cyclePath].sort().join("\u2192");
93
+ if (!circularSet.has(cycleKey)) {
94
+ circularSet.add(cycleKey);
95
+ circularDependencies.push({ cycle: cyclePath });
96
+ }
97
+ }
98
+ }
99
+ }
100
+ return {
101
+ rootDir,
102
+ files,
103
+ edges,
104
+ circularDependencies,
105
+ totalFiles: Object.keys(files).length,
106
+ totalEdges: edges.length
107
+ };
47
108
  }
48
- if (result.exitCode !== 0 && !result.output) {
49
- throw new AnalyzerError(
50
- `\u89E3\u6790\u304C\u30A8\u30E9\u30FC\u30B3\u30FC\u30C9 ${result.exitCode} \u3067\u7D42\u4E86\u3057\u307E\u3057\u305F`
51
- );
109
+ isExternalModule(mod) {
110
+ if (mod.coreModule) return true;
111
+ const depTypes = mod.dependencyTypes ?? [];
112
+ if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
113
+ return isExternalPath(mod.source);
114
+ }
115
+ isExternalDep(dep) {
116
+ if (dep.coreModule) return true;
117
+ if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core"))
118
+ return true;
119
+ return isExternalPath(dep.resolved);
52
120
  }
53
- const cruiseResult = result.output;
54
- return buildGraph(absRootDir, cruiseResult);
121
+ };
122
+ function isExternalPath(source) {
123
+ if (source.startsWith("@")) return true;
124
+ if (!source.includes("/") && !source.includes("\\") && !source.includes("."))
125
+ return true;
126
+ if (source.startsWith("node:")) return true;
127
+ return false;
55
128
  }
56
- function buildGraph(rootDir, cruiseResult) {
57
- const files = {};
58
- const edges = [];
59
- const circularSet = /* @__PURE__ */ new Set();
60
- const circularDependencies = [];
61
- for (const mod of cruiseResult.modules) {
62
- if (isExternalModule(mod)) continue;
63
- files[mod.source] = {
64
- path: mod.source,
65
- exists: !mod.couldNotResolve,
66
- dependencies: [],
67
- dependents: []
129
+
130
+ // src/analyzer/engines/regex-engine.ts
131
+ import { readdir, readFile } from "fs/promises";
132
+ import { join, relative, resolve as resolve2 } from "path";
133
+
134
+ // src/analyzer/engines/cycle.ts
135
+ function detectCycles(edges) {
136
+ const adj = /* @__PURE__ */ new Map();
137
+ for (const edge of edges) {
138
+ if (!adj.has(edge.source)) adj.set(edge.source, []);
139
+ adj.get(edge.source).push(edge.target);
140
+ }
141
+ const visited = /* @__PURE__ */ new Set();
142
+ const inStack = /* @__PURE__ */ new Set();
143
+ const cycles = [];
144
+ const cycleKeys = /* @__PURE__ */ new Set();
145
+ function dfs(node, path) {
146
+ if (inStack.has(node)) {
147
+ const cycleStart = path.indexOf(node);
148
+ if (cycleStart !== -1) {
149
+ const cycle = path.slice(cycleStart);
150
+ const key = [...cycle].sort().join("\u2192");
151
+ if (!cycleKeys.has(key)) {
152
+ cycleKeys.add(key);
153
+ cycles.push({ cycle });
154
+ }
155
+ }
156
+ return;
157
+ }
158
+ if (visited.has(node)) return;
159
+ visited.add(node);
160
+ inStack.add(node);
161
+ path.push(node);
162
+ for (const neighbor of adj.get(node) ?? []) {
163
+ dfs(neighbor, path);
164
+ }
165
+ path.pop();
166
+ inStack.delete(node);
167
+ }
168
+ for (const node of adj.keys()) {
169
+ if (!visited.has(node)) {
170
+ dfs(node, []);
171
+ }
172
+ }
173
+ return cycles;
174
+ }
175
+
176
+ // src/analyzer/engines/regex-engine.ts
177
+ var RegexEngine = class {
178
+ constructor(config) {
179
+ this.config = config;
180
+ }
181
+ async analyze(rootDir, options = {}) {
182
+ const absRootDir = resolve2(rootDir);
183
+ const excludePatterns = [
184
+ ...this.config.defaultExclude ?? [],
185
+ ...options.exclude ?? [],
186
+ "\\.archtracker"
187
+ ].map((p) => new RegExp(p));
188
+ const projectFiles = await this.collectFiles(
189
+ absRootDir,
190
+ excludePatterns,
191
+ options.maxDepth ?? 0
192
+ );
193
+ const projectFileSet = new Set(projectFiles);
194
+ const files = {};
195
+ const edges = [];
196
+ for (const filePath of projectFiles) {
197
+ const relPath = relative(absRootDir, filePath);
198
+ files[relPath] = {
199
+ path: relPath,
200
+ exists: true,
201
+ dependencies: [],
202
+ dependents: []
203
+ };
204
+ }
205
+ for (const filePath of projectFiles) {
206
+ const relSource = relative(absRootDir, filePath);
207
+ let content;
208
+ try {
209
+ content = await readFile(filePath, "utf-8");
210
+ } catch {
211
+ continue;
212
+ }
213
+ const imports = this.extractImports(content);
214
+ for (const importPath of imports) {
215
+ const resolved = this.config.resolveImport(
216
+ importPath,
217
+ filePath,
218
+ absRootDir,
219
+ projectFileSet
220
+ );
221
+ if (!resolved) continue;
222
+ const relTarget = relative(absRootDir, resolved);
223
+ if (!files[relTarget]) continue;
224
+ edges.push({
225
+ source: relSource,
226
+ target: relTarget,
227
+ type: "static"
228
+ });
229
+ files[relSource].dependencies.push(relTarget);
230
+ files[relTarget].dependents.push(relSource);
231
+ }
232
+ }
233
+ const circularDependencies = detectCycles(edges);
234
+ return {
235
+ rootDir: absRootDir,
236
+ files,
237
+ edges,
238
+ circularDependencies,
239
+ totalFiles: Object.keys(files).length,
240
+ totalEdges: edges.length
68
241
  };
69
242
  }
70
- for (const mod of cruiseResult.modules) {
71
- for (const dep of mod.dependencies) {
72
- if (dep.couldNotResolve || dep.coreModule) continue;
73
- if (!files[mod.source] || isExternalDep(dep)) continue;
74
- const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
75
- edges.push({
76
- source: mod.source,
77
- target: dep.resolved,
78
- type: edgeType
79
- });
80
- if (files[mod.source]) {
81
- files[mod.source].dependencies.push(dep.resolved);
243
+ extractImports(content) {
244
+ const imports = [];
245
+ for (const pattern of this.config.importPatterns) {
246
+ const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
247
+ let match;
248
+ while ((match = regex.exec(content)) !== null) {
249
+ if (match[1]) {
250
+ imports.push(match[1]);
251
+ }
82
252
  }
83
- if (files[dep.resolved]) {
84
- files[dep.resolved].dependents.push(mod.source);
253
+ }
254
+ return imports;
255
+ }
256
+ async collectFiles(dir, excludePatterns, maxDepth, currentDepth = 0) {
257
+ if (maxDepth > 0 && currentDepth >= maxDepth) return [];
258
+ const results = [];
259
+ let entries;
260
+ try {
261
+ entries = await readdir(dir, { withFileTypes: true });
262
+ } catch {
263
+ return results;
264
+ }
265
+ for (const entry of entries) {
266
+ const fullPath = join(dir, entry.name);
267
+ const relPath = relative(dir, fullPath);
268
+ if (excludePatterns.some(
269
+ (p) => p.test(entry.name) || p.test(relPath) || p.test(fullPath)
270
+ )) {
271
+ continue;
85
272
  }
86
- if (dep.circular && dep.cycle) {
87
- const cyclePath = dep.cycle.map((c) => c.name);
88
- const cycleKey = [...cyclePath].sort().join("\u2192");
89
- if (!circularSet.has(cycleKey)) {
90
- circularSet.add(cycleKey);
91
- circularDependencies.push({ cycle: cyclePath });
273
+ if (entry.isDirectory()) {
274
+ if (entry.name.startsWith(".")) continue;
275
+ const sub = await this.collectFiles(
276
+ fullPath,
277
+ excludePatterns,
278
+ maxDepth,
279
+ currentDepth + 1
280
+ );
281
+ results.push(...sub);
282
+ } else if (entry.isFile()) {
283
+ const dotIdx = entry.name.lastIndexOf(".");
284
+ if (dotIdx > 0) {
285
+ const ext = entry.name.slice(dotIdx);
286
+ if (this.config.extensions.includes(ext)) {
287
+ results.push(fullPath);
288
+ }
92
289
  }
93
290
  }
94
291
  }
292
+ return results;
95
293
  }
96
- return {
97
- rootDir,
98
- files,
99
- edges,
100
- circularDependencies,
101
- totalFiles: Object.keys(files).length,
102
- totalEdges: edges.length
103
- };
294
+ };
295
+
296
+ // src/analyzer/engines/detect.ts
297
+ import { readdir as readdir2, stat as stat2 } from "fs/promises";
298
+ import { join as join2 } from "path";
299
+ var MARKERS = [
300
+ { file: "Cargo.toml", language: "rust" },
301
+ { file: "go.mod", language: "go" },
302
+ { file: "pyproject.toml", language: "python" },
303
+ { file: "setup.py", language: "python" },
304
+ { file: "requirements.txt", language: "python" },
305
+ { file: "Pipfile", language: "python" },
306
+ { file: "pom.xml", language: "java" },
307
+ { file: "build.gradle", language: "java" },
308
+ { file: "build.gradle.kts", language: "kotlin" },
309
+ { file: "Package.swift", language: "swift" },
310
+ { file: "Gemfile", language: "ruby" },
311
+ { file: "composer.json", language: "php" },
312
+ { file: "CMakeLists.txt", language: "c-cpp" },
313
+ { file: "Makefile", language: "c-cpp" },
314
+ { file: "package.json", language: "javascript" },
315
+ { file: "tsconfig.json", language: "javascript" }
316
+ ];
317
+ var EXT_MAP = {
318
+ ".ts": "javascript",
319
+ ".tsx": "javascript",
320
+ ".js": "javascript",
321
+ ".jsx": "javascript",
322
+ ".mjs": "javascript",
323
+ ".cjs": "javascript",
324
+ ".py": "python",
325
+ ".rs": "rust",
326
+ ".go": "go",
327
+ ".java": "java",
328
+ ".c": "c-cpp",
329
+ ".cpp": "c-cpp",
330
+ ".cc": "c-cpp",
331
+ ".cxx": "c-cpp",
332
+ ".h": "c-cpp",
333
+ ".hpp": "c-cpp",
334
+ ".rb": "ruby",
335
+ ".php": "php",
336
+ ".swift": "swift",
337
+ ".kt": "kotlin",
338
+ ".kts": "kotlin"
339
+ };
340
+ async function detectLanguage(rootDir) {
341
+ for (const marker of MARKERS) {
342
+ try {
343
+ const s = await stat2(join2(rootDir, marker.file));
344
+ if (s.isFile() || s.isDirectory()) {
345
+ return marker.language;
346
+ }
347
+ } catch {
348
+ }
349
+ }
350
+ const counts = /* @__PURE__ */ new Map();
351
+ try {
352
+ await scanExtensions(rootDir, counts, 2, 0);
353
+ } catch {
354
+ }
355
+ if (counts.size > 0) {
356
+ let maxLang = "javascript";
357
+ let maxCount = 0;
358
+ for (const [lang, count] of counts) {
359
+ if (count > maxCount) {
360
+ maxCount = count;
361
+ maxLang = lang;
362
+ }
363
+ }
364
+ return maxLang;
365
+ }
366
+ return "javascript";
104
367
  }
105
- function isExternalModule(mod) {
106
- if (mod.coreModule) return true;
107
- const depTypes = mod.dependencyTypes ?? [];
108
- if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
109
- return isExternalPath(mod.source);
368
+ async function scanExtensions(dir, counts, maxDepth, currentDepth) {
369
+ if (currentDepth >= maxDepth) return;
370
+ const entries = await readdir2(dir, { withFileTypes: true });
371
+ for (const entry of entries) {
372
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
373
+ if (entry.isDirectory() && currentDepth < maxDepth - 1) {
374
+ await scanExtensions(
375
+ join2(dir, entry.name),
376
+ counts,
377
+ maxDepth,
378
+ currentDepth + 1
379
+ );
380
+ } else if (entry.isFile()) {
381
+ const dotIdx = entry.name.lastIndexOf(".");
382
+ if (dotIdx > 0) {
383
+ const ext = entry.name.slice(dotIdx);
384
+ const lang = EXT_MAP[ext];
385
+ if (lang) {
386
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
387
+ }
388
+ }
389
+ }
390
+ }
110
391
  }
111
- function isExternalDep(dep) {
112
- if (dep.coreModule) return true;
113
- if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
114
- return isExternalPath(dep.resolved);
392
+
393
+ // src/analyzer/engines/languages.ts
394
+ import { join as join3, dirname, resolve as resolve3 } from "path";
395
+ var python = {
396
+ id: "python",
397
+ extensions: [".py"],
398
+ importPatterns: [
399
+ // from package.module import something
400
+ { regex: /^from\s+(\.[\w.]*|\w[\w.]*)\s+import\b/gm },
401
+ // import package.module
402
+ { regex: /^import\s+([\w.]+)/gm }
403
+ ],
404
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
405
+ if (importPath.startsWith(".")) {
406
+ const dots = importPath.match(/^\.+/)?.[0].length ?? 1;
407
+ let base = dirname(sourceFile);
408
+ for (let i = 1; i < dots; i++) base = dirname(base);
409
+ const rest = importPath.slice(dots).replace(/\./g, "/");
410
+ return tryPythonResolve(join3(base, rest), projectFiles);
411
+ }
412
+ const parts = importPath.replace(/\./g, "/");
413
+ return tryPythonResolve(join3(rootDir, parts), projectFiles);
414
+ },
415
+ defaultExclude: ["__pycache__", "\\.venv", "venv", "\\.egg-info", "dist", "build"]
416
+ };
417
+ function tryPythonResolve(base, projectFiles) {
418
+ if (projectFiles.has(base + ".py")) return base + ".py";
419
+ if (projectFiles.has(join3(base, "__init__.py"))) return join3(base, "__init__.py");
420
+ return null;
115
421
  }
116
- function isExternalPath(source) {
117
- if (source.startsWith("@")) return true;
118
- if (!source.includes("/") && !source.includes("\\") && !source.includes(".")) return true;
119
- if (source.startsWith("node:")) return true;
120
- return false;
422
+ var rust = {
423
+ id: "rust",
424
+ extensions: [".rs"],
425
+ importPatterns: [
426
+ // use crate::module::item;
427
+ { regex: /\buse\s+crate::(\w[\w:]*)/gm },
428
+ // mod child;
429
+ { regex: /\bmod\s+(\w+)\s*;/gm }
430
+ ],
431
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
432
+ const srcDir = join3(rootDir, "src");
433
+ if (!importPath.includes("::")) {
434
+ const parentDir = dirname(sourceFile);
435
+ const asFile = join3(parentDir, importPath + ".rs");
436
+ if (projectFiles.has(asFile)) return asFile;
437
+ const asDir = join3(parentDir, importPath, "mod.rs");
438
+ if (projectFiles.has(asDir)) return asDir;
439
+ return null;
440
+ }
441
+ const segments = importPath.split("::");
442
+ for (let i = segments.length; i > 0; i--) {
443
+ const path = segments.slice(0, i).join("/");
444
+ const asFile = join3(srcDir, path + ".rs");
445
+ if (projectFiles.has(asFile)) return asFile;
446
+ const asDir = join3(srcDir, path, "mod.rs");
447
+ if (projectFiles.has(asDir)) return asDir;
448
+ }
449
+ return null;
450
+ },
451
+ defaultExclude: ["target"]
452
+ };
453
+ var go = {
454
+ id: "go",
455
+ extensions: [".go"],
456
+ importPatterns: [
457
+ // import "path" or import ( "path" )
458
+ { regex: /\bimport\s+(?:\w+\s+)?"([^"]+)"/gm },
459
+ // import block entries
460
+ { regex: /^\s+(?:\w+\s+)?"([^"]+)"/gm }
461
+ ],
462
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
463
+ const modPrefix = goModulePrefix(rootDir);
464
+ if (!modPrefix || !importPath.startsWith(modPrefix)) return null;
465
+ const relPath = importPath.slice(modPrefix.length + 1);
466
+ const pkgDir = join3(rootDir, relPath);
467
+ for (const f of projectFiles) {
468
+ if (f.startsWith(pkgDir + "/") && f.endsWith(".go")) return f;
469
+ }
470
+ return null;
471
+ },
472
+ defaultExclude: ["vendor"]
473
+ };
474
+ var goModCache = /* @__PURE__ */ new Map();
475
+ function goModulePrefix(rootDir) {
476
+ if (goModCache.has(rootDir)) return goModCache.get(rootDir);
477
+ try {
478
+ const fs = __require("fs");
479
+ const content = fs.readFileSync(join3(rootDir, "go.mod"), "utf-8");
480
+ const match = content.match(/^module\s+(.+)$/m);
481
+ const prefix = match ? match[1].trim() : null;
482
+ goModCache.set(rootDir, prefix);
483
+ return prefix;
484
+ } catch {
485
+ goModCache.set(rootDir, null);
486
+ return null;
487
+ }
488
+ }
489
+ var java = {
490
+ id: "java",
491
+ extensions: [".java"],
492
+ importPatterns: [
493
+ // import com.example.ClassName;
494
+ { regex: /^import\s+(?:static\s+)?([\w.]+);/gm }
495
+ ],
496
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
497
+ const filePath = importPath.replace(/\./g, "/") + ".java";
498
+ for (const srcRoot of ["", "src/main/java/", "src/", "app/src/main/java/"]) {
499
+ const full = join3(rootDir, srcRoot, filePath);
500
+ if (projectFiles.has(full)) return full;
501
+ }
502
+ return null;
503
+ },
504
+ defaultExclude: ["build", "target", "\\.gradle", "\\.idea"]
505
+ };
506
+ var cCpp = {
507
+ id: "c-cpp",
508
+ extensions: [".c", ".cpp", ".cc", ".cxx", ".h", ".hpp"],
509
+ importPatterns: [
510
+ // #include "file.h" (skip <system> includes)
511
+ { regex: /^#include\s+"([^"]+)"/gm }
512
+ ],
513
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
514
+ const fromSource = resolve3(dirname(sourceFile), importPath);
515
+ if (projectFiles.has(fromSource)) return fromSource;
516
+ const fromRoot = join3(rootDir, importPath);
517
+ if (projectFiles.has(fromRoot)) return fromRoot;
518
+ for (const incDir of ["include", "src"]) {
519
+ const full = join3(rootDir, incDir, importPath);
520
+ if (projectFiles.has(full)) return full;
521
+ }
522
+ return null;
523
+ },
524
+ defaultExclude: ["build", "cmake-build", "\\.o$", "\\.obj$"]
525
+ };
526
+ var ruby = {
527
+ id: "ruby",
528
+ extensions: [".rb"],
529
+ importPatterns: [
530
+ // require_relative 'path'
531
+ { regex: /\brequire_relative\s+['"]([^'"]+)['"]/gm },
532
+ // require 'path' (for project-internal requires)
533
+ { regex: /\brequire\s+['"]([^'"]+)['"]/gm }
534
+ ],
535
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
536
+ const withExt = importPath.endsWith(".rb") ? importPath : importPath + ".rb";
537
+ const fromSource = resolve3(dirname(sourceFile), withExt);
538
+ if (projectFiles.has(fromSource)) return fromSource;
539
+ const fromRoot = join3(rootDir, withExt);
540
+ if (projectFiles.has(fromRoot)) return fromRoot;
541
+ const fromLib = join3(rootDir, "lib", withExt);
542
+ if (projectFiles.has(fromLib)) return fromLib;
543
+ return null;
544
+ },
545
+ defaultExclude: ["vendor", "\\.bundle"]
546
+ };
547
+ var php = {
548
+ id: "php",
549
+ extensions: [".php"],
550
+ importPatterns: [
551
+ // require/include/require_once/include_once 'path'
552
+ { regex: /\b(?:require|include)(?:_once)?\s+['"]([^'"]+)['"]/gm },
553
+ // use Namespace\Class (PSR-4 style)
554
+ { regex: /^use\s+([\w\\]+)/gm }
555
+ ],
556
+ resolveImport(importPath, sourceFile, rootDir, projectFiles) {
557
+ if (importPath.includes("/") || importPath.endsWith(".php")) {
558
+ const withExt = importPath.endsWith(".php") ? importPath : importPath + ".php";
559
+ const fromSource = resolve3(dirname(sourceFile), withExt);
560
+ if (projectFiles.has(fromSource)) return fromSource;
561
+ const fromRoot2 = join3(rootDir, withExt);
562
+ if (projectFiles.has(fromRoot2)) return fromRoot2;
563
+ return null;
564
+ }
565
+ const filePath = importPath.replace(/\\/g, "/") + ".php";
566
+ const fromRoot = join3(rootDir, filePath);
567
+ if (projectFiles.has(fromRoot)) return fromRoot;
568
+ const fromSrc = join3(rootDir, "src", filePath);
569
+ if (projectFiles.has(fromSrc)) return fromSrc;
570
+ return null;
571
+ },
572
+ defaultExclude: ["vendor"]
573
+ };
574
+ var swift = {
575
+ id: "swift",
576
+ extensions: [".swift"],
577
+ importPatterns: [
578
+ // import ModuleName
579
+ { regex: /^import\s+(?:class|struct|enum|protocol|func|var|let|typealias)?\s*(\w+)/gm }
580
+ ],
581
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
582
+ const spmDir = join3(rootDir, "Sources", importPath);
583
+ for (const f of projectFiles) {
584
+ if (f.startsWith(spmDir + "/") && f.endsWith(".swift")) return f;
585
+ }
586
+ return null;
587
+ },
588
+ defaultExclude: ["\\.build", "DerivedData"]
589
+ };
590
+ var kotlin = {
591
+ id: "kotlin",
592
+ extensions: [".kt", ".kts"],
593
+ importPatterns: [
594
+ // import com.example.ClassName
595
+ { regex: /^import\s+([\w.]+)/gm }
596
+ ],
597
+ resolveImport(importPath, _sourceFile, rootDir, projectFiles) {
598
+ const filePath = importPath.replace(/\./g, "/");
599
+ for (const ext of [".kt", ".kts"]) {
600
+ for (const srcRoot of [
601
+ "",
602
+ "src/main/kotlin/",
603
+ "src/main/java/",
604
+ "src/",
605
+ "app/src/main/kotlin/",
606
+ "app/src/main/java/"
607
+ ]) {
608
+ const full = join3(rootDir, srcRoot, filePath + ext);
609
+ if (projectFiles.has(full)) return full;
610
+ }
611
+ }
612
+ return null;
613
+ },
614
+ defaultExclude: ["build", "\\.gradle", "\\.idea"]
615
+ };
616
+ var LANGUAGE_CONFIGS = {
617
+ javascript: null,
618
+ // handled by DependencyCruiserEngine
619
+ python,
620
+ rust,
621
+ go,
622
+ java,
623
+ "c-cpp": cCpp,
624
+ ruby,
625
+ php,
626
+ swift,
627
+ kotlin
628
+ };
629
+ function getLanguageConfig(id) {
630
+ return LANGUAGE_CONFIGS[id] ?? null;
631
+ }
632
+
633
+ // src/analyzer/analyze.ts
634
+ async function analyzeProject(rootDir, options = {}) {
635
+ const absRootDir = resolve4(rootDir);
636
+ const language = options.language ?? await detectLanguage(absRootDir);
637
+ try {
638
+ if (language === "javascript") {
639
+ return await new DependencyCruiserEngine().analyze(absRootDir, options);
640
+ }
641
+ const config = getLanguageConfig(language);
642
+ if (!config) {
643
+ throw new AnalyzerError(`No analyzer config for language: ${language}`);
644
+ }
645
+ return await new RegexEngine(config).analyze(absRootDir, options);
646
+ } catch (error) {
647
+ if (error instanceof AnalyzerError) throw error;
648
+ const message = error instanceof Error ? error.message : String(error);
649
+ throw new AnalyzerError(message, { cause: error });
650
+ }
121
651
  }
122
652
  var AnalyzerError = class extends Error {
123
653
  constructor(message, options) {
@@ -436,8 +966,8 @@ function formatAnalysisReport(graph, options = {}) {
436
966
  }
437
967
 
438
968
  // src/storage/snapshot.ts
439
- import { mkdir, writeFile, readFile, access } from "fs/promises";
440
- import { join } from "path";
969
+ import { mkdir, writeFile, readFile as readFile2, access } from "fs/promises";
970
+ import { join as join4 } from "path";
441
971
  import { z } from "zod";
442
972
 
443
973
  // src/types/schema.ts
@@ -471,8 +1001,8 @@ var SnapshotSchema = z.object({
471
1001
  graph: DependencyGraphSchema
472
1002
  });
473
1003
  async function saveSnapshot(projectRoot, graph) {
474
- const dirPath = join(projectRoot, ARCHTRACKER_DIR);
475
- const filePath = join(dirPath, SNAPSHOT_FILE);
1004
+ const dirPath = join4(projectRoot, ARCHTRACKER_DIR);
1005
+ const filePath = join4(dirPath, SNAPSHOT_FILE);
476
1006
  const snapshot = {
477
1007
  version: SCHEMA_VERSION,
478
1008
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -484,10 +1014,10 @@ async function saveSnapshot(projectRoot, graph) {
484
1014
  return snapshot;
485
1015
  }
486
1016
  async function loadSnapshot(projectRoot) {
487
- const filePath = join(projectRoot, ARCHTRACKER_DIR, SNAPSHOT_FILE);
1017
+ const filePath = join4(projectRoot, ARCHTRACKER_DIR, SNAPSHOT_FILE);
488
1018
  let raw;
489
1019
  try {
490
- raw = await readFile(filePath, "utf-8");
1020
+ raw = await readFile2(filePath, "utf-8");
491
1021
  } catch (error) {
492
1022
  if (isNodeError(error) && error.code === "ENOENT") {
493
1023
  return null;
@@ -622,10 +1152,10 @@ function arraysEqual(a, b) {
622
1152
  }
623
1153
 
624
1154
  // src/utils/path-guard.ts
625
- import { resolve as resolve2 } from "path";
1155
+ import { resolve as resolve5 } from "path";
626
1156
  function validatePath(inputPath, boundary) {
627
- const resolved = resolve2(inputPath);
628
- const root = boundary ? resolve2(boundary) : process.cwd();
1157
+ const resolved = resolve5(inputPath);
1158
+ const root = boundary ? resolve5(boundary) : process.cwd();
629
1159
  if (!resolved.startsWith(root)) {
630
1160
  throw new PathTraversalError(
631
1161
  t("pathGuard.traversal", { input: inputPath, resolved, boundary: root })
@@ -643,20 +1173,21 @@ var PathTraversalError = class extends Error {
643
1173
  // src/mcp/index.ts
644
1174
  var server = new McpServer({
645
1175
  name: "archtracker",
646
- version: "0.1.0"
1176
+ version: "0.2.0"
647
1177
  });
648
1178
  server.tool(
649
1179
  "generate_map",
650
- "Analyze dependency graph of a directory and return file import/export structure as JSON",
1180
+ "Analyze dependency graph of a directory and return file import/export structure as JSON. Supports JS/TS, Python, Rust, Go, Java, C/C++, Ruby, PHP, Swift, Kotlin.",
651
1181
  {
652
1182
  targetDir: z2.string().default("src").describe("Target directory path (default: src)"),
653
1183
  exclude: z2.array(z2.string()).optional().describe("Array of regex patterns to exclude (e.g. ['test', 'mock'])"),
654
- maxDepth: z2.number().int().min(0).optional().describe("Max analysis depth (0 = unlimited)")
1184
+ maxDepth: z2.number().int().min(0).optional().describe("Max analysis depth (0 = unlimited)"),
1185
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
655
1186
  },
656
- async ({ targetDir, exclude, maxDepth }) => {
1187
+ async ({ targetDir, exclude, maxDepth, language }) => {
657
1188
  try {
658
1189
  validatePath(targetDir);
659
- const graph = await analyzeProject(targetDir, { exclude, maxDepth });
1190
+ const graph = await analyzeProject(targetDir, { exclude, maxDepth, language });
660
1191
  const summary = [
661
1192
  t("mcp.analyzeComplete", { files: graph.totalFiles, edges: graph.totalEdges }),
662
1193
  graph.circularDependencies.length > 0 ? t("mcp.circularFound", { count: graph.circularDependencies.length }) : t("mcp.circularNone")
@@ -674,18 +1205,19 @@ server.tool(
674
1205
  );
675
1206
  server.tool(
676
1207
  "analyze_existing_architecture",
677
- "Comprehensive architecture analysis for existing projects. Shows critical components, circular dependencies, orphan files, coupling hotspots, and directory breakdown.",
1208
+ "Comprehensive architecture analysis for existing projects. Shows critical components, circular dependencies, orphan files, coupling hotspots, and directory breakdown. Supports 10 languages.",
678
1209
  {
679
1210
  targetDir: z2.string().default("src").describe("Target directory path (default: src)"),
680
1211
  exclude: z2.array(z2.string()).optional().describe("Array of regex patterns to exclude"),
681
1212
  topN: z2.number().int().min(1).max(50).optional().describe("Number of top items to show in each section (default: 10)"),
682
1213
  saveSnapshot: z2.boolean().optional().describe("Also save a snapshot after analysis (default: false)"),
683
- projectRoot: z2.string().default(".").describe("Project root (needed only when saveSnapshot is true)")
1214
+ projectRoot: z2.string().default(".").describe("Project root (needed only when saveSnapshot is true)"),
1215
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
684
1216
  },
685
- async ({ targetDir, exclude, topN, saveSnapshot: doSave, projectRoot }) => {
1217
+ async ({ targetDir, exclude, topN, saveSnapshot: doSave, projectRoot, language }) => {
686
1218
  try {
687
1219
  validatePath(targetDir);
688
- const graph = await analyzeProject(targetDir, { exclude });
1220
+ const graph = await analyzeProject(targetDir, { exclude, language });
689
1221
  const report = formatAnalysisReport(graph, { topN: topN ?? 10 });
690
1222
  const content = [
691
1223
  { type: "text", text: report }
@@ -706,13 +1238,14 @@ server.tool(
706
1238
  "Save the current dependency graph as a snapshot to .archtracker/snapshot.json",
707
1239
  {
708
1240
  targetDir: z2.string().default("src").describe("Target directory path"),
709
- projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)")
1241
+ projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)"),
1242
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
710
1243
  },
711
- async ({ targetDir, projectRoot }) => {
1244
+ async ({ targetDir, projectRoot, language }) => {
712
1245
  try {
713
1246
  validatePath(targetDir);
714
1247
  validatePath(projectRoot);
715
- const graph = await analyzeProject(targetDir);
1248
+ const graph = await analyzeProject(targetDir, { language });
716
1249
  const snapshot = await saveSnapshot(projectRoot, graph);
717
1250
  const keyComponents = Object.values(graph.files).sort((a, b) => b.dependents.length - a.dependents.length).slice(0, 5).map((f) => ` ${t("cli.dependedBy", { path: f.path, count: f.dependents.length })}`);
718
1251
  const report = [
@@ -735,15 +1268,16 @@ server.tool(
735
1268
  "Compare saved snapshot with current code dependencies and warn about files that may need updates",
736
1269
  {
737
1270
  targetDir: z2.string().default("src").describe("Target directory path"),
738
- projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)")
1271
+ projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)"),
1272
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
739
1273
  },
740
- async ({ targetDir, projectRoot }) => {
1274
+ async ({ targetDir, projectRoot, language }) => {
741
1275
  try {
742
1276
  validatePath(targetDir);
743
1277
  validatePath(projectRoot);
744
1278
  const existingSnapshot = await loadSnapshot(projectRoot);
745
1279
  if (!existingSnapshot) {
746
- const graph = await analyzeProject(targetDir);
1280
+ const graph = await analyzeProject(targetDir, { language });
747
1281
  await saveSnapshot(projectRoot, graph);
748
1282
  return {
749
1283
  content: [
@@ -758,7 +1292,7 @@ server.tool(
758
1292
  ]
759
1293
  };
760
1294
  }
761
- const currentGraph = await analyzeProject(targetDir);
1295
+ const currentGraph = await analyzeProject(targetDir, { language });
762
1296
  const diff = computeDiff(existingSnapshot.graph, currentGraph);
763
1297
  const report = formatDiffReport(diff);
764
1298
  return { content: [{ type: "text", text: report }] };
@@ -772,13 +1306,14 @@ server.tool(
772
1306
  "Get current valid file paths and architecture summary for AI session initialization",
773
1307
  {
774
1308
  targetDir: z2.string().default("src").describe("Target directory path"),
775
- projectRoot: z2.string().default(".").describe("Project root")
1309
+ projectRoot: z2.string().default(".").describe("Project root"),
1310
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
776
1311
  },
777
- async ({ targetDir, projectRoot }) => {
1312
+ async ({ targetDir, projectRoot, language }) => {
778
1313
  try {
779
1314
  let snapshot = await loadSnapshot(projectRoot);
780
1315
  if (!snapshot) {
781
- const graph2 = await analyzeProject(targetDir);
1316
+ const graph2 = await analyzeProject(targetDir, { language });
782
1317
  snapshot = await saveSnapshot(projectRoot, graph2);
783
1318
  }
784
1319
  const graph = snapshot.graph;
@@ -831,15 +1366,16 @@ server.tool(
831
1366
  ),
832
1367
  targetDir: z2.string().default("src").describe("Target directory path"),
833
1368
  projectRoot: z2.string().default(".").describe("Project root"),
834
- limit: z2.number().int().min(1).max(50).optional().describe("Max results (default: 10)")
1369
+ limit: z2.number().int().min(1).max(50).optional().describe("Max results (default: 10)"),
1370
+ language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "ruby", "php", "swift", "kotlin"]).optional().describe("Target language (auto-detected if omitted)")
835
1371
  },
836
- async ({ query, mode, targetDir, projectRoot, limit }) => {
1372
+ async ({ query, mode, targetDir, projectRoot, limit, language }) => {
837
1373
  try {
838
1374
  validatePath(targetDir);
839
1375
  validatePath(projectRoot);
840
1376
  let snapshot = await loadSnapshot(projectRoot);
841
1377
  if (!snapshot) {
842
- const graph2 = await analyzeProject(targetDir);
1378
+ const graph2 = await analyzeProject(targetDir, { language });
843
1379
  snapshot = await saveSnapshot(projectRoot, graph2);
844
1380
  }
845
1381
  const graph = snapshot.graph;