universal-ast-mapper 1.27.0 → 2.0.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.
Files changed (55) hide show
  1. package/BLUEPRINT.md +230 -230
  2. package/CHANGELOG.md +466 -321
  3. package/README.md +878 -877
  4. package/package.json +48 -47
  5. package/scripts/install-skill.mjs +187 -187
  6. package/dist/analysis.js +0 -134
  7. package/dist/callgraph.js +0 -467
  8. package/dist/check.js +0 -112
  9. package/dist/cli.js +0 -1275
  10. package/dist/complexity.js +0 -98
  11. package/dist/config.js +0 -53
  12. package/dist/contextpack.js +0 -79
  13. package/dist/coupling.js +0 -35
  14. package/dist/crosslang.js +0 -425
  15. package/dist/diskcache.js +0 -97
  16. package/dist/explorer.js +0 -123
  17. package/dist/extractors/c.js +0 -204
  18. package/dist/extractors/common.js +0 -56
  19. package/dist/extractors/cpp.js +0 -272
  20. package/dist/extractors/csharp.js +0 -209
  21. package/dist/extractors/go.js +0 -212
  22. package/dist/extractors/java.js +0 -152
  23. package/dist/extractors/kotlin.js +0 -159
  24. package/dist/extractors/php.js +0 -208
  25. package/dist/extractors/python.js +0 -153
  26. package/dist/extractors/ruby.js +0 -146
  27. package/dist/extractors/rust.js +0 -249
  28. package/dist/extractors/swift.js +0 -192
  29. package/dist/extractors/typescript.js +0 -577
  30. package/dist/gitdiff.js +0 -178
  31. package/dist/graph-analysis.js +0 -279
  32. package/dist/graph.js +0 -165
  33. package/dist/html.js +0 -326
  34. package/dist/index.js +0 -1407
  35. package/dist/layers.js +0 -36
  36. package/dist/modulecoupling.js +0 -0
  37. package/dist/parser.js +0 -84
  38. package/dist/pool.js +0 -114
  39. package/dist/prompts.js +0 -67
  40. package/dist/registry.js +0 -87
  41. package/dist/report.js +0 -187
  42. package/dist/resolver.js +0 -222
  43. package/dist/roots.js +0 -47
  44. package/dist/search.js +0 -68
  45. package/dist/semantic.js +0 -365
  46. package/dist/sfc.js +0 -27
  47. package/dist/skeleton.js +0 -132
  48. package/dist/sourcemap.js +0 -60
  49. package/dist/testmap.js +0 -167
  50. package/dist/tsconfig.js +0 -212
  51. package/dist/typeflow.js +0 -124
  52. package/dist/types.js +0 -5
  53. package/dist/unused-params.js +0 -127
  54. package/dist/worker.js +0 -27
  55. package/dist/workspace.js +0 -330
