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/README.md +8 -4
- package/dist/cli/index.js +127 -144
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +5 -10
- package/dist/index.js +59 -128
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +115 -140
- package/dist/mcp/index.js.map +1 -1
- package/package.json +2 -5
- package/scripts/postinstall.cjs +0 -45
- package/scripts/screenshot.mjs +0 -121
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ When AI agents modify code, they **miss cascading impacts**:
|
|
|
39
39
|
|
|
40
40
|
## Features
|
|
41
41
|
|
|
42
|
-
- **Dependency Graph Analysis** —
|
|
42
|
+
- **Dependency Graph Analysis** — Regex-based static analysis for **13 languages** (JS/TS, Python, Rust, Go, Java, C/C++, C#, Ruby, PHP, Swift, Kotlin, Dart, Scala)
|
|
43
43
|
- **Interactive Web Viewer** — Force-directed graph, hierarchy diagram, diff view with D3.js
|
|
44
44
|
- **Impact Simulation** — Click any file to visualize transitive dependents (BFS traversal)
|
|
45
45
|
- **Snapshot Diffing** — Save architecture snapshots and detect drift over time
|
|
@@ -156,6 +156,7 @@ archtracker ci-setup [options] Generate GitHub Actions workflow
|
|
|
156
156
|
Options:
|
|
157
157
|
-t, --target <dir> Target directory (default: "src")
|
|
158
158
|
-r, --root <dir> Project root (default: ".")
|
|
159
|
+
-l, --language <lang> Target language (auto-detected if omitted)
|
|
159
160
|
-p, --port <number> Port for web viewer (default: 3000)
|
|
160
161
|
-w, --watch Watch for file changes and auto-reload
|
|
161
162
|
-e, --exclude <pattern> Exclude patterns (regex)
|
|
@@ -251,7 +252,8 @@ The web viewer also supports language switching via the settings panel.
|
|
|
251
252
|
## Requirements
|
|
252
253
|
|
|
253
254
|
- **Node.js** >= 18.0.0
|
|
254
|
-
|
|
255
|
+
|
|
256
|
+
Supported languages: JavaScript/TypeScript, Python, Rust, Go, Java, C/C++, C#, Ruby, PHP, Swift, Kotlin, Dart, Scala
|
|
255
257
|
|
|
256
258
|
## Contributing
|
|
257
259
|
|
|
@@ -287,7 +289,7 @@ AI エージェントがコードを修正する際、**波及的な影響を見
|
|
|
287
289
|
|
|
288
290
|
## 機能
|
|
289
291
|
|
|
290
|
-
- **依存関係グラフ分析** —
|
|
292
|
+
- **依存関係グラフ分析** — 正規表現ベースの静的解析、**13言語**対応(JS/TS, Python, Rust, Go, Java, C/C++, C#, Ruby, PHP, Swift, Kotlin, Dart, Scala)
|
|
291
293
|
- **インタラクティブ Web ビューア** — D3.js による力学モデルグラフ、階層図、差分ビュー
|
|
292
294
|
- **影響シミュレーション** — ファイルをクリックして推移的な被依存ファイルを可視化(BFS探索)
|
|
293
295
|
- **スナップショット差分** — アーキテクチャスナップショットを保存し、ドリフトを検出
|
|
@@ -404,6 +406,7 @@ archtracker ci-setup [options] GitHub Actions ワークフローを生成
|
|
|
404
406
|
オプション:
|
|
405
407
|
-t, --target <dir> 対象ディレクトリ(デフォルト: "src")
|
|
406
408
|
-r, --root <dir> プロジェクトルート(デフォルト: ".")
|
|
409
|
+
-l, --language <lang> 対象言語(省略時は自動検出)
|
|
407
410
|
-p, --port <number> Web ビューアのポート(デフォルト: 3000)
|
|
408
411
|
-w, --watch ファイル変更の監視と自動リロード
|
|
409
412
|
-e, --exclude <pattern> 除外パターン(正規表現)
|
|
@@ -479,7 +482,8 @@ Web ビューアでも設定パネルから言語を切り替え可能です。
|
|
|
479
482
|
## 動作要件
|
|
480
483
|
|
|
481
484
|
- **Node.js** >= 18.0.0
|
|
482
|
-
|
|
485
|
+
|
|
486
|
+
対応言語: JavaScript/TypeScript, Python, Rust, Go, Java, C/C++, C#, Ruby, PHP, Swift, Kotlin, Dart, Scala
|
|
483
487
|
|
|
484
488
|
## コントリビュート
|
|
485
489
|
|
package/dist/cli/index.js
CHANGED
|
@@ -4,128 +4,15 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { watch } from "fs";
|
|
6
6
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
7
|
-
import { join as
|
|
7
|
+
import { join as join6 } from "path";
|
|
8
8
|
|
|
9
9
|
// src/analyzer/analyze.ts
|
|
10
|
-
import { resolve as
|
|
11
|
-
|
|
12
|
-
// src/analyzer/engines/dependency-cruiser.ts
|
|
13
|
-
import { resolve } from "path";
|
|
14
|
-
import { cruise } from "dependency-cruiser";
|
|
15
|
-
var DEFAULT_EXCLUDE = [
|
|
16
|
-
"node_modules",
|
|
17
|
-
"\\.d\\.ts$",
|
|
18
|
-
"dist",
|
|
19
|
-
"build",
|
|
20
|
-
"coverage",
|
|
21
|
-
"\\.archtracker"
|
|
22
|
-
];
|
|
23
|
-
var DependencyCruiserEngine = class {
|
|
24
|
-
async analyze(rootDir, options = {}) {
|
|
25
|
-
const {
|
|
26
|
-
exclude = [],
|
|
27
|
-
maxDepth = 0,
|
|
28
|
-
tsConfigPath,
|
|
29
|
-
includeTypeOnly = true
|
|
30
|
-
} = options;
|
|
31
|
-
const absRootDir = resolve(rootDir);
|
|
32
|
-
const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
|
|
33
|
-
const excludePattern = allExclude.join("|");
|
|
34
|
-
const cruiseOptions = {
|
|
35
|
-
baseDir: absRootDir,
|
|
36
|
-
exclude: { path: excludePattern },
|
|
37
|
-
doNotFollow: { path: "node_modules" },
|
|
38
|
-
maxDepth,
|
|
39
|
-
tsPreCompilationDeps: includeTypeOnly ? true : false,
|
|
40
|
-
combinedDependencies: false
|
|
41
|
-
};
|
|
42
|
-
if (tsConfigPath) {
|
|
43
|
-
cruiseOptions.tsConfig = { fileName: tsConfigPath };
|
|
44
|
-
}
|
|
45
|
-
let result;
|
|
46
|
-
try {
|
|
47
|
-
result = await cruise(["."], cruiseOptions);
|
|
48
|
-
} catch (error) {
|
|
49
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
-
throw new Error(`dependency-cruiser failed: ${message}`, {
|
|
51
|
-
cause: error
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (result.exitCode !== 0 && !result.output) {
|
|
55
|
-
throw new Error(`Analysis exited with code ${result.exitCode}`);
|
|
56
|
-
}
|
|
57
|
-
const cruiseResult = result.output;
|
|
58
|
-
return this.buildGraph(absRootDir, cruiseResult);
|
|
59
|
-
}
|
|
60
|
-
buildGraph(rootDir, cruiseResult) {
|
|
61
|
-
const files = {};
|
|
62
|
-
const edges = [];
|
|
63
|
-
const circularSet = /* @__PURE__ */ new Set();
|
|
64
|
-
const circularDependencies = [];
|
|
65
|
-
for (const mod of cruiseResult.modules) {
|
|
66
|
-
if (this.isExternalModule(mod)) continue;
|
|
67
|
-
files[mod.source] = {
|
|
68
|
-
path: mod.source,
|
|
69
|
-
exists: !mod.couldNotResolve,
|
|
70
|
-
dependencies: [],
|
|
71
|
-
dependents: []
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
for (const mod of cruiseResult.modules) {
|
|
75
|
-
for (const dep of mod.dependencies) {
|
|
76
|
-
if (dep.couldNotResolve || dep.coreModule) continue;
|
|
77
|
-
if (!files[mod.source] || this.isExternalDep(dep)) continue;
|
|
78
|
-
const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
|
|
79
|
-
edges.push({ source: mod.source, target: dep.resolved, type: edgeType });
|
|
80
|
-
if (files[mod.source]) {
|
|
81
|
-
files[mod.source].dependencies.push(dep.resolved);
|
|
82
|
-
}
|
|
83
|
-
if (files[dep.resolved]) {
|
|
84
|
-
files[dep.resolved].dependents.push(mod.source);
|
|
85
|
-
}
|
|
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 });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
rootDir,
|
|
98
|
-
files,
|
|
99
|
-
edges,
|
|
100
|
-
circularDependencies,
|
|
101
|
-
totalFiles: Object.keys(files).length,
|
|
102
|
-
totalEdges: edges.length
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
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);
|
|
110
|
-
}
|
|
111
|
-
isExternalDep(dep) {
|
|
112
|
-
if (dep.coreModule) return true;
|
|
113
|
-
if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core"))
|
|
114
|
-
return true;
|
|
115
|
-
return isExternalPath(dep.resolved);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
function isExternalPath(source) {
|
|
119
|
-
if (source.startsWith("@")) return true;
|
|
120
|
-
if (!source.includes("/") && !source.includes("\\") && !source.includes("."))
|
|
121
|
-
return true;
|
|
122
|
-
if (source.startsWith("node:")) return true;
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
10
|
+
import { resolve as resolve3 } from "path";
|
|
11
|
+
import { stat as stat3 } from "fs/promises";
|
|
125
12
|
|
|
126
13
|
// src/analyzer/engines/regex-engine.ts
|
|
127
14
|
import { readdir, readFile } from "fs/promises";
|
|
128
|
-
import { join, relative, resolve
|
|
15
|
+
import { join, relative, resolve } from "path";
|
|
129
16
|
|
|
130
17
|
// src/analyzer/engines/cycle.ts
|
|
131
18
|
function detectCycles(edges) {
|
|
@@ -683,7 +570,7 @@ var RegexEngine = class {
|
|
|
683
570
|
this.config = config;
|
|
684
571
|
}
|
|
685
572
|
async analyze(rootDir, options = {}) {
|
|
686
|
-
const absRootDir =
|
|
573
|
+
const absRootDir = resolve(rootDir);
|
|
687
574
|
const excludePatterns = [
|
|
688
575
|
...this.config.defaultExclude ?? [],
|
|
689
576
|
...options.exclude ?? [],
|
|
@@ -930,7 +817,26 @@ async function scanExtensions(dir, counts, maxDepth, currentDepth) {
|
|
|
930
817
|
|
|
931
818
|
// src/analyzer/engines/languages.ts
|
|
932
819
|
import { readFileSync } from "fs";
|
|
933
|
-
import { join as join3, dirname, resolve as
|
|
820
|
+
import { join as join3, dirname, resolve as resolve2 } from "path";
|
|
821
|
+
|
|
822
|
+
// src/analyzer/engines/types.ts
|
|
823
|
+
var LANGUAGE_IDS = [
|
|
824
|
+
"javascript",
|
|
825
|
+
"python",
|
|
826
|
+
"rust",
|
|
827
|
+
"go",
|
|
828
|
+
"java",
|
|
829
|
+
"c-cpp",
|
|
830
|
+
"c-sharp",
|
|
831
|
+
"ruby",
|
|
832
|
+
"php",
|
|
833
|
+
"swift",
|
|
834
|
+
"kotlin",
|
|
835
|
+
"dart",
|
|
836
|
+
"scala"
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
// src/analyzer/engines/languages.ts
|
|
934
840
|
var python = {
|
|
935
841
|
id: "python",
|
|
936
842
|
extensions: [".py"],
|
|
@@ -1194,7 +1100,7 @@ var cCpp = {
|
|
|
1194
1100
|
{ regex: /^#include\s+"([^"]+)"/gm }
|
|
1195
1101
|
],
|
|
1196
1102
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1197
|
-
const fromSource =
|
|
1103
|
+
const fromSource = resolve2(dirname(sourceFile), importPath);
|
|
1198
1104
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1199
1105
|
const fromRoot = join3(rootDir, importPath);
|
|
1200
1106
|
if (projectFiles.has(fromRoot)) return fromRoot;
|
|
@@ -1218,7 +1124,7 @@ var ruby = {
|
|
|
1218
1124
|
],
|
|
1219
1125
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1220
1126
|
const withExt = importPath.endsWith(".rb") ? importPath : importPath + ".rb";
|
|
1221
|
-
const fromSource =
|
|
1127
|
+
const fromSource = resolve2(dirname(sourceFile), withExt);
|
|
1222
1128
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1223
1129
|
const fromRoot = join3(rootDir, withExt);
|
|
1224
1130
|
if (projectFiles.has(fromRoot)) return fromRoot;
|
|
@@ -1241,7 +1147,7 @@ var php = {
|
|
|
1241
1147
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1242
1148
|
if (importPath.includes("/") || importPath.endsWith(".php")) {
|
|
1243
1149
|
const withExt = importPath.endsWith(".php") ? importPath : importPath + ".php";
|
|
1244
|
-
const fromSource =
|
|
1150
|
+
const fromSource = resolve2(dirname(sourceFile), withExt);
|
|
1245
1151
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1246
1152
|
const fromRoot2 = join3(rootDir, withExt);
|
|
1247
1153
|
if (projectFiles.has(fromRoot2)) return fromRoot2;
|
|
@@ -1361,7 +1267,7 @@ var dart = {
|
|
|
1361
1267
|
if (projectFiles.has(full)) return full;
|
|
1362
1268
|
return null;
|
|
1363
1269
|
}
|
|
1364
|
-
const resolved =
|
|
1270
|
+
const resolved = resolve2(dirname(sourceFile), importPath);
|
|
1365
1271
|
if (projectFiles.has(resolved)) return resolved;
|
|
1366
1272
|
return null;
|
|
1367
1273
|
},
|
|
@@ -1425,9 +1331,47 @@ var scala = {
|
|
|
1425
1331
|
},
|
|
1426
1332
|
defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
|
|
1427
1333
|
};
|
|
1334
|
+
var javascript = {
|
|
1335
|
+
id: "javascript",
|
|
1336
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
1337
|
+
commentStyle: "c-style",
|
|
1338
|
+
importPatterns: [
|
|
1339
|
+
// ES6: import [type] [stuff from] "path"
|
|
1340
|
+
{ regex: /import\s+(?:type\s+)?(?:[\w*{}\s,]+\s+from\s+)?["']([^"']+)["']/g },
|
|
1341
|
+
// Dynamic: import("path")
|
|
1342
|
+
{ regex: /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g },
|
|
1343
|
+
// Re-export: export [type] { stuff } from "path" / export * from "path"
|
|
1344
|
+
{ regex: /export\s+(?:type\s+)?(?:\{[^}]*\}|\*(?:\s+as\s+\w+)?)\s+from\s+["']([^"']+)["']/g },
|
|
1345
|
+
// CommonJS: require("path")
|
|
1346
|
+
{ regex: /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g }
|
|
1347
|
+
],
|
|
1348
|
+
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1349
|
+
if (importPath.startsWith("node:")) return null;
|
|
1350
|
+
if (!importPath.startsWith(".")) return null;
|
|
1351
|
+
const resolved = resolve2(dirname(sourceFile), importPath);
|
|
1352
|
+
if (projectFiles.has(resolved)) return resolved;
|
|
1353
|
+
if (resolved.endsWith(".js")) {
|
|
1354
|
+
const tsPath = resolved.slice(0, -3) + ".ts";
|
|
1355
|
+
if (projectFiles.has(tsPath)) return tsPath;
|
|
1356
|
+
const tsxPath = resolved.slice(0, -3) + ".tsx";
|
|
1357
|
+
if (projectFiles.has(tsxPath)) return tsxPath;
|
|
1358
|
+
}
|
|
1359
|
+
if (resolved.endsWith(".jsx")) {
|
|
1360
|
+
const tsxPath = resolved.slice(0, -4) + ".tsx";
|
|
1361
|
+
if (projectFiles.has(tsxPath)) return tsxPath;
|
|
1362
|
+
}
|
|
1363
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]) {
|
|
1364
|
+
if (projectFiles.has(resolved + ext)) return resolved + ext;
|
|
1365
|
+
}
|
|
1366
|
+
for (const idx of ["/index.ts", "/index.tsx", "/index.js", "/index.jsx"]) {
|
|
1367
|
+
if (projectFiles.has(resolved + idx)) return resolved + idx;
|
|
1368
|
+
}
|
|
1369
|
+
return null;
|
|
1370
|
+
},
|
|
1371
|
+
defaultExclude: ["node_modules", "\\.d\\.ts$", "dist", "build", "coverage"]
|
|
1372
|
+
};
|
|
1428
1373
|
var LANGUAGE_CONFIGS = {
|
|
1429
|
-
javascript
|
|
1430
|
-
// handled by DependencyCruiserEngine
|
|
1374
|
+
javascript,
|
|
1431
1375
|
python,
|
|
1432
1376
|
rust,
|
|
1433
1377
|
go,
|
|
@@ -1447,12 +1391,18 @@ function getLanguageConfig(id) {
|
|
|
1447
1391
|
|
|
1448
1392
|
// src/analyzer/analyze.ts
|
|
1449
1393
|
async function analyzeProject(rootDir, options = {}) {
|
|
1450
|
-
const absRootDir =
|
|
1451
|
-
const language = options.language ?? await detectLanguage(absRootDir);
|
|
1394
|
+
const absRootDir = resolve3(rootDir);
|
|
1452
1395
|
try {
|
|
1453
|
-
|
|
1454
|
-
|
|
1396
|
+
const s = await stat3(absRootDir);
|
|
1397
|
+
if (!s.isDirectory()) {
|
|
1398
|
+
throw new AnalyzerError(`Not a directory: ${absRootDir}`);
|
|
1455
1399
|
}
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
if (error instanceof AnalyzerError) throw error;
|
|
1402
|
+
throw new AnalyzerError(`Directory not found: ${absRootDir}`, { cause: error });
|
|
1403
|
+
}
|
|
1404
|
+
const language = options.language ?? await detectLanguage(absRootDir);
|
|
1405
|
+
try {
|
|
1456
1406
|
const config = getLanguageConfig(language);
|
|
1457
1407
|
if (!config) {
|
|
1458
1408
|
throw new AnalyzerError(`No analyzer config for language: ${language}`);
|
|
@@ -2981,11 +2931,30 @@ function startViewer(graph, options = {}) {
|
|
|
2981
2931
|
};
|
|
2982
2932
|
}
|
|
2983
2933
|
|
|
2934
|
+
// src/utils/version.ts
|
|
2935
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2936
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
2937
|
+
import { fileURLToPath } from "url";
|
|
2938
|
+
function loadVersion() {
|
|
2939
|
+
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
2940
|
+
for (let i = 0; i < 5; i++) {
|
|
2941
|
+
try {
|
|
2942
|
+
const pkg = JSON.parse(readFileSync2(join5(dir, "package.json"), "utf-8"));
|
|
2943
|
+
return pkg.version;
|
|
2944
|
+
} catch {
|
|
2945
|
+
dir = dirname2(dir);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
return "0.0.0";
|
|
2949
|
+
}
|
|
2950
|
+
var VERSION = loadVersion();
|
|
2951
|
+
|
|
2984
2952
|
// src/cli/index.ts
|
|
2953
|
+
var VALID_LANGUAGES = LANGUAGE_IDS;
|
|
2985
2954
|
var program = new Command();
|
|
2986
2955
|
program.name("archtracker").description(
|
|
2987
2956
|
"Architecture & Dependency Tracker \u2014 Prevent missed architecture changes in AI-driven development"
|
|
2988
|
-
).version(
|
|
2957
|
+
).version(VERSION).option("--lang <locale>", "Language (en/ja, auto-detected from LANG env)").hook("preAction", (thisCommand) => {
|
|
2989
2958
|
const lang = thisCommand.opts().lang;
|
|
2990
2959
|
if (lang === "en" || lang === "ja") {
|
|
2991
2960
|
setLocale(lang);
|
|
@@ -2994,11 +2963,13 @@ program.name("archtracker").description(
|
|
|
2994
2963
|
program.command("init").description("Generate initial snapshot and save to .archtracker/").option("-t, --target <dir>", "Target directory", "src").option("-r, --root <dir>", "Project root", ".").option(
|
|
2995
2964
|
"-e, --exclude <patterns...>",
|
|
2996
2965
|
"Exclude patterns (regex)"
|
|
2997
|
-
).action(async (opts) => {
|
|
2966
|
+
).option("-l, --language <lang>", `Target language (${LANGUAGE_IDS.join(", ")})`).action(async (opts) => {
|
|
2998
2967
|
try {
|
|
2968
|
+
const language = validateLanguage(opts.language);
|
|
2999
2969
|
console.log(t("cli.analyzing"));
|
|
3000
2970
|
const graph = await analyzeProject(opts.target, {
|
|
3001
|
-
exclude: opts.exclude
|
|
2971
|
+
exclude: opts.exclude,
|
|
2972
|
+
language
|
|
3002
2973
|
});
|
|
3003
2974
|
const snapshot = await saveSnapshot(opts.root, graph);
|
|
3004
2975
|
console.log(t("cli.snapshotSaved"));
|
|
@@ -3027,11 +2998,13 @@ program.command("analyze").description(
|
|
|
3027
2998
|
).option("-t, --target <dir>", "Target directory", "src").option("-r, --root <dir>", "Project root", ".").option(
|
|
3028
2999
|
"-e, --exclude <patterns...>",
|
|
3029
3000
|
"Exclude patterns (regex)"
|
|
3030
|
-
).option("-n, --top <number>", "Number of top components to show", "10").option("--save", "Also save a snapshot after analysis").action(async (opts) => {
|
|
3001
|
+
).option("-n, --top <number>", "Number of top components to show", "10").option("--save", "Also save a snapshot after analysis").option("-l, --language <lang>", `Target language (${LANGUAGE_IDS.join(", ")})`).action(async (opts) => {
|
|
3031
3002
|
try {
|
|
3003
|
+
const language = validateLanguage(opts.language);
|
|
3032
3004
|
console.log(t("cli.analyzing"));
|
|
3033
3005
|
const graph = await analyzeProject(opts.target, {
|
|
3034
|
-
exclude: opts.exclude
|
|
3006
|
+
exclude: opts.exclude,
|
|
3007
|
+
language
|
|
3035
3008
|
});
|
|
3036
3009
|
const report = formatAnalysisReport(graph, { topN: parseInt(opts.top, 10) });
|
|
3037
3010
|
console.log(report);
|
|
@@ -3045,15 +3018,16 @@ program.command("analyze").description(
|
|
|
3045
3018
|
});
|
|
3046
3019
|
program.command("check").description(
|
|
3047
3020
|
"Compare snapshot with current code and report change impacts"
|
|
3048
|
-
).option("-t, --target <dir>", "Target directory", "src").option("-r, --root <dir>", "Project root", ".").option("--ci", "CI mode: exit code 1 if affected files exist").action(async (opts) => {
|
|
3021
|
+
).option("-t, --target <dir>", "Target directory", "src").option("-r, --root <dir>", "Project root", ".").option("--ci", "CI mode: exit code 1 if affected files exist").option("-l, --language <lang>", `Target language (${LANGUAGE_IDS.join(", ")})`).action(async (opts) => {
|
|
3049
3022
|
try {
|
|
3023
|
+
const language = validateLanguage(opts.language);
|
|
3050
3024
|
const existingSnapshot = await loadSnapshot(opts.root);
|
|
3051
3025
|
if (!existingSnapshot) {
|
|
3052
3026
|
console.log(t("cli.noSnapshot"));
|
|
3053
3027
|
process.exit(1);
|
|
3054
3028
|
}
|
|
3055
3029
|
console.log(t("cli.analyzing"));
|
|
3056
|
-
const currentGraph = await analyzeProject(opts.target);
|
|
3030
|
+
const currentGraph = await analyzeProject(opts.target, { language });
|
|
3057
3031
|
const diff = computeDiff(existingSnapshot.graph, currentGraph);
|
|
3058
3032
|
const report = formatDiffReport(diff);
|
|
3059
3033
|
console.log(report);
|
|
@@ -3067,12 +3041,13 @@ program.command("check").description(
|
|
|
3067
3041
|
});
|
|
3068
3042
|
program.command("context").description(
|
|
3069
3043
|
"Display current architecture context (for AI session initialization)"
|
|
3070
|
-
).option("-t, --target <dir>", "Target directory", "src").option("-r, --root <dir>", "Project root", ".").option("--json", "Output in JSON format").action(async (opts) => {
|
|
3044
|
+
).option("-t, --target <dir>", "Target directory", "src").option("-r, --root <dir>", "Project root", ".").option("--json", "Output in JSON format").option("-l, --language <lang>", `Target language (${LANGUAGE_IDS.join(", ")})`).action(async (opts) => {
|
|
3071
3045
|
try {
|
|
3046
|
+
const language = validateLanguage(opts.language);
|
|
3072
3047
|
let snapshot = await loadSnapshot(opts.root);
|
|
3073
3048
|
if (!snapshot) {
|
|
3074
3049
|
console.log(t("cli.autoGenerating"));
|
|
3075
|
-
const graph2 = await analyzeProject(opts.target);
|
|
3050
|
+
const graph2 = await analyzeProject(opts.target, { language });
|
|
3076
3051
|
snapshot = await saveSnapshot(opts.root, graph2);
|
|
3077
3052
|
}
|
|
3078
3053
|
const graph = snapshot.graph;
|
|
@@ -3110,19 +3085,20 @@ program.command("serve").description(
|
|
|
3110
3085
|
).option("-t, --target <dir>", "Target directory", "src").option("-r, --root <dir>", "Project root", ".").option("-p, --port <number>", "Port number", "3000").option(
|
|
3111
3086
|
"-e, --exclude <patterns...>",
|
|
3112
3087
|
"Exclude patterns (regex)"
|
|
3113
|
-
).option("-w, --watch", "Watch for file changes and auto-reload").action(async (opts) => {
|
|
3088
|
+
).option("-w, --watch", "Watch for file changes and auto-reload").option("-l, --language <lang>", `Target language (${LANGUAGE_IDS.join(", ")})`).action(async (opts) => {
|
|
3114
3089
|
try {
|
|
3090
|
+
const language = validateLanguage(opts.language);
|
|
3115
3091
|
console.log(t("web.starting"));
|
|
3116
3092
|
console.log(t("cli.analyzing"));
|
|
3117
3093
|
let graph;
|
|
3118
3094
|
let diff = null;
|
|
3119
3095
|
const snapshot = await loadSnapshot(opts.root);
|
|
3120
3096
|
if (snapshot) {
|
|
3121
|
-
const currentGraph = await analyzeProject(opts.target, { exclude: opts.exclude });
|
|
3097
|
+
const currentGraph = await analyzeProject(opts.target, { exclude: opts.exclude, language });
|
|
3122
3098
|
diff = computeDiff(snapshot.graph, currentGraph);
|
|
3123
3099
|
graph = currentGraph;
|
|
3124
3100
|
} else {
|
|
3125
|
-
graph = await analyzeProject(opts.target, { exclude: opts.exclude });
|
|
3101
|
+
graph = await analyzeProject(opts.target, { exclude: opts.exclude, language });
|
|
3126
3102
|
}
|
|
3127
3103
|
const port = parseInt(opts.port, 10);
|
|
3128
3104
|
const viewer = startViewer(graph, { port, diff });
|
|
@@ -3136,7 +3112,7 @@ program.command("serve").description(
|
|
|
3136
3112
|
debounce = setTimeout(async () => {
|
|
3137
3113
|
try {
|
|
3138
3114
|
console.log(t("web.reloading"));
|
|
3139
|
-
const newGraph = await analyzeProject(opts.target, { exclude: opts.exclude });
|
|
3115
|
+
const newGraph = await analyzeProject(opts.target, { exclude: opts.exclude, language });
|
|
3140
3116
|
viewer.close();
|
|
3141
3117
|
startViewer(newGraph, { port });
|
|
3142
3118
|
console.log(t("web.reloaded"));
|
|
@@ -3170,15 +3146,22 @@ jobs:
|
|
|
3170
3146
|
- run: npx archtracker check --target ${opts.target} --ci
|
|
3171
3147
|
`;
|
|
3172
3148
|
try {
|
|
3173
|
-
const dir =
|
|
3149
|
+
const dir = join6(".github", "workflows");
|
|
3174
3150
|
await mkdir2(dir, { recursive: true });
|
|
3175
|
-
const path =
|
|
3151
|
+
const path = join6(dir, "arch-check.yml");
|
|
3176
3152
|
await writeFile2(path, workflow, "utf-8");
|
|
3177
3153
|
console.log(t("ci.generated", { path }));
|
|
3178
3154
|
} catch (error) {
|
|
3179
3155
|
handleError(error);
|
|
3180
3156
|
}
|
|
3181
3157
|
});
|
|
3158
|
+
function validateLanguage(lang) {
|
|
3159
|
+
if (!lang) return void 0;
|
|
3160
|
+
if (VALID_LANGUAGES.includes(lang)) return lang;
|
|
3161
|
+
console.error(`Invalid language: ${lang}`);
|
|
3162
|
+
console.error(`Valid languages: ${LANGUAGE_IDS.join(", ")}`);
|
|
3163
|
+
process.exit(1);
|
|
3164
|
+
}
|
|
3182
3165
|
function handleError(error) {
|
|
3183
3166
|
if (error instanceof AnalyzerError) {
|
|
3184
3167
|
console.error(t("error.cli.analyzer", { message: error.message }));
|