archtracker-mcp 0.1.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.js ADDED
@@ -0,0 +1,587 @@
1
+ // src/types/schema.ts
2
+ var SCHEMA_VERSION = "1.0";
3
+
4
+ // src/analyzer/analyze.ts
5
+ import { resolve } from "path";
6
+ import { cruise } from "dependency-cruiser";
7
+ var DEFAULT_EXCLUDE = [
8
+ "node_modules",
9
+ "\\.d\\.ts$",
10
+ "dist",
11
+ "build",
12
+ "coverage",
13
+ "\\.archtracker"
14
+ ];
15
+ async function analyzeProject(rootDir, options = {}) {
16
+ const {
17
+ exclude = [],
18
+ maxDepth = 0,
19
+ tsConfigPath,
20
+ includeTypeOnly = true
21
+ } = options;
22
+ const absRootDir = resolve(rootDir);
23
+ const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
24
+ const excludePattern = allExclude.join("|");
25
+ const cruiseOptions = {
26
+ baseDir: absRootDir,
27
+ exclude: { path: excludePattern },
28
+ doNotFollow: { path: "node_modules" },
29
+ maxDepth,
30
+ tsPreCompilationDeps: includeTypeOnly ? true : false,
31
+ combinedDependencies: false
32
+ };
33
+ if (tsConfigPath) {
34
+ cruiseOptions.tsConfig = { fileName: tsConfigPath };
35
+ }
36
+ let result;
37
+ try {
38
+ result = await cruise(["."], cruiseOptions);
39
+ } catch (error) {
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ throw new AnalyzerError(
42
+ `dependency-cruiser \u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${message}`,
43
+ { cause: error }
44
+ );
45
+ }
46
+ if (result.exitCode !== 0 && !result.output) {
47
+ throw new AnalyzerError(
48
+ `\u89E3\u6790\u304C\u30A8\u30E9\u30FC\u30B3\u30FC\u30C9 ${result.exitCode} \u3067\u7D42\u4E86\u3057\u307E\u3057\u305F`
49
+ );
50
+ }
51
+ const cruiseResult = result.output;
52
+ return buildGraph(absRootDir, cruiseResult);
53
+ }
54
+ function buildGraph(rootDir, cruiseResult) {
55
+ const files = {};
56
+ const edges = [];
57
+ const circularSet = /* @__PURE__ */ new Set();
58
+ const circularDependencies = [];
59
+ for (const mod of cruiseResult.modules) {
60
+ if (isExternalModule(mod)) continue;
61
+ files[mod.source] = {
62
+ path: mod.source,
63
+ exists: !mod.couldNotResolve,
64
+ dependencies: [],
65
+ dependents: []
66
+ };
67
+ }
68
+ for (const mod of cruiseResult.modules) {
69
+ for (const dep of mod.dependencies) {
70
+ if (dep.couldNotResolve || dep.coreModule) continue;
71
+ if (!files[mod.source] || isExternalDep(dep)) continue;
72
+ const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
73
+ edges.push({
74
+ source: mod.source,
75
+ target: dep.resolved,
76
+ type: edgeType
77
+ });
78
+ if (files[mod.source]) {
79
+ files[mod.source].dependencies.push(dep.resolved);
80
+ }
81
+ if (files[dep.resolved]) {
82
+ files[dep.resolved].dependents.push(mod.source);
83
+ }
84
+ if (dep.circular && dep.cycle) {
85
+ const cyclePath = dep.cycle.map((c) => c.name);
86
+ const cycleKey = [...cyclePath].sort().join("\u2192");
87
+ if (!circularSet.has(cycleKey)) {
88
+ circularSet.add(cycleKey);
89
+ circularDependencies.push({ cycle: cyclePath });
90
+ }
91
+ }
92
+ }
93
+ }
94
+ return {
95
+ rootDir,
96
+ files,
97
+ edges,
98
+ circularDependencies,
99
+ totalFiles: Object.keys(files).length,
100
+ totalEdges: edges.length
101
+ };
102
+ }
103
+ function isExternalModule(mod) {
104
+ if (mod.coreModule) return true;
105
+ const depTypes = mod.dependencyTypes ?? [];
106
+ if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
107
+ return isExternalPath(mod.source);
108
+ }
109
+ function isExternalDep(dep) {
110
+ if (dep.coreModule) return true;
111
+ if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
112
+ return isExternalPath(dep.resolved);
113
+ }
114
+ function isExternalPath(source) {
115
+ if (source.startsWith("@")) return true;
116
+ if (!source.includes("/") && !source.includes("\\") && !source.includes(".")) return true;
117
+ if (source.startsWith("node:")) return true;
118
+ return false;
119
+ }
120
+ var AnalyzerError = class extends Error {
121
+ constructor(message, options) {
122
+ super(message, options);
123
+ this.name = "AnalyzerError";
124
+ }
125
+ };
126
+
127
+ // src/i18n/index.ts
128
+ var currentLocale = detectLocale();
129
+ function getLocale() {
130
+ return currentLocale;
131
+ }
132
+ function setLocale(locale) {
133
+ currentLocale = locale;
134
+ }
135
+ function detectLocale() {
136
+ const env = process.env.LC_ALL || process.env.LANG || "";
137
+ if (env.startsWith("ja")) return "ja";
138
+ return "en";
139
+ }
140
+ function t(key, vars) {
141
+ const messages = currentLocale === "ja" ? ja : en;
142
+ let msg = messages[key] ?? en[key] ?? key;
143
+ if (vars) {
144
+ for (const [k, v] of Object.entries(vars)) {
145
+ msg = msg.replaceAll(`{${k}}`, String(v));
146
+ }
147
+ }
148
+ return msg;
149
+ }
150
+ var en = {
151
+ // Analyzer
152
+ "analyzer.failed": "dependency-cruiser failed: {message}",
153
+ "analyzer.exitCode": "Analysis finished with error code {code}",
154
+ // Storage
155
+ "storage.parseFailed": "Failed to parse snapshot.json. File may be corrupted: {path}",
156
+ "storage.readFailed": "Failed to read snapshot.json: {path}",
157
+ "storage.invalidSchema": "snapshot.json schema is invalid. Please regenerate with `archtracker init`:\n{issues}",
158
+ "storage.versionMismatch": "snapshot.json version ({version}) is incompatible with current schema ({expected}). Regenerate with `archtracker init`.",
159
+ // Path guard
160
+ "pathGuard.traversal": 'Path points outside project root: "{input}" \u2192 "{resolved}" (allowed: "{boundary}")',
161
+ // Diff report
162
+ "diff.title": "# Architecture Change Report\n",
163
+ "diff.noChanges": "No changes \u2014 snapshot matches current code.\n",
164
+ "diff.added": "## Added Files ({count})",
165
+ "diff.removed": "## Removed Files ({count})",
166
+ "diff.modified": "## Modified Dependencies ({count})",
167
+ "diff.affected": "## Files Requiring Review ({count})",
168
+ "diff.reasonRemoved": 'Dependency "{file}" was removed',
169
+ "diff.reasonModified": 'Dependency "{file}" had its dependencies changed',
170
+ "diff.reasonAdded": 'New dependency "{file}" was added',
171
+ // Search
172
+ "search.pathMatch": 'Path matches "{pattern}"',
173
+ "search.affected": 'May be affected by changes to "{file}" (via: {via})',
174
+ "search.critical": "{count} files depend on this component",
175
+ "search.orphan": "Orphan file (no dependencies, no dependents)",
176
+ "search.noResults": 'No results: "{query}" (mode: {mode})',
177
+ "search.results": "Results: {count} (mode: {mode})",
178
+ // CLI
179
+ "cli.analyzing": "Analyzing...",
180
+ "cli.snapshotSaved": "Snapshot saved",
181
+ "cli.timestamp": " Timestamp: {ts}",
182
+ "cli.fileCount": " Files: {count}",
183
+ "cli.edgeCount": " Edges: {count}",
184
+ "cli.circularCount": " Circular deps: {count}",
185
+ "cli.keyComponents": "\nKey components:",
186
+ "cli.dependedBy": "{path} ({count} dependents)",
187
+ "cli.noSnapshot": "No snapshot found. Run `archtracker init` first.",
188
+ "cli.ciFailed": "\nCI check failed: {count} file(s) require review",
189
+ "cli.autoGenerating": "No snapshot found, auto-generating...",
190
+ "cli.project": "Project: {path}",
191
+ "cli.validPaths": "\nValid file paths:",
192
+ "cli.snapshot": "Snapshot: {ts}",
193
+ // MCP
194
+ "mcp.analyzeComplete": "Analysis complete: {files} files, {edges} edges",
195
+ "mcp.circularFound": "Circular deps: {count} found",
196
+ "mcp.circularNone": "Circular deps: none",
197
+ "mcp.snapshotSaved": "Snapshot saved",
198
+ "mcp.autoInit": "No snapshot existed. Initial snapshot auto-generated.",
199
+ "mcp.nextCheckEnabled": "Diff checking will be active from the next run.",
200
+ "mcp.queryRequired": '"{mode}" mode requires the query parameter',
201
+ // Analyze report
202
+ "analyze.title": "# Architecture Analysis Report\n",
203
+ "analyze.overview": "## Overview",
204
+ "analyze.totalFiles": " Total files: {count}",
205
+ "analyze.totalEdges": " Total edges: {count}",
206
+ "analyze.totalCircular": " Circular dependencies: {count}",
207
+ "analyze.criticalTitle": "\n## Critical Components (Top {count})",
208
+ "analyze.criticalItem": " {path} ({count} dependents)",
209
+ "analyze.circularTitle": "\n## Circular Dependencies ({count})",
210
+ "analyze.circularItem": " {files}",
211
+ "analyze.orphanTitle": "\n## Orphan Files ({count})",
212
+ "analyze.couplingTitle": "\n## High Coupling (Top {count} by import count)",
213
+ "analyze.couplingItem": " {path} ({count} imports)",
214
+ "analyze.layerTitle": "\n## Directory Breakdown",
215
+ "analyze.layerItem": " {dir}/ \u2014 {count} files",
216
+ "analyze.noIssues": "\nNo architectural issues detected.",
217
+ "analyze.snapshotSaved": "\nSnapshot saved alongside analysis.",
218
+ // CI
219
+ "ci.generated": "GitHub Actions workflow generated: {path}",
220
+ // Web viewer
221
+ "web.starting": "Starting architecture viewer...",
222
+ "web.listening": "Architecture graph available at: http://localhost:{port}",
223
+ "web.stop": "Press Ctrl+C to stop",
224
+ "web.watching": "Watching {dir}/ for changes...",
225
+ "web.reloading": "File change detected, reloading...",
226
+ "web.reloaded": "Graph reloaded",
227
+ // Errors
228
+ "error.analyzer": "[Analysis Error] {message}",
229
+ "error.storage": "[Storage Error] {message}",
230
+ "error.pathTraversal": "[Security Error] {message}",
231
+ "error.generic": "[Error] {message}",
232
+ "error.unexpected": "[Error] Unexpected error: {message}",
233
+ "error.cli.analyzer": "Analysis error: {message}",
234
+ "error.cli.storage": "Storage error: {message}",
235
+ "error.cli.generic": "Error: {message}",
236
+ "error.cli.unexpected": "Unexpected error: {message}"
237
+ };
238
+ var ja = {
239
+ // Analyzer
240
+ "analyzer.failed": "dependency-cruiser \u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
241
+ "analyzer.exitCode": "\u89E3\u6790\u304C\u30A8\u30E9\u30FC\u30B3\u30FC\u30C9 {code} \u3067\u7D42\u4E86\u3057\u307E\u3057\u305F",
242
+ // Storage
243
+ "storage.parseFailed": "snapshot.json \u306E\u30D1\u30FC\u30B9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u30D5\u30A1\u30A4\u30EB\u304C\u7834\u640D\u3057\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059: {path}",
244
+ "storage.readFailed": "snapshot.json \u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {path}",
245
+ "storage.invalidSchema": "snapshot.json \u306E\u30B9\u30AD\u30FC\u30DE\u304C\u4E0D\u6B63\u3067\u3059\u3002archtracker init \u3067\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044:\n{issues}",
246
+ "storage.versionMismatch": "snapshot.json \u306E\u30D0\u30FC\u30B8\u30E7\u30F3 ({version}) \u304C\u73FE\u5728\u306E\u30B9\u30AD\u30FC\u30DE ({expected}) \u3068\u4E92\u63DB\u6027\u304C\u3042\u308A\u307E\u305B\u3093\u3002archtracker init \u3067\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
247
+ // Path guard
248
+ "pathGuard.traversal": '\u30D1\u30B9\u304C\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u306E\u5916\u90E8\u3092\u6307\u3057\u3066\u3044\u307E\u3059: "{input}" \u2192 "{resolved}" (\u8A31\u53EF\u7BC4\u56F2: "{boundary}")',
249
+ // Diff report
250
+ "diff.title": "# \u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u5909\u66F4\u30EC\u30DD\u30FC\u30C8\n",
251
+ "diff.noChanges": "\u5909\u66F4\u306A\u3057 \u2014 \u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3068\u73FE\u5728\u306E\u30B3\u30FC\u30C9\u306F\u4E00\u81F4\u3057\u3066\u3044\u307E\u3059\u3002\n",
252
+ "diff.added": "## \u8FFD\u52A0\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
253
+ "diff.removed": "## \u524A\u9664\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
254
+ "diff.modified": "## \u4F9D\u5B58\u95A2\u4FC2\u304C\u5909\u66F4\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
255
+ "diff.affected": "## \u78BA\u8A8D\u304C\u5FC5\u8981\u306A\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
256
+ "diff.reasonRemoved": '\u4F9D\u5B58\u5148 "{file}" \u304C\u524A\u9664\u3055\u308C\u307E\u3057\u305F',
257
+ "diff.reasonModified": '\u4F9D\u5B58\u5148 "{file}" \u306E\u4F9D\u5B58\u95A2\u4FC2\u304C\u5909\u66F4\u3055\u308C\u307E\u3057\u305F',
258
+ "diff.reasonAdded": '\u65B0\u3057\u3044\u4F9D\u5B58\u5148 "{file}" \u304C\u8FFD\u52A0\u3055\u308C\u307E\u3057\u305F',
259
+ // Search
260
+ "search.pathMatch": '\u30D1\u30B9\u304C "{pattern}" \u306B\u30DE\u30C3\u30C1',
261
+ "search.affected": '"{file}" \u306E\u5909\u66F4\u306B\u3088\u308A\u5F71\u97FF\u3092\u53D7\u3051\u308B\u53EF\u80FD\u6027\uFF08\u7D4C\u7531: {via}\uFF09',
262
+ "search.critical": "{count}\u4EF6\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u4F9D\u5B58\u3059\u308B\u91CD\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8",
263
+ "search.orphan": "\u5B64\u7ACB\u30D5\u30A1\u30A4\u30EB\uFF08\u4F9D\u5B58\u306A\u3057\u30FB\u88AB\u4F9D\u5B58\u306A\u3057\uFF09",
264
+ "search.noResults": '\u691C\u7D22\u7D50\u679C\u306A\u3057: "{query}" (\u30E2\u30FC\u30C9: {mode})',
265
+ "search.results": "\u691C\u7D22\u7D50\u679C: {count}\u4EF6 (\u30E2\u30FC\u30C9: {mode})",
266
+ // CLI
267
+ "cli.analyzing": "\u89E3\u6790\u4E2D...",
268
+ "cli.snapshotSaved": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
269
+ "cli.timestamp": " \u30BF\u30A4\u30E0\u30B9\u30BF\u30F3\u30D7: {ts}",
270
+ "cli.fileCount": " \u30D5\u30A1\u30A4\u30EB\u6570: {count}",
271
+ "cli.edgeCount": " \u30A8\u30C3\u30B8\u6570: {count}",
272
+ "cli.circularCount": " \u5FAA\u74B0\u53C2\u7167: {count}\u4EF6",
273
+ "cli.keyComponents": "\n\u4E3B\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8:",
274
+ "cli.dependedBy": "{path} ({count}\u4EF6\u304C\u4F9D\u5B58)",
275
+ "cli.noSnapshot": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`archtracker init` \u3092\u5148\u306B\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
276
+ "cli.ciFailed": "\nCI \u30C1\u30A7\u30C3\u30AF\u5931\u6557: {count}\u4EF6\u306E\u8981\u78BA\u8A8D\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u3059",
277
+ "cli.autoGenerating": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u7121\u3044\u305F\u3081\u81EA\u52D5\u751F\u6210\u3057\u307E\u3059...",
278
+ "cli.project": "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8: {path}",
279
+ "cli.validPaths": "\n\u6709\u52B9\u306A\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9:",
280
+ "cli.snapshot": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8: {ts}",
281
+ // MCP
282
+ "mcp.analyzeComplete": "\u89E3\u6790\u5B8C\u4E86: {files}\u30D5\u30A1\u30A4\u30EB, {edges}\u30A8\u30C3\u30B8",
283
+ "mcp.circularFound": "\u5FAA\u74B0\u53C2\u7167: {count}\u4EF6\u691C\u51FA",
284
+ "mcp.circularNone": "\u5FAA\u74B0\u53C2\u7167: \u306A\u3057",
285
+ "mcp.snapshotSaved": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
286
+ "mcp.autoInit": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u5B58\u5728\u3057\u306A\u304B\u3063\u305F\u305F\u3081\u3001\u521D\u671F\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u81EA\u52D5\u751F\u6210\u3057\u307E\u3057\u305F\u3002",
287
+ "mcp.nextCheckEnabled": "\u6B21\u56DE\u306E\u5B9F\u884C\u6642\u304B\u3089\u5DEE\u5206\u30C1\u30A7\u30C3\u30AF\u304C\u6709\u52B9\u306B\u306A\u308A\u307E\u3059\u3002",
288
+ "mcp.queryRequired": '"{mode}" \u30E2\u30FC\u30C9\u3067\u306F query \u30D1\u30E9\u30E1\u30FC\u30BF\u304C\u5FC5\u9808\u3067\u3059',
289
+ // Analyze report
290
+ "analyze.title": "# \u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u5206\u6790\u30EC\u30DD\u30FC\u30C8\n",
291
+ "analyze.overview": "## \u6982\u8981",
292
+ "analyze.totalFiles": " \u7DCF\u30D5\u30A1\u30A4\u30EB\u6570: {count}",
293
+ "analyze.totalEdges": " \u7DCF\u30A8\u30C3\u30B8\u6570: {count}",
294
+ "analyze.totalCircular": " \u5FAA\u74B0\u53C2\u7167: {count}\u4EF6",
295
+ "analyze.criticalTitle": "\n## \u91CD\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8 (\u4E0A\u4F4D{count}\u4EF6)",
296
+ "analyze.criticalItem": " {path} ({count}\u4EF6\u304C\u4F9D\u5B58)",
297
+ "analyze.circularTitle": "\n## \u5FAA\u74B0\u53C2\u7167 ({count}\u4EF6)",
298
+ "analyze.circularItem": " {files}",
299
+ "analyze.orphanTitle": "\n## \u5B64\u7ACB\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
300
+ "analyze.couplingTitle": "\n## \u9AD8\u7D50\u5408\u30D5\u30A1\u30A4\u30EB (import\u6570 \u4E0A\u4F4D{count}\u4EF6)",
301
+ "analyze.couplingItem": " {path} ({count}\u4EF6\u3092import)",
302
+ "analyze.layerTitle": "\n## \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u69CB\u6210",
303
+ "analyze.layerItem": " {dir}/ \u2014 {count}\u30D5\u30A1\u30A4\u30EB",
304
+ "analyze.noIssues": "\n\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u4E0A\u306E\u554F\u984C\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
305
+ "analyze.snapshotSaved": "\n\u5206\u6790\u3068\u540C\u6642\u306B\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002",
306
+ // CI
307
+ "ci.generated": "GitHub Actions \u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u3092\u751F\u6210\u3057\u307E\u3057\u305F: {path}",
308
+ // Web viewer
309
+ "web.starting": "\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u30D3\u30E5\u30FC\u30A2\u30FC\u3092\u8D77\u52D5\u4E2D...",
310
+ "web.listening": "\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u30B0\u30E9\u30D5: http://localhost:{port}",
311
+ "web.stop": "Ctrl+C \u3067\u505C\u6B62",
312
+ "web.watching": "{dir}/ \u3092\u76E3\u8996\u4E2D...",
313
+ "web.reloading": "\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u3092\u691C\u51FA\u3001\u30EA\u30ED\u30FC\u30C9\u4E2D...",
314
+ "web.reloaded": "\u30B0\u30E9\u30D5\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
315
+ // Errors
316
+ "error.analyzer": "[\u89E3\u6790\u30A8\u30E9\u30FC] {message}",
317
+ "error.storage": "[\u30B9\u30C8\u30EC\u30FC\u30B8\u30A8\u30E9\u30FC] {message}",
318
+ "error.pathTraversal": "[\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30A8\u30E9\u30FC] {message}",
319
+ "error.generic": "[\u30A8\u30E9\u30FC] {message}",
320
+ "error.unexpected": "[\u30A8\u30E9\u30FC] \u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F: {message}",
321
+ "error.cli.analyzer": "\u89E3\u6790\u30A8\u30E9\u30FC: {message}",
322
+ "error.cli.storage": "\u30B9\u30C8\u30EC\u30FC\u30B8\u30A8\u30E9\u30FC: {message}",
323
+ "error.cli.generic": "\u30A8\u30E9\u30FC: {message}",
324
+ "error.cli.unexpected": "\u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC: {message}"
325
+ };
326
+
327
+ // src/analyzer/report.ts
328
+ function formatAnalysisReport(graph, options = {}) {
329
+ const topN = options.topN ?? 10;
330
+ const lines = [];
331
+ const files = Object.values(graph.files);
332
+ lines.push(t("analyze.title"));
333
+ lines.push(t("analyze.overview"));
334
+ lines.push(t("analyze.totalFiles", { count: graph.totalFiles }));
335
+ lines.push(t("analyze.totalEdges", { count: graph.totalEdges }));
336
+ lines.push(t("analyze.totalCircular", { count: graph.circularDependencies.length }));
337
+ const critical = files.filter((f) => f.dependents.length > 0).sort((a, b) => b.dependents.length - a.dependents.length).slice(0, topN);
338
+ if (critical.length > 0) {
339
+ lines.push(t("analyze.criticalTitle", { count: critical.length }));
340
+ for (const f of critical) {
341
+ lines.push(t("analyze.criticalItem", { path: f.path, count: f.dependents.length }));
342
+ }
343
+ }
344
+ if (graph.circularDependencies.length > 0) {
345
+ lines.push(t("analyze.circularTitle", { count: graph.circularDependencies.length }));
346
+ for (const c of graph.circularDependencies) {
347
+ lines.push(t("analyze.circularItem", { files: c.cycle.join(" \u2192 ") }));
348
+ }
349
+ }
350
+ const highCoupling = files.filter((f) => f.dependencies.length > 0).sort((a, b) => b.dependencies.length - a.dependencies.length).slice(0, topN);
351
+ if (highCoupling.length > 0) {
352
+ lines.push(t("analyze.couplingTitle", { count: highCoupling.length }));
353
+ for (const f of highCoupling) {
354
+ lines.push(t("analyze.couplingItem", { path: f.path, count: f.dependencies.length }));
355
+ }
356
+ }
357
+ const orphans = files.filter(
358
+ (f) => f.dependents.length === 0 && f.dependencies.length === 0
359
+ );
360
+ if (orphans.length > 0) {
361
+ lines.push(t("analyze.orphanTitle", { count: orphans.length }));
362
+ for (const f of orphans) {
363
+ lines.push(` ${f.path}`);
364
+ }
365
+ }
366
+ const dirCounts = /* @__PURE__ */ new Map();
367
+ for (const f of files) {
368
+ const dir = f.path.includes("/") ? f.path.substring(0, f.path.lastIndexOf("/")) : ".";
369
+ dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
370
+ }
371
+ if (dirCounts.size > 1) {
372
+ lines.push(t("analyze.layerTitle"));
373
+ const sorted = [...dirCounts.entries()].sort((a, b) => b[1] - a[1]);
374
+ for (const [dir, count] of sorted) {
375
+ lines.push(t("analyze.layerItem", { dir, count }));
376
+ }
377
+ }
378
+ if (graph.circularDependencies.length === 0 && orphans.length === 0) {
379
+ lines.push(t("analyze.noIssues"));
380
+ }
381
+ return lines.join("\n");
382
+ }
383
+
384
+ // src/storage/snapshot.ts
385
+ import { mkdir, writeFile, readFile, access } from "fs/promises";
386
+ import { join } from "path";
387
+ import { z } from "zod";
388
+ var ARCHTRACKER_DIR = ".archtracker";
389
+ var SNAPSHOT_FILE = "snapshot.json";
390
+ var FileNodeSchema = z.object({
391
+ path: z.string(),
392
+ exists: z.boolean(),
393
+ dependencies: z.array(z.string()),
394
+ dependents: z.array(z.string())
395
+ });
396
+ var DependencyGraphSchema = z.object({
397
+ rootDir: z.string(),
398
+ files: z.record(z.string(), FileNodeSchema),
399
+ edges: z.array(z.object({
400
+ source: z.string(),
401
+ target: z.string(),
402
+ type: z.enum(["static", "dynamic", "type-only"])
403
+ })),
404
+ circularDependencies: z.array(z.object({ cycle: z.array(z.string()) })),
405
+ totalFiles: z.number(),
406
+ totalEdges: z.number()
407
+ });
408
+ var SnapshotSchema = z.object({
409
+ version: z.literal(SCHEMA_VERSION),
410
+ timestamp: z.string(),
411
+ rootDir: z.string(),
412
+ graph: DependencyGraphSchema
413
+ });
414
+ async function saveSnapshot(projectRoot, graph) {
415
+ const dirPath = join(projectRoot, ARCHTRACKER_DIR);
416
+ const filePath = join(dirPath, SNAPSHOT_FILE);
417
+ const snapshot = {
418
+ version: SCHEMA_VERSION,
419
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
420
+ rootDir: graph.rootDir,
421
+ graph
422
+ };
423
+ await mkdir(dirPath, { recursive: true });
424
+ await writeFile(filePath, JSON.stringify(snapshot, null, 2), "utf-8");
425
+ return snapshot;
426
+ }
427
+ async function loadSnapshot(projectRoot) {
428
+ const filePath = join(projectRoot, ARCHTRACKER_DIR, SNAPSHOT_FILE);
429
+ let raw;
430
+ try {
431
+ raw = await readFile(filePath, "utf-8");
432
+ } catch (error) {
433
+ if (isNodeError(error) && error.code === "ENOENT") {
434
+ return null;
435
+ }
436
+ throw new StorageError(
437
+ t("storage.readFailed", { path: filePath }),
438
+ { cause: error }
439
+ );
440
+ }
441
+ let parsed;
442
+ try {
443
+ parsed = JSON.parse(raw);
444
+ } catch {
445
+ throw new StorageError(
446
+ t("storage.parseFailed", { path: filePath })
447
+ );
448
+ }
449
+ const result = SnapshotSchema.safeParse(parsed);
450
+ if (!result.success) {
451
+ const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).slice(0, 5).join("\n");
452
+ throw new StorageError(
453
+ t("storage.invalidSchema", { issues })
454
+ );
455
+ }
456
+ return result.data;
457
+ }
458
+ async function hasArchtrackerDir(projectRoot) {
459
+ try {
460
+ await access(join(projectRoot, ARCHTRACKER_DIR));
461
+ return true;
462
+ } catch {
463
+ return false;
464
+ }
465
+ }
466
+ var StorageError = class extends Error {
467
+ constructor(message, options) {
468
+ super(message, options);
469
+ this.name = "StorageError";
470
+ }
471
+ };
472
+ function isNodeError(error) {
473
+ return error instanceof Error && "code" in error;
474
+ }
475
+
476
+ // src/storage/diff.ts
477
+ function computeDiff(oldGraph, newGraph) {
478
+ const oldFiles = new Set(Object.keys(oldGraph.files));
479
+ const newFiles = new Set(Object.keys(newGraph.files));
480
+ const added = [...newFiles].filter((f) => !oldFiles.has(f));
481
+ const removed = [...oldFiles].filter((f) => !newFiles.has(f));
482
+ const modified = [];
483
+ for (const file of newFiles) {
484
+ if (!oldFiles.has(file)) continue;
485
+ const oldDeps = oldGraph.files[file].dependencies.slice().sort();
486
+ const newDeps = newGraph.files[file].dependencies.slice().sort();
487
+ if (!arraysEqual(oldDeps, newDeps)) {
488
+ modified.push(file);
489
+ }
490
+ }
491
+ const removedSet = new Set(removed);
492
+ const changedFiles = /* @__PURE__ */ new Set([...removed, ...modified]);
493
+ const affectedDependents = [];
494
+ const seenAffected = /* @__PURE__ */ new Set();
495
+ for (const changedFile of changedFiles) {
496
+ const graph = removedSet.has(changedFile) ? oldGraph : newGraph;
497
+ const node = graph.files[changedFile];
498
+ if (!node) continue;
499
+ for (const dependent of node.dependents) {
500
+ const key = `${dependent}\u2192${changedFile}`;
501
+ if (seenAffected.has(key)) continue;
502
+ seenAffected.add(key);
503
+ const reason = removedSet.has(changedFile) ? t("diff.reasonRemoved", { file: changedFile }) : t("diff.reasonModified", { file: changedFile });
504
+ affectedDependents.push({
505
+ file: dependent,
506
+ reason,
507
+ dependsOn: changedFile
508
+ });
509
+ }
510
+ }
511
+ for (const addedFile of added) {
512
+ const node = newGraph.files[addedFile];
513
+ if (!node) continue;
514
+ for (const dependent of node.dependents) {
515
+ const key = `${dependent}\u2192${addedFile}`;
516
+ if (seenAffected.has(key)) continue;
517
+ seenAffected.add(key);
518
+ affectedDependents.push({
519
+ file: dependent,
520
+ reason: t("diff.reasonAdded", { file: addedFile }),
521
+ dependsOn: addedFile
522
+ });
523
+ }
524
+ }
525
+ return { added, removed, modified, affectedDependents };
526
+ }
527
+ function formatDiffReport(diff) {
528
+ const lines = [];
529
+ lines.push(t("diff.title"));
530
+ if (diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0) {
531
+ lines.push(t("diff.noChanges"));
532
+ return lines.join("\n");
533
+ }
534
+ if (diff.added.length > 0) {
535
+ lines.push(t("diff.added", { count: diff.added.length }));
536
+ for (const f of diff.added) {
537
+ lines.push(` + ${f}`);
538
+ }
539
+ lines.push("");
540
+ }
541
+ if (diff.removed.length > 0) {
542
+ lines.push(t("diff.removed", { count: diff.removed.length }));
543
+ for (const f of diff.removed) {
544
+ lines.push(` - ${f}`);
545
+ }
546
+ lines.push("");
547
+ }
548
+ if (diff.modified.length > 0) {
549
+ lines.push(t("diff.modified", { count: diff.modified.length }));
550
+ for (const f of diff.modified) {
551
+ lines.push(` ~ ${f}`);
552
+ }
553
+ lines.push("");
554
+ }
555
+ if (diff.affectedDependents.length > 0) {
556
+ lines.push(t("diff.affected", { count: diff.affectedDependents.length }));
557
+ for (const a of diff.affectedDependents) {
558
+ lines.push(` ! ${a.file}`);
559
+ lines.push(` ${a.reason}`);
560
+ }
561
+ lines.push("");
562
+ }
563
+ return lines.join("\n");
564
+ }
565
+ function arraysEqual(a, b) {
566
+ if (a.length !== b.length) return false;
567
+ for (let i = 0; i < a.length; i++) {
568
+ if (a[i] !== b[i]) return false;
569
+ }
570
+ return true;
571
+ }
572
+ export {
573
+ AnalyzerError,
574
+ SCHEMA_VERSION,
575
+ StorageError,
576
+ analyzeProject,
577
+ computeDiff,
578
+ formatAnalysisReport,
579
+ formatDiffReport,
580
+ getLocale,
581
+ hasArchtrackerDir,
582
+ loadSnapshot,
583
+ saveSnapshot,
584
+ setLocale,
585
+ t
586
+ };
587
+ //# sourceMappingURL=index.js.map