universal-ast-mapper 1.18.0 → 1.19.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/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ since 1.0.0, guarantees a stable MCP tool / CLI surface across the 1.x line.
6
6
 
7
7
  ---
8
8
 
9
+ ## [1.19.0] — 2026-06-09 · Dashboard: coupling + SDP
10
+ - The health dashboard (`ast-map report` / `get_codebase_report`) now surfaces the
11
+ v1.14–1.16 architecture metrics: a **Module coupling** card (per-directory instability
12
+ bars with Ca/Ce) and a **Layer violations** card (stable→volatile SDP inversions),
13
+ plus an **SDP violations** stat tile.
14
+ - SDP violations now factor into the health score (small capped penalty), so a codebase
15
+ that systematically depends "uphill" on the stability gradient scores lower.
16
+ - `ReportData` gains `layerViolations` and `modules`; purely additive.
17
+ - Tests: 4 new assertions (131 total) — report carries the new data and the HTML renders
18
+ both cards.
19
+
9
20
  ## [1.18.0] — 2026-06-09 · Vue & Svelte SFC support
10
21
  - `.vue` and `.svelte` **single-file components** are now first-class inputs. The
11
22
  `<script>` / `<script setup>` block is lifted out and parsed with the TS/JS extractor
package/README.md CHANGED
@@ -368,7 +368,7 @@ Given a compiled JS/CSS file with an inline (`data:`) or external `sourceMapping
368
368
  ---
369
369
 
370
370
  ### `get_codebase_report`
371
- A one-shot **codebase health summary**: file/symbol counts, language breakdown, a health **grade (A–F)** + score, complexity hotspots, god nodes, dead exports, and circular dependencies. Rendered as a premium HTML dashboard by `ast-map report`.
371
+ A one-shot **codebase health summary**: file/symbol counts, language breakdown, a health **grade (A–F)** + score, complexity hotspots, god nodes, dead exports, circular dependencies, **module coupling** (per-directory instability), and **layer violations** (SDP). Rendered as a premium HTML dashboard by `ast-map report`.
372
372
 
373
373
  ```json
