archtracker-mcp 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -56,6 +56,12 @@ interface ArchSnapshot {
56
56
  graph: DependencyGraph;
57
57
  /** Present only when layers.json was used */
58
58
  multiLayer?: MultiLayerGraph;
59
+ /** Analysis options used to generate this snapshot (for diff consistency checks) */
60
+ analysisOptions?: {
61
+ targetDir?: string;
62
+ language?: string;
63
+ exclude?: string[];
64
+ };
59
65
  }
60
66
  /** Diff result between two snapshots */
61
67
  interface ArchDiff {
@@ -156,7 +162,7 @@ declare function formatAnalysisReport(graph: DependencyGraph, options?: {
156
162
  * Each layer is analyzed independently via analyzeProject().
157
163
  * Results are merged into a unified graph with layer-prefixed node paths.
158
164
  */
159
- declare function analyzeMultiLayer(projectRoot: string, layerDefs: LayerDefinition[]): Promise<MultiLayerGraph>;
165
+ declare function analyzeMultiLayer(projectRoot: string, layerDefs: LayerDefinition[], globalExclude?: string[]): Promise<MultiLayerGraph>;
160
166
  /**
161
167
  * Auto-detect cross-layer connections by scanning file contents
162
168
  * for references to identifiers defined in other layers.
@@ -180,7 +186,11 @@ declare function detectCrossLayerConnections(layers: Record<string, DependencyGr
180
186
  * Creates .archtracker/ directory if it doesn't exist.
181
187
  * Writes snapshot.json with schema version and timestamp.
182
188
  */
183
- declare function saveSnapshot(projectRoot: string, graph: DependencyGraph, multiLayer?: MultiLayerGraph): Promise<ArchSnapshot>;
189
+ declare function saveSnapshot(projectRoot: string, graph: DependencyGraph, multiLayer?: MultiLayerGraph, analysisOptions?: {
190
+ targetDir?: string;
191
+ language?: string;
192
+ exclude?: string[];
193
+ }): Promise<ArchSnapshot>;
184
194
  /**
185
195
  * Load the most recent snapshot from .archtracker/snapshot.json.
186
196
  *
package/dist/index.js CHANGED
@@ -1564,6 +1564,8 @@ var en = {
1564
1564
  "diff.reasonRemoved": 'Dependency "{file}" was removed',
1565
1565
  "diff.reasonModified": 'Dependency "{file}" had its dependencies changed',
1566
1566
  "diff.reasonAdded": 'New dependency "{file}" was added',
1567
+ "diff.testSummary": " ... and {count} test/fixture file(s)",
1568
+ "diff.testAffectedSummary": " ... and {count} test/fixture-related review(s)",
1567
1569
  // Search
1568
1570
  "search.pathMatch": 'Path matches "{pattern}"',
1569
1571
  "search.affected": 'May be affected by changes to "{file}" (via: {via})',
@@ -1625,6 +1627,12 @@ var en = {
1625
1627
  "web.watching": "Watching {dir}/ for changes...",
1626
1628
  "web.reloading": "File change detected, reloading...",
1627
1629
  "web.reloaded": "Graph reloaded",
1630
+ // History
1631
+ "history.title": "# Snapshot History\n",
1632
+ "history.empty": "No snapshots found. Run `archtracker init` to create one.",
1633
+ "history.entry": " {ts} | {files} files, {edges} edges, {circular} circular{layers}",
1634
+ "history.count": "{count} snapshot(s) recorded",
1635
+ "history.snapshotNotFound": "Snapshot not found for timestamp: {ts}",
1628
1636
  // Errors
1629
1637
  "error.analyzer": "[Analysis Error] {message}",
1630
1638
  "error.storage": "[Storage Error] {message}",
@@ -1657,6 +1665,8 @@ var ja = {
1657
1665
  "diff.reasonRemoved": '\u4F9D\u5B58\u5148 "{file}" \u304C\u524A\u9664\u3055\u308C\u307E\u3057\u305F',
1658
1666
  "diff.reasonModified": '\u4F9D\u5B58\u5148 "{file}" \u306E\u4F9D\u5B58\u95A2\u4FC2\u304C\u5909\u66F4\u3055\u308C\u307E\u3057\u305F',
1659
1667
  "diff.reasonAdded": '\u65B0\u3057\u3044\u4F9D\u5B58\u5148 "{file}" \u304C\u8FFD\u52A0\u3055\u308C\u307E\u3057\u305F',
1668
+ "diff.testSummary": " ... \u4ED6 {count}\u4EF6\u306E\u30C6\u30B9\u30C8/\u30D5\u30A3\u30AF\u30B9\u30C1\u30E3\u30D5\u30A1\u30A4\u30EB",
1669
+ "diff.testAffectedSummary": " ... \u4ED6 {count}\u4EF6\u306E\u30C6\u30B9\u30C8/\u30D5\u30A3\u30AF\u30B9\u30C1\u30E3\u95A2\u9023\u306E\u78BA\u8A8D\u9805\u76EE",
1660
1670
  // Search
1661
1671
  "search.pathMatch": '\u30D1\u30B9\u304C "{pattern}" \u306B\u30DE\u30C3\u30C1',
1662
1672
  "search.affected": '"{file}" \u306E\u5909\u66F4\u306B\u3088\u308A\u5F71\u97FF\u3092\u53D7\u3051\u308B\u53EF\u80FD\u6027\uFF08\u7D4C\u7531: {via}\uFF09',
@@ -1718,6 +1728,12 @@ var ja = {
1718
1728
  "web.watching": "{dir}/ \u3092\u76E3\u8996\u4E2D...",
1719
1729
  "web.reloading": "\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u3092\u691C\u51FA\u3001\u30EA\u30ED\u30FC\u30C9\u4E2D...",
1720
1730
  "web.reloaded": "\u30B0\u30E9\u30D5\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
1731
+ // History
1732
+ "history.title": "# \u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u5C65\u6B74\n",
1733
+ "history.empty": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`archtracker init` \u3067\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1734
+ "history.entry": " {ts} | {files}\u30D5\u30A1\u30A4\u30EB, {edges}\u30A8\u30C3\u30B8, \u5FAA\u74B0{circular}\u4EF6{layers}",
1735
+ "history.count": "{count}\u4EF6\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u8A18\u9332",
1736
+ "history.snapshotNotFound": "\u6307\u5B9A\u306E\u30BF\u30A4\u30E0\u30B9\u30BF\u30F3\u30D7\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: {ts}",
1721
1737
  // Errors
1722
1738
  "error.analyzer": "[\u89E3\u6790\u30A8\u30E9\u30FC] {message}",
1723
1739
  "error.storage": "[\u30B9\u30C8\u30EC\u30FC\u30B8\u30A8\u30E9\u30FC] {message}",
@@ -1802,14 +1818,14 @@ var LAYER_COLORS = [
1802
1818
  "#ffa657",
1803
1819
  "#7ee787"
1804
1820
  ];
1805
- async function analyzeMultiLayer(projectRoot, layerDefs) {
1821
+ async function analyzeMultiLayer(projectRoot, layerDefs, globalExclude) {
1806
1822
  const layers = {};
1807
1823
  const layerMetadata = [];
1808
1824
  for (let idx = 0; idx < layerDefs.length; idx++) {
1809
1825
  const def = layerDefs[idx];
1810
1826
  const targetDir = resolve4(projectRoot, def.targetDir);
1811
1827
  const graph = await analyzeProject(targetDir, {
1812
- exclude: def.exclude,
1828
+ exclude: def.exclude ?? globalExclude,
1813
1829
  language: def.language
1814
1830
  });
1815
1831
  const language = def.language ?? await detectLanguage(targetDir) ?? "javascript";
@@ -2241,12 +2257,19 @@ function isNodeError(error) {
2241
2257
  return error instanceof Error && "code" in error;
2242
2258
  }
2243
2259
 
2260
+ // src/storage/graph-cache.ts
2261
+ import { readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, stat as stat4 } from "fs/promises";
2262
+ import { join as join6, resolve as resolve5 } from "path";
2263
+ import { readdir as readdir3 } from "fs/promises";
2264
+ import { createHash } from "crypto";
2265
+
2244
2266
  // src/storage/snapshot.ts
2245
- import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3, access } from "fs/promises";
2246
- import { join as join6 } from "path";
2267
+ import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile4, readdir as readdir4, access } from "fs/promises";
2268
+ import { join as join7 } from "path";
2247
2269
  import { z as z2 } from "zod";
2248
2270
  var ARCHTRACKER_DIR2 = ".archtracker";
2249
2271
  var SNAPSHOT_FILE = "snapshot.json";
2272
+ var HISTORY_DIR = "history";
2250
2273
  var FileNodeSchema = z2.object({
2251
2274
  path: z2.string(),
2252
2275
  exists: z2.boolean(),
@@ -2271,25 +2294,34 @@ var SnapshotSchema = z2.object({
2271
2294
  rootDir: z2.string(),
2272
2295
  graph: DependencyGraphSchema
2273
2296
  });
2274
- async function saveSnapshot(projectRoot, graph, multiLayer) {
2275
- const dirPath = join6(projectRoot, ARCHTRACKER_DIR2);
2276
- const filePath = join6(dirPath, SNAPSHOT_FILE);
2297
+ async function saveSnapshot(projectRoot, graph, multiLayer, analysisOptions) {
2298
+ const dirPath = join7(projectRoot, ARCHTRACKER_DIR2);
2299
+ const filePath = join7(dirPath, SNAPSHOT_FILE);
2277
2300
  const snapshot = {
2278
2301
  version: SCHEMA_VERSION,
2279
2302
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2280
2303
  rootDir: graph.rootDir,
2281
2304
  graph,
2282
- ...multiLayer ? { multiLayer } : {}
2305
+ ...multiLayer ? { multiLayer } : {},
2306
+ ...analysisOptions ? { analysisOptions } : {}
2283
2307
  };
2284
- await mkdir2(dirPath, { recursive: true });
2285
- await writeFile2(filePath, JSON.stringify(snapshot, null, 2), "utf-8");
2308
+ await mkdir3(dirPath, { recursive: true });
2309
+ const json = JSON.stringify(snapshot, null, 2);
2310
+ await writeFile3(filePath, json, "utf-8");
2311
+ try {
2312
+ const historyPath = join7(dirPath, HISTORY_DIR);
2313
+ await mkdir3(historyPath, { recursive: true });
2314
+ const safeTs = snapshot.timestamp.replace(/[:.]/g, "-");
2315
+ await writeFile3(join7(historyPath, `${safeTs}.json`), json, "utf-8");
2316
+ } catch {
2317
+ }
2286
2318
  return snapshot;
2287
2319
  }
2288
2320
  async function loadSnapshot(projectRoot) {
2289
- const filePath = join6(projectRoot, ARCHTRACKER_DIR2, SNAPSHOT_FILE);
2321
+ const filePath = join7(projectRoot, ARCHTRACKER_DIR2, SNAPSHOT_FILE);
2290
2322
  let raw;
2291
2323
  try {
2292
- raw = await readFile3(filePath, "utf-8");
2324
+ raw = await readFile4(filePath, "utf-8");
2293
2325
  } catch (error) {
2294
2326
  if (isNodeError2(error) && error.code === "ENOENT") {
2295
2327
  return null;
@@ -2318,7 +2350,7 @@ async function loadSnapshot(projectRoot) {
2318
2350
  }
2319
2351
  async function hasArchtrackerDir(projectRoot) {
2320
2352
  try {
2321
- await access(join6(projectRoot, ARCHTRACKER_DIR2));
2353
+ await access(join7(projectRoot, ARCHTRACKER_DIR2));
2322
2354
  return true;
2323
2355
  } catch {
2324
2356
  return false;
@@ -2385,6 +2417,17 @@ function computeDiff(oldGraph, newGraph) {
2385
2417
  }
2386
2418
  return { added, removed, modified, affectedDependents };
2387
2419
  }
2420
+ function isTestOrFixture(path) {
2421
+ return /(__fixtures__|__tests__|__mocks__|\.test\.|\.spec\.|\.e2e\.)/.test(path);
2422
+ }
2423
+ function partition(arr, pred) {
2424
+ const yes = [];
2425
+ const no = [];
2426
+ for (const item of arr) {
2427
+ (pred(item) ? yes : no).push(item);
2428
+ }
2429
+ return [yes, no];
2430
+ }
2388
2431
  function formatDiffReport(diff) {
2389
2432
  const lines = [];
2390
2433
  lines.push(t("diff.title"));
@@ -2393,32 +2436,51 @@ function formatDiffReport(diff) {
2393
2436
  return lines.join("\n");
2394
2437
  }
2395
2438
  if (diff.added.length > 0) {
2439
+ const [testFiles, srcFiles] = partition(diff.added, isTestOrFixture);
2396
2440
  lines.push(t("diff.added", { count: diff.added.length }));
2397
- for (const f of diff.added) {
2441
+ for (const f of srcFiles) {
2398
2442
  lines.push(` + ${f}`);
2399
2443
  }
2444
+ if (testFiles.length > 0) {
2445
+ lines.push(t("diff.testSummary", { count: testFiles.length }));
2446
+ }
2400
2447
  lines.push("");
2401
2448
  }
2402
2449
  if (diff.removed.length > 0) {
2450
+ const [testFiles, srcFiles] = partition(diff.removed, isTestOrFixture);
2403
2451
  lines.push(t("diff.removed", { count: diff.removed.length }));
2404
- for (const f of diff.removed) {
2452
+ for (const f of srcFiles) {
2405
2453
  lines.push(` - ${f}`);
2406
2454
  }
2455
+ if (testFiles.length > 0) {
2456
+ lines.push(t("diff.testSummary", { count: testFiles.length }));
2457
+ }
2407
2458
  lines.push("");
2408
2459
  }
2409
2460
  if (diff.modified.length > 0) {
2461
+ const [testFiles, srcFiles] = partition(diff.modified, isTestOrFixture);
2410
2462
  lines.push(t("diff.modified", { count: diff.modified.length }));
2411
- for (const f of diff.modified) {
2463
+ for (const f of srcFiles) {
2412
2464
  lines.push(` ~ ${f}`);
2413
2465
  }
2466
+ if (testFiles.length > 0) {
2467
+ lines.push(t("diff.testSummary", { count: testFiles.length }));
2468
+ }
2414
2469
  lines.push("");
2415
2470
  }
2416
2471
  if (diff.affectedDependents.length > 0) {
2472
+ const [testEntries, srcEntries] = partition(
2473
+ diff.affectedDependents,
2474
+ (a) => isTestOrFixture(a.file) || isTestOrFixture(a.dependsOn)
2475
+ );
2417
2476
  lines.push(t("diff.affected", { count: diff.affectedDependents.length }));
2418
- for (const a of diff.affectedDependents) {
2477
+ for (const a of srcEntries) {
2419
2478
  lines.push(` ! ${a.file}`);
2420
2479
  lines.push(` ${a.reason}`);
2421
2480
  }
2481
+ if (testEntries.length > 0) {
2482
+ lines.push(t("diff.testAffectedSummary", { count: testEntries.length }));
2483
+ }
2422
2484
  lines.push("");
2423
2485
  }
2424
2486
  return lines.join("\n");