universal-ast-mapper 1.25.0 → 1.26.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 +12 -0
- package/README.md +2 -1
- package/dist/explorer.js +25 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ since 1.0.0, guarantees a stable MCP tool / CLI surface across the 1.x line.
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [1.26.0] — 2026-06-11 · Coupling overlay in the explorer
|
|
10
|
+
- **`ast-map explore` color modes** — new toolbar dropdown: `color: folder`
|
|
11
|
+
(existing per-directory hues) or **`color: coupling`** — nodes shaded by
|
|
12
|
+
**instability** I = Ce/(Ca+Ce) on a green (0, stable) → yellow → red
|
|
13
|
+
(1, volatile) scale; orphan files stay gray.
|
|
14
|
+
- **Legend** (bottom-left, shown in coupling mode) explains the scale; the hover
|
|
15
|
+
tooltip and the detail sidebar now show **Ca / Ce / I** per file.
|
|
16
|
+
- Explorer nodes carry `ca` / `ce` / `inst` computed from the deduped file-level
|
|
17
|
+
import edges — same definition as `get_coupling` (Robert C. Martin metrics).
|
|
18
|
+
- Still a single self-contained HTML file, dark-mode aware, zero dependencies.
|
|
19
|
+
- Tests: +5 checks in `test/analysis.mjs` (144 total).
|
|
20
|
+
|
|
9
21
|
## [1.25.0] — 2026-06-11 · Semantic symbol search
|
|
10
22
|
- **New MCP tool `semantic_search`** + **CLI `ast-map find <query> [dir]`** — find
|
|
11
23
|
symbols by *meaning*, not exact name: "remove expired cache entries" →
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ An **MCP server + CLI tool** that turns source code into structured, machine-rea
|
|
|
4
4
|
|
|
5
5
|
Built on [tree-sitter](https://tree-sitter.github.io/) WASM grammars. Zero regex guessing — real AST parsing.
|
|
6
6
|
|
|
7
|
-
**29 MCP tools / 31 CLI commands / 5 MCP prompts** spanning skeletons, dependency graphs, and deep analysis — dead code, cycles, change-impact, complexity, duplicates, unused params, type-flow, decorators — plus monorepo support, an interactive **graph explorer** (`ast-map explore`), **watch mode**, a one-page **health dashboard** (`ast-map report`), a **persistent parse cache + parallel parsing** (warm re-scans skip parsing entirely), and a **CI quality gate** (`ast-map check`, baseline ratchet).
|
|
7
|
+
**29 MCP tools / 31 CLI commands / 5 MCP prompts** spanning skeletons, dependency graphs, and deep analysis — dead code, cycles, change-impact, complexity, duplicates, unused params, type-flow, decorators — plus monorepo support, an interactive **graph explorer** with a **coupling overlay** (`ast-map explore`), **watch mode**, a one-page **health dashboard** (`ast-map report`), a **persistent parse cache + parallel parsing** (warm re-scans skip parsing entirely), and a **CI quality gate** (`ast-map check`, baseline ratchet).
|
|
8
8
|
|
|
9
9
|
**Supported languages:** TypeScript · TSX · JavaScript (ESM/CJS) · Python · Go · Rust · Java · C# · C · C++ · Kotlin · Swift · Vue · Svelte (SFC `<script>`) · **PHP** · **Ruby**
|
|
10
10
|
|
|
@@ -820,6 +820,7 @@ Not part of the public API: the internal `src/` module layout and the generated
|
|
|
820
820
|
|
|
821
821
|
| Version | What changed |
|
|
822
822
|
|---------|--------------|
|
|
823
|
+
| **1.26.0** | **Coupling overlay in the explorer** — `ast-map explore` gains a `color: coupling` mode: nodes shaded by **instability** I = Ce/(Ca+Ce) on a green (stable) → red (volatile) scale, with a legend, and Ca / Ce / I readouts in the hover tooltip and detail sidebar. Spot load-bearing files and volatile hotspots at a glance. |
|
|
823
824
|
| **1.25.0** | **Semantic symbol search** — new MCP tool `semantic_search` + CLI `ast-map find <query>`: find symbols by *meaning* ("remove expired sessions" → `clearDiskCache`). Identifier tokenization + 60-group programming thesaurus + stemming + fuzzy matching + BM25-style IDF ranking over names, docs, signatures and paths. No embeddings, no network. (**29 tools / 31 commands**) |
|
|
824
825
|
| **1.24.0** | **TS path-alias resolution** — bare imports like `@/components/Button` now resolve via the **nearest** `tsconfig.json`/`jsconfig.json` (`compilerOptions.paths` + `baseUrl`, relative `extends` chains, longest-prefix matching, string-aware JSONC parser). Wired into `resolve_imports`, the symbol graph, and the call graph — on a real Next.js app this took the import graph from 31 to **324 edges** and cut false dead-exports by ~30%. |
|
|
825
826
|
| **1.23.0** | **Configurable root boundary** — `AST_MAP_ROOT` accepts **multiple roots** (path-delimiter separated) and `AST_MAP_UNLOCKED=1` allows analyzing **any absolute path** on request (default stays locked). Analysis/graph/report rel-paths now computed against the matched root, so cross-root results are correct. New `roots` module + 13-check test suite. |
|
package/dist/explorer.js
CHANGED
|
@@ -20,7 +20,7 @@ function deriveFileGraph(graph) {
|
|
|
20
20
|
continue;
|
|
21
21
|
const f = n;
|
|
22
22
|
const parts = f.id.split("/");
|
|
23
|
-
nodes.push({ id: f.id, symbols: f.symbolCount, group: parts.length > 1 ? parts[0] : "(root)", lang: f.language, syms: fileSyms.get(f.id) ?? [] });
|
|
23
|
+
nodes.push({ id: f.id, symbols: f.symbolCount, group: parts.length > 1 ? parts[0] : "(root)", lang: f.language, syms: fileSyms.get(f.id) ?? [], ca: 0, ce: 0, inst: 0 });
|
|
24
24
|
}
|
|
25
25
|
const seen = new Set();
|
|
26
26
|
const links = [];
|
|
@@ -37,6 +37,18 @@ function deriveFileGraph(graph) {
|
|
|
37
37
|
seen.add(key);
|
|
38
38
|
links.push({ source: e.from, target: toFile });
|
|
39
39
|
}
|
|
40
|
+
// Per-file coupling (Ca = fan-in, Ce = fan-out, I = Ce/(Ca+Ce)) from the deduped links.
|
|
41
|
+
const outSet = new Map();
|
|
42
|
+
const inSet = new Map();
|
|
43
|
+
for (const l of links) {
|
|
44
|
+
(outSet.get(l.source) ?? outSet.set(l.source, new Set()).get(l.source)).add(l.target);
|
|
45
|
+
(inSet.get(l.target) ?? inSet.set(l.target, new Set()).get(l.target)).add(l.source);
|
|
46
|
+
}
|
|
47
|
+
for (const n of nodes) {
|
|
48
|
+
n.ce = outSet.get(n.id)?.size ?? 0;
|
|
49
|
+
n.ca = inSet.get(n.id)?.size ?? 0;
|
|
50
|
+
n.inst = n.ca + n.ce === 0 ? 0 : Math.round((n.ce / (n.ca + n.ce)) * 100) / 100;
|
|
51
|
+
}
|
|
40
52
|
return { nodes, links };
|
|
41
53
|
}
|
|
42
54
|
const STYLE = "body{margin:0;font-family:system-ui,sans-serif;color:#222;background:#fafafa}" +
|
|
@@ -51,7 +63,10 @@ const STYLE = "body{margin:0;font-family:system-ui,sans-serif;color:#222;backgro
|
|
|
51
63
|
"#panel .row{padding:3px 6px;border-radius:5px;cursor:pointer;word-break:break-all;line-height:1.5}#panel .row:hover{background:#f0f0f0}" +
|
|
52
64
|
"#panel .sym{color:#444;padding:2px 6px;word-break:break-all}#panel .k{color:#999;font-size:11px}" +
|
|
53
65
|
"#close{position:absolute;top:10px;right:12px;cursor:pointer;color:#999;font-size:18px;line-height:1;border:none;background:none}" +
|
|
54
|
-
"
|
|
66
|
+
"#mode{padding:5px 8px;border:1px solid #ddd;border-radius:6px;font-size:12px;background:#fff;color:#222}" +
|
|
67
|
+
"#leg{position:fixed;left:14px;bottom:14px;z-index:3;background:#fff;border:1px solid #e5e5e5;border-radius:8px;padding:8px 12px;font-size:11px;color:#555;display:none}" +
|
|
68
|
+
"#leg .bar{width:150px;height:8px;border-radius:4px;background:linear-gradient(90deg,hsl(120,65%,46%),hsl(60,75%,50%),hsl(0,70%,52%));margin:5px 0 3px}" +
|
|
69
|
+
"@media(prefers-color-scheme:dark){body{color:#ddd;background:#161616}#bar,#panel{background:#1e1e1e;border-color:#333}#q{background:#2a2a2a;border-color:#444;color:#ddd}#panel .row:hover{background:#2a2a2a}#panel .sym{color:#bbb}#mode{background:#2a2a2a;border-color:#444;color:#ddd}#leg{background:#1e1e1e;border-color:#333;color:#bbb}}";
|
|
55
70
|
const CLIENT = "var c=document.getElementById('cv'),ctx=c.getContext('2d'),tip=document.getElementById('tip'),panel=document.getElementById('panel');" +
|
|
56
71
|
"var PANELW=300,panelOpen=false;" +
|
|
57
72
|
"var W,H;function resize(){var r=devicePixelRatio||1;W=innerWidth||c.clientWidth||800;H=(innerHeight-48)||c.clientHeight||600;c.width=W*r;c.height=H*r;ctx.setTransform(r,0,0,r,0,0);}addEventListener('resize',function(){resize();});resize();" +
|
|
@@ -62,7 +77,8 @@ const CLIENT = "var c=document.getElementById('cv'),ctx=c.getContext('2d'),tip=d
|
|
|
62
77
|
"sim.forEach(function(n){n.x=W/2+(Math.random()-0.5)*240;n.y=H/2+(Math.random()-0.5)*240;});" +
|
|
63
78
|
"var groups={},gi=0;function color(g){if(groups[g]==null)groups[g]=gi++;return 'hsl('+((groups[g]*67)%360)+',58%,55%)';}" +
|
|
64
79
|
"var adj={};links.forEach(function(l){(adj[l.source]=adj[l.source]||[]).push(l.target);(adj[l.target]=adj[l.target]||[]).push(l.source);});" +
|
|
65
|
-
"var view={x:0,y:0,k:1},sel=null,hover=null,drag=null,pan=null,q='',autofit=true;" +
|
|
80
|
+
"var view={x:0,y:0,k:1},sel=null,hover=null,drag=null,pan=null,q='',autofit=true,mode='group';" +
|
|
81
|
+
"function instColor(i){return 'hsl('+Math.round((1-i)*120)+',65%,'+Math.round(46+i*8)+'%)';}" +
|
|
66
82
|
"function radius(n){return 4+Math.sqrt(n.symbols||0)*1.7;}" +
|
|
67
83
|
"function tick(){if(!sim.length)return;var k=0.0016;for(var i=0;i<sim.length;i++){var a=sim[i];a.vx+=(W/2-a.x)*k;a.vy+=(H/2-a.y)*k;for(var j=i+1;j<sim.length;j++){var b=sim[j];var dx=a.x-b.x,dy=a.y-b.y,d2=dx*dx+dy*dy;if(d2<100)d2=100;var d=Math.sqrt(d2),f=2200/d2,fx=f*dx/d,fy=f*dy/d;a.vx+=fx;a.vy+=fy;b.vx-=fx;b.vy-=fy;}}" +
|
|
68
84
|
"links.forEach(function(l){var a=byId[l.source],b=byId[l.target];if(!a||!b)return;var dx=b.x-a.x,dy=b.y-a.y,d=Math.sqrt(dx*dx+dy*dy)+0.01,f=(d-90)*0.02,fx=f*dx/d,fy=f*dy/d;a.vx+=fx;a.vy+=fy;b.vx-=fx;b.vy-=fy;});" +
|
|
@@ -74,12 +90,12 @@ const CLIENT = "var c=document.getElementById('cv'),ctx=c.getContext('2d'),tip=d
|
|
|
74
90
|
"function esc(t){return String(t).replace(/&/g,'&').replace(/</g,'<');}" +
|
|
75
91
|
"function rowList(ids){if(!ids||!ids.length)return '<div class=\"sym\" style=\"color:#aaa\">none</div>';return ids.slice().sort().map(function(id){return '<div class=\"row\" data-id=\"'+esc(id)+'\">'+esc(id)+'</div>';}).join('');}" +
|
|
76
92
|
"function showPanel(n){sel=n;panelOpen=true;var imp=out[n.id]||[],impBy=inn[n.id]||[];var syms=(n.syms||[]).map(function(s){var i=s.indexOf(' ');return '<div class=\"sym\"><span class=\"k\">'+esc(s.slice(0,i))+'</span> '+esc(s.slice(i+1))+'</div>';}).join('')||'<div class=\"sym\" style=\"color:#aaa\">none</div>';" +
|
|
77
|
-
"panel.innerHTML='<button id=\"close\">×</button>'+'<h2>'+esc(n.id.split('/').pop())+'</h2><div class=\"path\">'+esc(n.id)+'</div>'+'<div class=\"meta\">'+esc(n.lang)+' · '+(n.symbols||0)+' symbols'+(deg[n.id]?'':' · no in-scope deps')+'</div>'+'<h3>Imports ('+imp.length+')</h3>'+rowList(imp)+'<h3>Imported by ('+impBy.length+')</h3>'+rowList(impBy)+'<h3>Symbols</h3>'+syms;" +
|
|
93
|
+
"panel.innerHTML='<button id=\"close\">×</button>'+'<h2>'+esc(n.id.split('/').pop())+'</h2><div class=\"path\">'+esc(n.id)+'</div>'+'<div class=\"meta\">'+esc(n.lang)+' · '+(n.symbols||0)+' symbols'+(deg[n.id]?' · Ca '+(n.ca||0)+' · Ce '+(n.ce||0)+' · I '+(n.inst||0):' · no in-scope deps')+'</div>'+'<h3>Imports ('+imp.length+')</h3>'+rowList(imp)+'<h3>Imported by ('+impBy.length+')</h3>'+rowList(impBy)+'<h3>Symbols</h3>'+syms;" +
|
|
78
94
|
"panel.style.display='block';}" +
|
|
79
95
|
"panel.addEventListener('click',function(e){if(e.target.id==='close'){panelOpen=false;sel=null;panel.style.display='none';autofit=true;return;}var id=e.target.getAttribute('data-id');if(id&&byId[id]){showPanel(byId[id]);center(byId[id]);}});" +
|
|
80
96
|
"function draw(){ctx.clearRect(0,0,W,H);ctx.save();ctx.translate(view.x,view.y);ctx.scale(view.k,view.k);ctx.lineWidth=0.8;" +
|
|
81
97
|
"links.forEach(function(l){var a=byId[l.source],b=byId[l.target];if(!a||!b)return;var on=sel&&(l.source===sel.id||l.target===sel.id);ctx.strokeStyle=on?'rgba(110,110,240,0.9)':'rgba(150,150,150,0.18)';ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.stroke();});" +
|
|
82
|
-
"function dot(n,orphan){var dim=(sel&&n!==sel&&(adj[sel.id]||[]).indexOf(n.id)<0)||(q&&n.id.toLowerCase().indexOf(q)<0);ctx.globalAlpha=dim?0.14:(orphan?0.55:1);ctx.beginPath();ctx.arc(n.x,n.y,orphan?3.2:radius(n),0,6.2832);ctx.fillStyle=color(n.group);ctx.fill();if(n===sel||n===hover){ctx.lineWidth=2;ctx.strokeStyle='#fff';ctx.stroke();ctx.lineWidth=0.8;}}" +
|
|
98
|
+
"function dot(n,orphan){var dim=(sel&&n!==sel&&(adj[sel.id]||[]).indexOf(n.id)<0)||(q&&n.id.toLowerCase().indexOf(q)<0);ctx.globalAlpha=dim?0.14:(orphan?0.55:1);ctx.beginPath();ctx.arc(n.x,n.y,orphan?3.2:radius(n),0,6.2832);ctx.fillStyle=mode==='inst'?(deg[n.id]?instColor(n.inst):'#999'):color(n.group);ctx.fill();if(n===sel||n===hover){ctx.lineWidth=2;ctx.strokeStyle='#fff';ctx.stroke();ctx.lineWidth=0.8;}}" +
|
|
83
99
|
"orphans.forEach(function(n){dot(n,true);});sim.forEach(function(n){dot(n,false);});" +
|
|
84
100
|
"ctx.globalAlpha=1;ctx.fillStyle=getComputedStyle(document.body).color;ctx.font='11px system-ui';sim.forEach(function(n){if(n===sel||n===hover||n.symbols>=14){ctx.fillText(n.id.split('/').pop(),n.x+radius(n)+3,n.y+3);}});ctx.restore();}" +
|
|
85
101
|
"function loop(){var w=innerWidth,hh=innerHeight-48;if(w&&hh&&(w!==W||hh!==H))resize();tick();tick();layoutOrphans();if(autofit)fitView();draw();var bx=bb4(nodes);document.getElementById(\"dbg\").textContent=\"W=\"+W+\" H=\"+H+\" iw=\"+innerWidth+\"x\"+innerHeight+\" dpr=\"+(devicePixelRatio||1)+\" k=\"+view.k.toFixed(2)+\" vx=\"+Math.round(view.x)+\" vy=\"+Math.round(view.y)+\" fit=\"+autofit+\" sim=\"+sim.length+\" orph=\"+orphans.length+\" worldBox=\"+Math.round(bx[0])+\",\"+Math.round(bx[1])+\"..\"+Math.round(bx[2])+\",\"+Math.round(bx[3]);requestAnimationFrame(loop);}" +
|
|
@@ -87,10 +103,11 @@ const CLIENT = "var c=document.getElementById('cv'),ctx=c.getContext('2d'),tip=d
|
|
|
87
103
|
"function pick(p){var all=sim.concat(orphans);for(var i=all.length-1;i>=0;i--){var n=all[i];var r=(deg[n.id]?radius(n):3.2)+5;if((p.x-n.x)*(p.x-n.x)+(p.y-n.y)*(p.y-n.y)<=r*r)return n;}return null;}" +
|
|
88
104
|
"c.addEventListener('mousedown',function(e){autofit=false;var n=pick(world(e));if(n){drag=n;showPanel(n);}else{pan={x:e.clientX-view.x,y:e.clientY-view.y};}});" +
|
|
89
105
|
"c.addEventListener('dblclick',function(){panelOpen=false;sel=null;panel.style.display='none';autofit=true;});" +
|
|
90
|
-
"addEventListener('mousemove',function(e){var p=world(e);if(drag){drag.x=p.x;drag.y=p.y;drag.vx=0;drag.vy=0;}else if(pan){view.x=e.clientX-pan.x;view.y=e.clientY-pan.y;}else{hover=pick(p);if(hover){tip.style.display='block';tip.style.left=(e.clientX+12)+'px';tip.style.top=(e.clientY+12)+'px';tip.textContent=hover.id+' · '+(hover.symbols||0)+' symbols · '+hover.lang;}else tip.style.display='none';}});" +
|
|
106
|
+
"addEventListener('mousemove',function(e){var p=world(e);if(drag){drag.x=p.x;drag.y=p.y;drag.vx=0;drag.vy=0;}else if(pan){view.x=e.clientX-pan.x;view.y=e.clientY-pan.y;}else{hover=pick(p);if(hover){tip.style.display='block';tip.style.left=(e.clientX+12)+'px';tip.style.top=(e.clientY+12)+'px';tip.textContent=hover.id+' · '+(hover.symbols||0)+' symbols · '+hover.lang+(deg[hover.id]?' · Ca '+hover.ca+' Ce '+hover.ce+' I '+hover.inst:'');}else tip.style.display='none';}});" +
|
|
91
107
|
"addEventListener('mouseup',function(){drag=null;pan=null;});" +
|
|
92
108
|
"c.addEventListener('wheel',function(e){e.preventDefault();autofit=false;var s=e.deltaY<0?1.1:0.9;var mx=e.clientX,my=e.clientY-48;view.x=mx-(mx-view.x)*s;view.y=my-(my-view.y)*s;view.k*=s;},{passive:false});" +
|
|
93
109
|
"document.getElementById('q').addEventListener('input',function(e){q=e.target.value.toLowerCase();});" +
|
|
110
|
+
"document.getElementById('mode').addEventListener('change',function(e){mode=e.target.value;document.getElementById('leg').style.display=mode==='inst'?'block':'none';});" +
|
|
94
111
|
"addEventListener('keydown',function(e){if(e.key==='d'&&e.target.tagName!=='INPUT'){var x=document.getElementById('dbg');x.style.display=x.style.display==='none'?'block':'none';}});loop();";
|
|
95
112
|
/** Build a self-contained, dependency-free HTML graph explorer. */
|
|
96
113
|
export function buildExplorerHtml(graph, root) {
|
|
@@ -100,7 +117,7 @@ export function buildExplorerHtml(graph, root) {
|
|
|
100
117
|
return ("<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" +
|
|
101
118
|
"<title>AST-MCP — " + title + " graph</title><style>" + STYLE + "</style></head><body>" +
|
|
102
119
|
"<div id=\"bar\"><h1>AST-MCP graph</h1><span class=\"muted\">" + data.nodes.length + " files · " + data.links.length + " edges · drag / scroll / click</span>" +
|
|
103
|
-
"<input id=\"q\" placeholder=\"filter files…\"
|
|
104
|
-
"<canvas id=\"cv\"></canvas><div id=\"tip\"></div><div id=\"panel\"></div><div id=\"dbg\" style=\"position:fixed;left:8px;bottom:8px;font:11px monospace;color:#e07;z-index:6;pointer-events:none;white-space:pre;display:none\"></div>" +
|
|
120
|
+
"<input id=\"q\" placeholder=\"filter files…\" /><select id=\"mode\"><option value=\"group\">color: folder</option><option value=\"inst\">color: coupling</option></select></div>" +
|
|
121
|
+
"<canvas id=\"cv\"></canvas><div id=\"tip\"></div><div id=\"panel\"></div><div id=\"leg\"><b>Instability I = Ce/(Ca+Ce)</b><div class=\"bar\"></div><div style=\"display:flex;justify-content:space-between\"><span>0 = stable</span><span>1 = volatile</span></div></div><div id=\"dbg\" style=\"position:fixed;left:8px;bottom:8px;font:11px monospace;color:#e07;z-index:6;pointer-events:none;white-space:pre;display:none\"></div>" +
|
|
105
122
|
"<script>var DATA=" + dataJson + ";</script><script>" + CLIENT + "</script></body></html>");
|
|
106
123
|
}
|
package/package.json
CHANGED