package/dist/diskcache.js DELETED
@@ -1,97 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import crypto from "node:crypto";
4
- // ─── Persistent (on-disk) parse cache ─────────────────────────────────────────
5
- // Content-hash keyed: the key embeds schemaVersion + grammar source + detail +
6
- // the file's raw bytes, so entries never go stale — a changed file simply maps
7
- // to a new key. Stored as sharded JSON files under <root>/.ast-map/cache.
8
- // Enabled by calling initDiskCache() once at startup (CLI / MCP server / worker).
9
- let cacheDir = null;
10
- /** Enable (or disable with null) the disk cache for this process. */
11
- export function initDiskCache(dir) {
12
- cacheDir = dir;
13
- }
14
- /** The currently active cache directory, or null when disabled. */
15
- export function diskCacheDir() {
16
- return cacheDir;
17
- }
18
- /** Conventional cache location for a project root. */
19
- export function defaultCacheDir(root) {
20
- return path.join(root, ".ast-map", "cache");
21
- }
22
- /** Stable cache key for a (source, detail, schema, grammar) tuple. */
23
- export function diskCacheKey(source, detail, schemaVersion, grammarSource) {
24
- return crypto
25
- .createHash("sha1")
26
- .update(`${schemaVersion}\0${grammarSource}\0${detail}\0`)
27
- .update(source)
28
- .digest("hex");
29
- }
30
- function shardPath(dir, key) {
31
- return path.join(dir, key.slice(0, 2), key.slice(2) + ".json");
32
- }
33
- /** Read a cached skeleton, or null on miss / disabled / corrupt entry. */
34
- export function diskCacheGet(key) {
35
- if (!cacheDir)
36
- return null;
37
- try {
38
- const raw = fs.readFileSync(shardPath(cacheDir, key), "utf8");
39
- const parsed = JSON.parse(raw);
40
- return parsed.skel ?? null;
41
- }
42
- catch {
43
- return null;
44
- }
45
- }
46
- /** Persist a skeleton under the given key (best-effort, never throws). */
47
- export function diskCachePut(key, skel) {
48
- if (!cacheDir)
49
- return;
50
- try {
51
- const file = shardPath(cacheDir, key);
52
- fs.mkdirSync(path.dirname(file), { recursive: true });
53
- const tmp = file + "." + process.pid + ".tmp";
54
- fs.writeFileSync(tmp, JSON.stringify({ skel }));
55
- fs.renameSync(tmp, file);
56
- }
57
- catch {
58
- /* cache write failures are non-fatal */
59
- }
60
- }
61
- /** Count entries + total size of a cache directory. */
62
- export function diskCacheStats(dir) {
63
- let entries = 0;
64
- let bytes = 0;
65
- const walk = (d) => {
66
- let names = [];
67
- try {
68
- names = fs.readdirSync(d, { withFileTypes: true });
69
- }
70
- catch {
71
- return;
72
- }
73
- for (const e of names) {
74
- const p = path.join(d, e.name);
75
- if (e.isDirectory())
76
- walk(p);
77
- else if (e.name.endsWith(".json")) {
78
- entries++;
79
- try {
80
- bytes += fs.statSync(p).size;
81
- }
82
- catch { /* skip */ }
83
- }
84
- }
85
- };
86
- walk(dir);
87
- return { dir, entries, bytes };
88
- }
89
- /** Remove every entry in a cache directory. Returns how many were removed. */
90
- export function clearDiskCache(dir) {
91
- const { entries } = diskCacheStats(dir);
92
- try {
93
- fs.rmSync(dir, { recursive: true, force: true });
94
- }
95
- catch { /* best-effort */ }
96
- return entries;
97
- }
package/dist/explorer.js DELETED
@@ -1,123 +0,0 @@
1
- /** Derive a file-level dependency graph (nodes = files, edges = imports). */
2
- function deriveFileGraph(graph) {
3
- const nodeMap = new Map(graph.nodes.map((n) => [n.id, n]));
4
- // top-level symbol names per file (for the detail panel).
5
- const fileSyms = new Map();
6
- for (const n of graph.nodes) {
7
- if (n.nodeType !== "symbol")
8
- continue;
9
- const s = n;
10
- if (s.id.indexOf("::") !== s.id.lastIndexOf("::"))
11
- continue; // skip nested (one :: only)
12
- const arr = fileSyms.get(s.file) ?? [];
13
- if (arr.length < 60)
14
- arr.push(s.kind + " " + s.symbol);
15
- fileSyms.set(s.file, arr);
16
- }
17
- const nodes = [];
18
- for (const n of graph.nodes) {
19
- if (n.nodeType !== "file")
20
- continue;
21
- const f = n;
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) ?? [], ca: 0, ce: 0, inst: 0 });
24
- }
25
- const seen = new Set();
26
- const links = [];
27
- for (const e of graph.edges) {
28
- if (e.edgeType !== "imports")
29
- continue;
30
- const to = nodeMap.get(e.to);
31
- const toFile = to ? (to.nodeType === "file" ? to.id : to.file) : null;
32
- if (!toFile || e.from === toFile)
33
- continue;
34
- const key = e.from + "|" + toFile;
35
- if (seen.has(key))
36
- continue;
37
- seen.add(key);
38
- links.push({ source: e.from, target: toFile });
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
- }
52
- return { nodes, links };
53
- }
54
- const STYLE = "body{margin:0;font-family:system-ui,sans-serif;color:#222;background:#fafafa}" +
55
- "#bar{position:fixed;top:0;left:0;right:0;height:48px;display:flex;align-items:center;gap:12px;padding:0 14px;background:#fff;border-bottom:1px solid #e5e5e5;z-index:4;box-sizing:border-box}" +
56
- "#bar h1{font-size:14px;margin:0;font-weight:600}#bar .muted{color:#888;font-size:12px}" +
57
- "#q{flex:0 0 200px;padding:6px 10px;border:1px solid #ddd;border-radius:6px;font-size:13px}" +
58
- "#cv{position:fixed;top:48px;left:0;right:0;bottom:0;display:block;cursor:grab}" +
59
- "#tip{position:fixed;pointer-events:none;background:#222;color:#fff;font-size:12px;padding:4px 8px;border-radius:5px;display:none;z-index:5}" +
60
- "#panel{position:fixed;top:48px;right:0;bottom:0;width:300px;background:#fff;border-left:1px solid #e5e5e5;z-index:3;overflow-y:auto;padding:14px 16px;box-sizing:border-box;display:none;font-size:13px}" +
61
- "#panel h2{font-size:14px;margin:0 0 2px;word-break:break-all}#panel .path{color:#888;font-size:11px;margin-bottom:10px;word-break:break-all}" +
62
- "#panel .meta{color:#555;margin-bottom:12px}#panel h3{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:#999;margin:14px 0 6px}" +
63
- "#panel .row{padding:3px 6px;border-radius:5px;cursor:pointer;word-break:break-all;line-height:1.5}#panel .row:hover{background:#f0f0f0}" +
64
- "#panel .sym{color:#444;padding:2px 6px;word-break:break-all}#panel .k{color:#999;font-size:11px}" +
65
- "#close{position:absolute;top:10px;right:12px;cursor:pointer;color:#999;font-size:18px;line-height:1;border:none;background:none}" +
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}}";
70
- const CLIENT = "var c=document.getElementById('cv'),ctx=c.getContext('2d'),tip=document.getElementById('tip'),panel=document.getElementById('panel');" +
71
- "var PANELW=300,panelOpen=false;" +
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();" +
73
- "function availW(){return W-(panelOpen?PANELW:0);}" +
74
- "var nodes=DATA.nodes,links=DATA.links,byId={};nodes.forEach(function(n){byId[n.id]=n;n.vx=0;n.vy=0;});" +
75
- "var deg={},out={},inn={};links.forEach(function(l){deg[l.source]=(deg[l.source]||0)+1;deg[l.target]=(deg[l.target]||0)+1;(out[l.source]=out[l.source]||[]).push(l.target);(inn[l.target]=inn[l.target]||[]).push(l.source);});" +
76
- "var sim=nodes.filter(function(n){return deg[n.id];}),orphans=nodes.filter(function(n){return !deg[n.id];});" +
77
- "sim.forEach(function(n){n.x=W/2+(Math.random()-0.5)*240;n.y=H/2+(Math.random()-0.5)*240;});" +
78
- "var groups={},gi=0;function color(g){if(groups[g]==null)groups[g]=gi++;return 'hsl('+((groups[g]*67)%360)+',58%,55%)';}" +
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);});" +
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)+'%)';}" +
82
- "function radius(n){return 4+Math.sqrt(n.symbols||0)*1.7;}" +
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;}}" +
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;});" +
85
- "for(var i=0;i<sim.length;i++){var n=sim[i];if(n===drag)continue;n.vx*=0.85;n.vy*=0.85;if(n.vx>30)n.vx=30;if(n.vx<-30)n.vx=-30;if(n.vy>30)n.vy=30;if(n.vy<-30)n.vy=-30;n.x+=n.vx;n.y+=n.vy;}}" +
86
- "function bb4(arr){var a=1e9,b=1e9,c2=-1e9,d2=-1e9;for(var i=0;i<arr.length;i++){var n=arr[i];if(n.x<a)a=n.x;if(n.y<b)b=n.y;if(n.x>c2)c2=n.x;if(n.y>d2)d2=n.y;}return[a,b,c2,d2];}" +
87
- "function layoutOrphans(){if(!orphans.length)return;var bb=sim.length?bb4(sim):[W*0.3,H*0.3,W*0.7,H*0.5];var left=bb[0],bottom=bb[3]+46,wide=Math.max(bb[2]-bb[0],260);var cols=Math.max(1,Math.ceil(Math.sqrt(orphans.length*1.8)));var gap=Math.max(22,wide/cols);for(var i=0;i<orphans.length;i++){orphans[i].x=left+(i%cols)*gap;orphans[i].y=bottom+Math.floor(i/cols)*22;}}" +
88
- "function fitView(){var bb=bb4(nodes);var aw=availW();var bw=Math.max(bb[2]-bb[0],1),bh=Math.max(bb[3]-bb[1],1),k=Math.min(aw/(bw+70),H/(bh+70));k=Math.max(0.12,Math.min(k,2.2));view.k=k;view.x=aw/2-((bb[0]+bb[2])/2)*k;view.y=H/2-((bb[1]+bb[3])/2)*k;}" +
89
- "function center(n){view.k=Math.max(view.k,0.7);view.x=availW()/2-n.x*view.k;view.y=H/2-n.y*view.k;}" +
90
- "function esc(t){return String(t).replace(/&/g,'&amp;').replace(/</g,'&lt;');}" +
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('');}" +
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>';" +
93
- "panel.innerHTML='<button id=\"close\">&times;</button>'+'<h2>'+esc(n.id.split('/').pop())+'</h2><div class=\"path\">'+esc(n.id)+'</div>'+'<div class=\"meta\">'+esc(n.lang)+' &middot; '+(n.symbols||0)+' symbols'+(deg[n.id]?' &middot; Ca '+(n.ca||0)+' &middot; Ce '+(n.ce||0)+' &middot; I '+(n.inst||0):' &middot; no in-scope deps')+'</div>'+'<h3>Imports ('+imp.length+')</h3>'+rowList(imp)+'<h3>Imported by ('+impBy.length+')</h3>'+rowList(impBy)+'<h3>Symbols</h3>'+syms;" +
94
- "panel.style.display='block';}" +
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]);}});" +
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;" +
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();});" +
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;}}" +
99
- "orphans.forEach(function(n){dot(n,true);});sim.forEach(function(n){dot(n,false);});" +
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();}" +
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);}" +
102
- "function world(e){return{x:(e.clientX-view.x)/view.k,y:(e.clientY-48-view.y)/view.k};}" +
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;}" +
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};}});" +
105
- "c.addEventListener('dblclick',function(){panelOpen=false;sel=null;panel.style.display='none';autofit=true;});" +
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';}});" +
107
- "addEventListener('mouseup',function(){drag=null;pan=null;});" +
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});" +
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';});" +
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();";
112
- /** Build a self-contained, dependency-free HTML graph explorer. */
113
- export function buildExplorerHtml(graph, root) {
114
- const data = deriveFileGraph(graph);
115
- const dataJson = JSON.stringify(data);
116
- const title = root.split(/[\\/]/).filter(Boolean).pop() || "project";
117
- return ("<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" +
118
- "<title>AST-MCP — " + title + " graph</title><style>" + STYLE + "</style></head><body>" +
119
- "<div id=\"bar\"><h1>AST-MCP graph</h1><span class=\"muted\">" + data.nodes.length + " files · " + data.links.length + " edges · drag / scroll / click</span>" +
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>" +
122
- "<script>var DATA=" + dataJson + ";</script><script>" + CLIENT + "</script></body></html>");
123
- }
@@ -1,204 +0,0 @@
1
- import { namedChildren, nameOf, headerSignature, leadingComment } from "../parser.js";
2
- import { makeSymbol } from "./common.js";
3
- /* ─── helpers ─────────────────────────────────────────────────────────────── */
4
- function childOfType(node, type) {
5
- for (let i = 0; i < node.childCount; i++) {
6
- const c = node.child(i);
7
- if (c && c.type === type)
8
- return c;
9
- }
10
- return null;
11
- }
12
- /** Recursively unwrap a declarator chain to get the identifier text. */
13
- function nameFromDeclarator(node) {
14
- if (!node)
15
- return null;
16
- switch (node.type) {
17
- case "identifier":
18
- case "field_identifier":
19
- case "type_identifier":
20
- return node.text;
21
- case "pointer_declarator":
22
- case "array_declarator":
23
- case "parenthesized_declarator":
24
- return nameFromDeclarator(node.childForFieldName("declarator"));
25
- case "function_declarator": {
26
- const d = node.childForFieldName("declarator");
27
- return nameFromDeclarator(d);
28
- }
29
- case "init_declarator":
30
- return nameFromDeclarator(node.childForFieldName("declarator"));
31
- default:
32
- // best-effort: find first identifier-like child
33
- for (let i = 0; i < node.namedChildCount; i++) {
34
- const c = node.namedChild(i);
35
- if (c && (c.type === "identifier" || c.type === "field_identifier" || c.type === "type_identifier"))
36
- return c.text;
37
- }
38
- return null;
39
- }
40
- }
41
- function hasStaticStorage(node) {
42
- for (let i = 0; i < node.childCount; i++) {
43
- const c = node.child(i);
44
- if (c && c.type === "storage_class_specifier" && c.text === "static")
45
- return true;
46
- }
47
- return false;
48
- }
49
- /* ─── imports (#include) ──────────────────────────────────────────────────── */
50
- export function extractImportsC(root, _source) {
51
- const out = [];
52
- for (const child of namedChildren(root)) {
53
- if (child.type !== "preproc_include")
54
- continue;
55
- const pathNode = childOfType(child, "system_lib_string") ?? childOfType(child, "string_literal");
56
- if (!pathNode)
57
- continue;
58
- const raw = pathNode.text.replace(/^[<"]|[>"]$/g, "");
59
- const base = raw.split("/").pop() ?? raw;
60
- const sym = base.replace(/\.[hH]$|\.hpp$|\.hxx$|\.hh$/, "");
61
- out.push({ symbol: sym, from: raw });
62
- }
63
- return out;
64
- }
65
- /* ─── symbol extraction ───────────────────────────────────────────────────── */
66
- export function extractC(root, _source) {
67
- return collect(namedChildren(root));
68
- }
69
- function collect(nodes) {
70
- const out = [];
71
- for (const n of nodes) {
72
- const res = handle(n);
73
- if (Array.isArray(res))
74
- out.push(...res);
75
- else if (res)
76
- out.push(res);
77
- }
78
- return out;
79
- }
80
- function handle(node) {
81
- switch (node.type) {
82
- case "struct_specifier":
83
- case "union_specifier": {
84
- const name = nameOf(node);
85
- if (!name)
86
- return null;
87
- const body = node.childForFieldName("body");
88
- return makeSymbol({
89
- name,
90
- kind: "struct",
91
- node,
92
- rawKind: node.type,
93
- doc: leadingComment(node),
94
- children: body ? structFields(body) : [],
95
- });
96
- }
97
- case "enum_specifier": {
98
- const name = nameOf(node);
99
- if (!name)
100
- return null;
101
- return makeSymbol({
102
- name,
103
- kind: "enum",
104
- node,
105
- rawKind: node.type,
106
- doc: leadingComment(node),
107
- });
108
- }
109
- case "function_definition": {
110
- const decl = node.childForFieldName("declarator");
111
- const name = nameFromDeclarator(decl);
112
- if (!name)
113
- return null;
114
- const isStatic = hasStaticStorage(node);
115
- return makeSymbol({
116
- name,
117
- kind: "function",
118
- node,
119
- rawKind: node.type,
120
- signature: headerSignature(node, node.childForFieldName("body")),
121
- visibility: isStatic ? "private" : "public",
122
- exported: !isStatic,
123
- doc: leadingComment(node),
124
- });
125
- }
126
- case "declaration": {
127
- // top-level variable/function declarations (prototypes, externs, etc.)
128
- const decl = node.childForFieldName("declarator");
129
- const name = nameFromDeclarator(decl);
130
- if (!name)
131
- return null;
132
- // skip function prototypes — focus on real defs (function_definition)
133
- if (decl && containsFunctionDeclarator(decl))
134
- return null;
135
- return makeSymbol({
136
- name,
137
- kind: "var",
138
- node,
139
- rawKind: node.type,
140
- signature: node.text.replace(/\s+/g, " ").replace(/;$/, "").trim(),
141
- visibility: hasStaticStorage(node) ? "private" : "public",
142
- exported: !hasStaticStorage(node),
143
- });
144
- }
145
- case "preproc_def":
146
- case "preproc_function_def": {
147
- const name = nameOf(node);
148
- if (!name)
149
- return null;
150
- return makeSymbol({
151
- name,
152
- kind: "const",
153
- node,
154
- rawKind: node.type,
155
- signature: node.text.replace(/\s+/g, " ").trim(),
156
- });
157
- }
158
- case "type_definition": {
159
- // typedef — name is in the declarator (last identifier)
160
- const decl = node.childForFieldName("declarator");
161
- const name = nameFromDeclarator(decl);
162
- if (!name)
163
- return null;
164
- return makeSymbol({
165
- name,
166
- kind: "type",
167
- node,
168
- rawKind: node.type,
169
- signature: node.text.replace(/\s+/g, " ").replace(/;$/, "").trim(),
170
- });
171
- }
172
- default:
173
- return null;
174
- }
175
- }
176
- function containsFunctionDeclarator(node) {
177
- if (node.type === "function_declarator")
178
- return true;
179
- for (let i = 0; i < node.namedChildCount; i++) {
180
- const c = node.namedChild(i);
181
- if (c && containsFunctionDeclarator(c))
182
- return true;
183
- }
184
- return false;
185
- }
186
- function structFields(body) {
187
- const out = [];
188
- for (const field of namedChildren(body)) {
189
- if (field.type !== "field_declaration")
190
- continue;
191
- const decl = field.childForFieldName("declarator");
192
- const name = nameFromDeclarator(decl);
193
- if (!name)
194
- continue;
195
- out.push(makeSymbol({
196
- name,
197
- kind: "field",
198
- node: field,
199
- rawKind: field.type,
200
- signature: field.text.replace(/\s+/g, " ").replace(/;$/, "").trim(),
201
- }));
202
- }
203
- return out;
204
- }
@@ -1,56 +0,0 @@
1
- export function lineRange(node) {
2
- return { startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1 };
3
- }
4
- /** True if the identifier begins with an uppercase letter (Go export rule). */
5
- export function startsUpper(name) {
6
- const c = name[0];
7
- return !!c && c !== c.toLowerCase() && c === c.toUpperCase();
8
- }
9
- /** Python convention: a single leading underscore (but not dunder) means private. */
10
- export function pythonVisibility(name) {
11
- if (name.startsWith("__") && name.endsWith("__"))
12
- return "public"; // dunder
13
- return name.startsWith("_") ? "private" : "public";
14
- }
15
- export function makeSymbol(init) {
16
- const sym = {
17
- name: init.name,
18
- kind: init.kind,
19
- visibility: init.visibility ?? "public",
20
- range: lineRange(init.node),
21
- children: init.children ?? [],
22
- };
23
- if (init.rawKind !== undefined)
24
- sym.rawKind = init.rawKind;
25
- if (init.signature !== undefined)
26
- sym.signature = init.signature;
27
- if (init.exported !== undefined)
28
- sym.exported = init.exported;
29
- if (init.doc !== undefined)
30
- sym.doc = init.doc;
31
- return sym;
32
- }
33
- /** Count a symbol tree, including nested children. */
34
- export function countSymbols(symbols) {
35
- let n = 0;
36
- for (const s of symbols)
37
- n += 1 + countSymbols(s.children);
38
- return n;
39
- }
40
- /** Strip signature/doc/rawKind for the compact "outline" detail level. */
41
- export function toOutline(symbols) {
42
- return symbols.map((s) => {
43
- const out = {
44
- name: s.name,
45
- kind: s.kind,
46
- visibility: s.visibility,
47
- range: s.range,
48
- children: toOutline(s.children),
49
- };
50
- if (s.exported !== undefined)
51
- out.exported = s.exported;
52
- if (s.decorators)
53
- out.decorators = s.decorators;
54
- return out;
55
- });
56
- }