374
374
  { "grade": "B", "score": 82, "fileCount": 120, "symbolCount": 1400,
@@ -740,6 +740,7 @@ Not part of the public API: the internal `src/` module layout and the generated
740
740
 
741
741
  | Version | What changed |
742
742
  |---------|--------------|
743
+ | **1.19.0** | **Dashboard: coupling + SDP** — `ast-map report` / `get_codebase_report` now include **module coupling** (per-directory instability bars) and **layer violations** (stable→volatile, SDP) cards, plus an SDP stat; SDP inversions also factor into the health score. The v1.14–1.16 metrics are now visual. |
743
744
  | **1.18.0** | **Vue & Svelte SFC support** — `.vue` and `.svelte` single-file components are now parsed: the `<script>` / `<script setup>` block is lifted out (TS or JS) and its symbols + imports extracted, with cross-file graph edges into plain modules. Blank-padding keeps every symbol's line numbers pointing at the original SFC. **14 languages**. |
744
745
  | **1.17.0** | **MCP prompts** — the server now registers 5 parameterized **prompts** (`architecture_audit`, `safe_refactor`, `dead_code_cleanup`, `health_check`, `onboard_codebase`): named workflows a client can invoke from its prompt menu, each chaining the right tools. The Cookbook recipes, one call away. |
745
746
  | **1.16.0** | **Module coupling** — new `get_module_coupling` MCP tool + `ast-map modules` (alias `mods`) CLI: aggregates the import graph to the **directory/module level** — per-module Ca / Ce / instability plus weighted inter-module edges (intra-module imports ignored). The bird's-eye view above per-file coupling. **27 MCP tools**. |
package/dist/report.js CHANGED
@@ -4,6 +4,8 @@ import { resolveOptions } from "./config.js";
4
4
  import { buildSymbolGraph } from "./graph.js";
5
5
  import { findDeadExports, findCircularDeps, getTopSymbols } from "./graph-analysis.js";
6
6
  import { computeFileComplexity } from "./complexity.js";
7
+ import { findLayerViolations } from "./layers.js";
8
+ import { computeModuleCoupling } from "./modulecoupling.js";
7
9
  function gradeFor(score) {
8
10
  if (score >= 90)
9
11
  return "A";
@@ -46,6 +48,8 @@ export async function buildReport(absDir, root) {
46
48
  const dead = findDeadExports(graph).filter((d) => d.confidence === "high");
47
49
  const cycles = findCircularDeps(graph);
48
50
  const god = getTopSymbols(graph, 8);
51
+ const layerViolations = findLayerViolations(graph);
52
+ const modules = computeModuleCoupling(graph).modules;
49
53
  hotspots.sort((a, b) => b.complexity - a.complexity);
50
54
  const veryHigh = hotspots.filter((f) => f.complexity > 20).length;
51
55
  const high = hotspots.filter((f) => f.complexity > 10 && f.complexity <= 20).length;
@@ -55,6 +59,7 @@ export async function buildReport(absDir, root) {
55
59
  score -= Math.min(22, cycles.length * 6);
56
60
  score -= Math.min(28, veryHigh * 4 + high * 1);
57
61
  score -= Math.min(12, god.filter((g) => g.importCount >= 8).length * 4);
62
+ score -= Math.min(10, layerViolations.length);
58
63
  score = Math.max(0, Math.round(score));
59
64
  const languages = [...langCount.entries()]
60
65
  .map(([lang, f]) => ({ lang, files: f }))
@@ -72,6 +77,8 @@ export async function buildReport(absDir, root) {
72
77
  cycles: { count: cycles.length, items: cycles.slice(0, 12).map((c) => c.cycle) },
73
78
  godNodes: god.map((g) => ({ symbol: g.symbol, file: g.file, importCount: g.importCount })),
74
79
  complexity: { average: cxN ? Math.round((cxSum / cxN) * 10) / 10 : 0, max: cxMax, hotspots: hotspots.slice(0, 12) },
80
+ layerViolations: { count: layerViolations.length, items: layerViolations.slice(0, 12) },
81
+ modules: modules.slice(0, 10),
75
82
  };
76
83
  }
77
84
  /* ─── Premium HTML dashboard ───────────────────────────────────────────────── */
@@ -84,6 +91,9 @@ function esc(s) {
84
91
  function ratingColor(r) {
85
92
  return r === "very-high" ? "#e24b4a" : r === "high" ? "#d85a30" : r === "moderate" ? "#ba7517" : "#1d9e75";
86
93
  }
94
+ function instColor(i) {
95
+ return i >= 0.8 ? "#e24b4a" : i <= 0.2 ? "#1d9e75" : "#ba7517";
96
+ }
87
97
  function statCard(label, value, accent) {
88
98
  return `<div class="stat"><div class="sv"${accent ? ` style="color:${accent}"` : ""}>${value}</div><div class="sl">${label}</div></div>`;
89
99
  }
@@ -109,6 +119,13 @@ export function buildReportHtml(d) {
109
119
  const cycles = d.cycles.count
110
120
  ? d.cycles.items.map((c) => `<div class="li"><span class="mono">${esc(c.join(" → "))}</span></div>`).join("")
111
121
  : `<div class="ok">✓ No circular dependencies</div>`;
122
+ const modules = d.modules.length
123
+ ? d.modules.map((m) => bar(`${m.module} · ${m.files} file(s)`, m.instability, 1, instColor(m.instability), `Ca ${m.afferent} · Ce ${m.efferent} · <b>I ${m.instability.toFixed(2)}</b>`)).join("")
124
+ : `<div class="empty">No cross-module imports.</div>`;
125
+ const sdp = d.layerViolations.count
126
+ ? d.layerViolations.items.map((v) => `<div class="li"><span class="mono">${esc(v.from)}</span><span class="dim">→ ${esc(v.to)}</span><span class="pill" style="color:${instColor(0.9)}">+${v.severity.toFixed(2)}</span></div>`).join("")
127
+ + (d.layerViolations.count > d.layerViolations.items.length ? `<div class="more">+${d.layerViolations.count - d.layerViolations.items.length} more…</div>` : "")
128
+ : `<div class="ok">✓ No stability inversions (SDP)</div>`;
112
129
  return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
113
130
  <title>${esc(d.project)} — code health</title><style>
114
131
  :root{--bg:#fafaf8;--card:#fff;--bd:#e7e5df;--tx:#2b2b28;--dim:#8a8880;--soft:#f1efe9}
@@ -149,6 +166,7 @@ export function buildReportHtml(d) {
149
166
  ${statCard("Max complexity", d.complexity.max, ratingColor(d.complexity.max > 20 ? "very-high" : d.complexity.max > 10 ? "high" : "low"))}
150
167
  ${statCard("Dead exports", d.dead.count, d.dead.count ? "#d85a30" : "#1d9e75")}
151
168
  ${statCard("Cycles", d.cycles.count, d.cycles.count ? "#e24b4a" : "#1d9e75")}
169
+ ${statCard("SDP violations", d.layerViolations.count, d.layerViolations.count ? "#d85a30" : "#1d9e75")}
152
170
  </div>
153
171
  <div class="card"><h2>Language breakdown</h2>${langs}</div>
154
172
  <div class="card"><h2>Complexity hotspots</h2>${hotspots}</div>
@@ -156,6 +174,10 @@ export function buildReportHtml(d) {
156
174
  <div class="card"><h2>God nodes (most imported)</h2>${god}</div>
157
175
  <div class="card"><h2>Circular dependencies</h2>${cycles}</div>
158
176
  </div>
177
+ <div class="two">
178
+ <div class="card"><h2>Module coupling (instability)</h2>${modules}</div>
179
+ <div class="card"><h2>Layer violations (stable → volatile)</h2>${sdp}</div>
180
+ </div>
159
181
  <div class="card"><h2>Dead exports (high confidence)</h2>${dead}</div>
160
182
  <div class="foot">Generated by AST-MCP · universal-ast-mapper</div>
161
183
  </div></body></html>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-ast-mapper",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "MCP server that maps source files into a normalized code skeleton (JSON + HTML) using tree-sitter.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",