infernoflow 0.43.1 → 0.43.2

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
@@ -1,5 +1,14 @@
1
1
  # Changelog — infernoflow
2
2
 
3
+ ## 0.43.2 — 2026-05-06
4
+
5
+ ### Added
6
+ - **Component composition tree** — graph now shows parent → child component relationships. When `App.jsx` renders `<TaskList>` and `<TaskList>` renders `<TaskRow>`, those edges appear in the graph. The diagram reads like the actual JSX tree of your app, not just an unconnected list of components. Detection uses regex matching of `<CapitalizedTagName` patterns inside each component's JSX.
7
+ - **Entry-point detection** — components living in `src/App.{jsx,tsx,js,ts,vue,svelte}`, `src/main.*`, `src/index.*`, `pages/_app.*`, or `app/layout.*` are flagged as the entry. Rendered larger (radius 18 vs 11 for normal components), with a distinct pink color (`#e91e63`) and "🚪 entry" label in Mermaid output. Lets you see the root of the tree at a glance.
8
+
9
+ ### Effect on the visual
10
+ Now rooted at the entry component. Before this change, the graph showed disconnected tiers (UI / Component / Capability) with no narrative flow. After: **App (entry) → child components → their UI elements → their capabilities**. The diagram tells the story of how the app is composed.
11
+
3
12
  ## 0.43.1 — 2026-05-06
4
13
 
5
14
  ### Fixed
@@ -1,5 +1,5 @@
1
- import*as O from"node:fs";import*as v from"node:path";import{bold as k,cyan as G,gray as g,green as w,yellow as M,red as j}from"../ui/output.mjs";function D(a){try{return JSON.parse(O.readFileSync(a,"utf8"))}catch{return null}}function ne(a){return a?.stability||"experimental"}const C={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},N={frozen:j,stable:M,experimental:w};function V(a,o){const l={},r={},s={},e={};for(const t of a){const p=o.find(c=>c.id===t.id)||{};l[t.id]={id:t.id,name:t.name||p.name||p.title||t.id,stability:p.stability||"experimental",functions:t.codeAnalysis?.functions||[],calls:t.codeAnalysis?.calls||[],services:t.codeAnalysis?.services||[],dbCalls:t.codeAnalysis?.dbCalls||[],httpCalls:t.codeAnalysis?.httpCalls||[]},r[t.id]=new Set,s[t.id]=new Set;for(const c of t.codeAnalysis?.functions||[]){const f=c.replace(/\(\)$/,"");e[f]=t.id,e[f.toLowerCase()]=t.id}}for(const[t,p]of Object.entries(l))for(const c of p.calls){const f=c.replace(/\(\)$/,""),h=e[f]||e[f.toLowerCase()];h&&h!==t&&r[t]&&s[h]&&(r[t].add(h),s[h].add(t))}const u={},m={};for(const t of Object.keys(l))u[t]=[...r[t]],m[t]=[...s[t]];return{nodes:l,edges:u,reverse:m}}function Y(a){const{nodes:o,edges:l,reverse:r}=a,s=Object.keys(o).sort();console.log(),console.log(k(" Capability Dependency Graph")),console.log(g(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log();let e=!1;for(const m of s){const t=o[m],p=l[m]||[],c=r[m]||[],f=C[t.stability]||"\u{1F30A}",h=N[t.stability]||w;if(!(p.length===0&&c.length===0)){if(e=!0,console.log(` ${f} ${k(h(m))}`),p.length>0){console.log(g(" calls \u2192"));for(const x of p){const y=o[x],R=C[y?.stability]||"\u{1F30A}";console.log(g(` ${R} ${x}`))}}if(c.length>0){console.log(g(" called by \u2190"));for(const x of c){const y=C[o[x]?.stability]||"\u{1F30A}";console.log(g(` ${y} ${x}`))}}console.log()}}e||(console.log(g(" No inter-capability dependencies detected.")),console.log(g(" Run `infernoflow scan` first to populate call data.")),console.log());const u=Object.values(a.edges).reduce((m,t)=>m+t.length,0);console.log(g(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(g(` ${s.length} capabilities \xB7 ${u} dependency edge(s)`)),console.log()}function X(a,o){const{nodes:l,edges:r,reverse:s}=o,e=l[a];e||(console.error(j(`\u2717 Capability "${a}" not found in graph.`)),process.exit(1));const u=C[e.stability]||"\u{1F30A}",m=N[e.stability]||w;console.log(),console.log(k(` ${u} ${m(a)}`)+g(` (${e.stability})`)),e.services?.length&&console.log(g(" external: ")+G(e.services.join(", "))),console.log();const t=r[a]||[],p=s[a]||[];if(t.length>0){console.log(k(" Calls (downstream dependencies):"));for(const c of t){const f=l[c],h=N[f?.stability]||w,x=C[f?.stability]||"\u{1F30A}";console.log(` ${x} ${h(c)}`+g(f?.services?.length?` [${f.services.join(", ")}]`:""))}console.log()}else console.log(g(" No downstream dependencies.")),console.log();if(p.length>0){console.log(k(" Called by (upstream dependents):"));for(const c of p){const f=l[c],h=N[f?.stability]||w,x=C[f?.stability]||"\u{1F30A}";console.log(` ${x} ${h(c)}`)}console.log()}else console.log(g(" No capabilities call this one.")),console.log();if((e.stability==="frozen"||e.stability==="stable")&&p.length>0){const c=e.stability==="frozen"?j:M;console.log(c(` \u26A0 This capability is ${e.stability}. Changing it may break:`));for(const f of p)console.log(c(` \u2022 ${f}`));console.log()}}function Z(a,o){const l=[];if(!a||!o)return l;for(const[r,s]of Object.entries(o.nodes)){if(s.stability==="experimental")continue;const e=new Set(a.reverse?.[r]||[]),m=[...new Set(o.reverse[r]||[])].filter(t=>!e.has(t));if(m.length>0&&l.push({type:"new-dependents",capId:r,stability:s.stability,detail:`${m.join(", ")} now depend on this`}),s.stability==="frozen"){const t=new Set(a.edges?.[r]||[]),p=new Set(o.edges[r]||[]),c=[...p].filter(h=>!t.has(h)),f=[...t].filter(h=>!p.has(h));(c.length>0||f.length>0)&&l.push({type:"frozen-internals-changed",capId:r,stability:s.stability,detail:[c.length?`added calls: ${c.join(", ")}`:"",f.length?`removed calls: ${f.join(", ")}`:""].filter(Boolean).join("; ")})}}return l}async function te(a){const o=(a||[]).slice(1),l=o.includes("--json"),r=o.includes("--check"),s=o.includes("--mermaid"),e=o.includes("--html"),u=o.indexOf("--cap"),m=u!==-1?o[u+1]:null,t=process.cwd(),p=v.join(t,"inferno"),c=v.join(p,"scan.json"),f=v.join(p,"graph.json"),h=v.join(p,"capabilities.json"),x=300*1e3;let y=D(c);if(!y||!Array.isArray(y.capabilities)||y.capabilities.length===0||O.existsSync(c)&&Date.now()-O.statSync(c).mtimeMs>x){console.log(g(" \u27F3 Running infernoflow scan first (scan.json missing or stale)\u2026"));try{const{scanCommand:n}=await import("./scan.mjs");await n(["scan"]),y=D(c)}catch(n){console.error(j(`\u2717 Could not run scan automatically: ${n.message}`)),console.error(g(" Run `infernoflow scan` manually and try again.")),process.exit(1)}}(!y||!Array.isArray(y.capabilities)||y.capabilities.length===0)&&(console.error(j("\u2717 inferno/scan.json still empty after scan.")),console.error(g(" Make sure your contract has at least one capability and your code matches.")),process.exit(1));let H=[];const I=D(h);I&&(H=Array.isArray(I)?I:I.capabilities||[]);const F=y.capabilities||[],i=V(F,H),L=Array.isArray(y.components)?y.components:[],S={};for(const n of F){const d=(n.codeAnalysis?.files||[]).map($=>$.replace(/\\/g,"/"));for(const $ of d)S[$]||(S[$]=new Set),S[$].add(n.id)}let _=0;for(const n of L){const d=`comp:${n.name}`;i.nodes[d]={id:d,name:n.name,stability:"component",kind:"component",file:n.file,functions:[],calls:[]},i.edges[d]=i.edges[d]||new Set,i.reverse[d]=i.reverse[d]||new Set;const $=S[n.file]?[...S[n.file]]:[];for(const b of $)i.edges[d].add(b),i.reverse[b]||(i.reverse[b]=new Set),i.reverse[b].add(d),_++}const P=Array.isArray(y.uiElements)?y.uiElements:[],T={};for(const n of L)T[n.file]||(T[n.file]=n.name);const E={};for(const n of F){const d=n.codeAnalysis?.functions||[];for(const $ of d){const b=$.replace(/\(\)$/,"");E[b]=n.id,E[b.toLowerCase()]=n.id}}let A=0;for(const n of P){const d=`ui:${n.tag}:${n.handler}:${n.file.replace(/[^a-z0-9]/gi,"_")}`;i.nodes[d]={id:d,name:n.label||n.handler,stability:"ui",kind:"ui",tag:n.tag,handler:n.handler,file:n.file,functions:[],calls:[]},i.edges[d]=i.edges[d]||new Set,i.reverse[d]=i.reverse[d]||new Set;const $=T[n.file];if($){const z=`comp:${$}`;if(i.nodes[z]){i.edges[d].add(z),i.reverse[z]||(i.reverse[z]=new Set),i.reverse[z].add(d),A++;continue}}const b=E[n.handler]||E[n.handler?.toLowerCase()];b&&(i.edges[d].add(b),i.reverse[b]||(i.reverse[b]=new Set),i.reverse[b].add(d),A++)}!l&&!s&&!e&&(_>0&&console.log(g(` \u{1F9E9} Wired ${L.length} component${L.length===1?"":"s"} to capabilities.`)),A>0&&console.log(g(` \u26A1 Wired ${A} UI element${A===1?"":"s"}.`)));const U=D(f),B=Z(U,i),J={builtAt:new Date().toISOString(),capabilities:Object.keys(i.nodes).length,edges:Object.values(i.edges).reduce((n,d)=>n+d.length,0),nodes:i.nodes,deps:i.edges,dependents:i.reverse};if(l||O.writeFileSync(f,JSON.stringify(J,null,2)),l){console.log(JSON.stringify(J,null,2));return}if(s){console.log(q(i));return}if(e){const n=v.join(p,"graph.html");O.writeFileSync(n,Q(i)),console.log(w("\u2714 Interactive graph saved \u2192 inferno/graph.html")),console.log(g(` Open it: file://${n.replace(/\\/g,"/")}`));return}if(m?X(m,i):Y(i),B.length>0){console.log(M(" \u26A0 Dependency changes detected:"));for(const n of B){const d=n.stability==="frozen"?j("\u{1F9CA}"):M("\u3030\uFE0F ");console.log(` ${d} ${k(n.capId)} \u2014 ${n.detail}`)}console.log(),r&&process.exit(1)}l||console.log(g(" Graph saved \u2192 inferno/graph.json"))}function q(a){const o=[];o.push("```mermaid"),o.push("graph LR"),o.push(" classDef frozen fill:#fee,stroke:#c44,color:#900;"),o.push(" classDef stable fill:#fffbe6,stroke:#cc9,color:#840;"),o.push(" classDef experimental fill:#eef,stroke:#88c,color:#226;"),o.push(" classDef component fill:#fff3e0,stroke:#ff9800,color:#bf6d00;"),o.push(" classDef ui fill:#e8f5e9,stroke:#4caf50,color:#2e7d32,stroke-dasharray:4 2;");for(const l of Object.keys(a.nodes)){const r=W(l),s=a.nodes[l];if(s.kind==="ui"){const u=`${K(s.tag)} ${s.name||s.handler}<br/><small>&lt;${s.tag}&gt;</small>`;o.push(` ${r}(["${u}"]):::ui`)}else if(s.kind==="component")o.push(` ${r}{{"\u{1F9E9} ${s.name}"}}:::component`);else{const e=s.functions?.length||0,u=`${s.name||l}<br/><small>${e} fn${e===1?"":"s"}</small>`;o.push(` ${r}["${u}"]:::${s.stability||"experimental"}`)}}for(const[l,r]of Object.entries(a.edges)){const s=r instanceof Set?[...r]:Array.isArray(r)?r:[];for(const e of s)o.push(` ${W(l)} --> ${W(e)}`)}return o.push("```"),o.join(`
2
- `)}function W(a){return String(a).replace(/[^a-zA-Z0-9_]/g,"_")}function K(a){switch(a){case"button":return"\u{1F518}";case"input":return"\u2328\uFE0F ";case"form":return"\u{1F4DD}";case"link":return"\u{1F517}";case"select":return"\u25BE";default:return"\u{1F9E9}"}}function Q(a){const o=Object.keys(a.nodes).map(s=>{const e=a.nodes[s];return{id:s,name:e.name||s,stability:e.stability||"experimental",kind:e.kind||"capability",tag:e.tag||null,handler:e.handler||null,file:e.file||null,functions:e.functions?.length||0}}),l=[];for(const[s,e]of Object.entries(a.edges)){const u=e instanceof Set?[...e]:Array.isArray(e)?e:[];for(const m of u)l.push({source:s,target:m})}const r=JSON.stringify({nodes:o,links:l});return`<!DOCTYPE html>
1
+ import*as I from"node:fs";import*as v from"node:path";import{bold as w,cyan as X,gray as p,green as j,yellow as T,red as S}from"../ui/output.mjs";function D(i){try{return JSON.parse(I.readFileSync(i,"utf8"))}catch{return null}}function se(i){return i?.stability||"experimental"}const C={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},F={frozen:S,stable:T,experimental:j};function Z(i,o){const l={},c={},s={},n={};for(const t of i){const g=o.find(d=>d.id===t.id)||{};l[t.id]={id:t.id,name:t.name||g.name||g.title||t.id,stability:g.stability||"experimental",functions:t.codeAnalysis?.functions||[],calls:t.codeAnalysis?.calls||[],services:t.codeAnalysis?.services||[],dbCalls:t.codeAnalysis?.dbCalls||[],httpCalls:t.codeAnalysis?.httpCalls||[]},c[t.id]=new Set,s[t.id]=new Set;for(const d of t.codeAnalysis?.functions||[]){const f=d.replace(/\(\)$/,"");n[f]=t.id,n[f.toLowerCase()]=t.id}}for(const[t,g]of Object.entries(l))for(const d of g.calls){const f=d.replace(/\(\)$/,""),u=n[f]||n[f.toLowerCase()];u&&u!==t&&c[t]&&s[u]&&(c[t].add(u),s[u].add(t))}const y={},h={};for(const t of Object.keys(l))y[t]=[...c[t]],h[t]=[...s[t]];return{nodes:l,edges:y,reverse:h}}function q(i){const{nodes:o,edges:l,reverse:c}=i,s=Object.keys(o).sort();console.log(),console.log(w(" Capability Dependency Graph")),console.log(p(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log();let n=!1;for(const h of s){const t=o[h],g=l[h]||[],d=c[h]||[],f=C[t.stability]||"\u{1F30A}",u=F[t.stability]||j;if(!(g.length===0&&d.length===0)){if(n=!0,console.log(` ${f} ${w(u(h))}`),g.length>0){console.log(p(" calls \u2192"));for(const $ of g){const b=o[$],H=C[b?.stability]||"\u{1F30A}";console.log(p(` ${H} ${$}`))}}if(d.length>0){console.log(p(" called by \u2190"));for(const $ of d){const b=C[o[$]?.stability]||"\u{1F30A}";console.log(p(` ${b} ${$}`))}}console.log()}}n||(console.log(p(" No inter-capability dependencies detected.")),console.log(p(" Run `infernoflow scan` first to populate call data.")),console.log());const y=Object.values(i.edges).reduce((h,t)=>h+t.length,0);console.log(p(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(p(` ${s.length} capabilities \xB7 ${y} dependency edge(s)`)),console.log()}function K(i,o){const{nodes:l,edges:c,reverse:s}=o,n=l[i];n||(console.error(S(`\u2717 Capability "${i}" not found in graph.`)),process.exit(1));const y=C[n.stability]||"\u{1F30A}",h=F[n.stability]||j;console.log(),console.log(w(` ${y} ${h(i)}`)+p(` (${n.stability})`)),n.services?.length&&console.log(p(" external: ")+X(n.services.join(", "))),console.log();const t=c[i]||[],g=s[i]||[];if(t.length>0){console.log(w(" Calls (downstream dependencies):"));for(const d of t){const f=l[d],u=F[f?.stability]||j,$=C[f?.stability]||"\u{1F30A}";console.log(` ${$} ${u(d)}`+p(f?.services?.length?` [${f.services.join(", ")}]`:""))}console.log()}else console.log(p(" No downstream dependencies.")),console.log();if(g.length>0){console.log(w(" Called by (upstream dependents):"));for(const d of g){const f=l[d],u=F[f?.stability]||j,$=C[f?.stability]||"\u{1F30A}";console.log(` ${$} ${u(d)}`)}console.log()}else console.log(p(" No capabilities call this one.")),console.log();if((n.stability==="frozen"||n.stability==="stable")&&g.length>0){const d=n.stability==="frozen"?S:T;console.log(d(` \u26A0 This capability is ${n.stability}. Changing it may break:`));for(const f of g)console.log(d(` \u2022 ${f}`));console.log()}}function Q(i,o){const l=[];if(!i||!o)return l;for(const[c,s]of Object.entries(o.nodes)){if(s.stability==="experimental")continue;const n=new Set(i.reverse?.[c]||[]),h=[...new Set(o.reverse[c]||[])].filter(t=>!n.has(t));if(h.length>0&&l.push({type:"new-dependents",capId:c,stability:s.stability,detail:`${h.join(", ")} now depend on this`}),s.stability==="frozen"){const t=new Set(i.edges?.[c]||[]),g=new Set(o.edges[c]||[]),d=[...g].filter(u=>!t.has(u)),f=[...t].filter(u=>!g.has(u));(d.length>0||f.length>0)&&l.push({type:"frozen-internals-changed",capId:c,stability:s.stability,detail:[d.length?`added calls: ${d.join(", ")}`:"",f.length?`removed calls: ${f.join(", ")}`:""].filter(Boolean).join("; ")})}}return l}async function ae(i){const o=(i||[]).slice(1),l=o.includes("--json"),c=o.includes("--check"),s=o.includes("--mermaid"),n=o.includes("--html"),y=o.indexOf("--cap"),h=y!==-1?o[y+1]:null,t=process.cwd(),g=v.join(t,"inferno"),d=v.join(g,"scan.json"),f=v.join(g,"graph.json"),u=v.join(g,"capabilities.json"),$=300*1e3;let b=D(d);if(!b||!Array.isArray(b.capabilities)||b.capabilities.length===0||I.existsSync(d)&&Date.now()-I.statSync(d).mtimeMs>$){console.log(p(" \u27F3 Running infernoflow scan first (scan.json missing or stale)\u2026"));try{const{scanCommand:e}=await import("./scan.mjs");await e(["scan"]),b=D(d)}catch(e){console.error(S(`\u2717 Could not run scan automatically: ${e.message}`)),console.error(p(" Run `infernoflow scan` manually and try again.")),process.exit(1)}}(!b||!Array.isArray(b.capabilities)||b.capabilities.length===0)&&(console.error(S("\u2717 inferno/scan.json still empty after scan.")),console.error(p(" Make sure your contract has at least one capability and your code matches.")),process.exit(1));let P=[];const L=D(u);L&&(P=Array.isArray(L)?L:L.capabilities||[]);const W=b.capabilities||[],a=Z(W,P),k=Array.isArray(b.components)?b.components:[],A={};for(const e of W){const r=(e.codeAnalysis?.files||[]).map(x=>x.replace(/\\/g,"/"));for(const x of r)A[x]||(A[x]=new Set),A[x].add(e.id)}const Y=[/(?:^|\/)src\/App\.(jsx|tsx|js|ts|vue|svelte)$/i,/(?:^|\/)src\/main\.(jsx|tsx|js|ts)$/i,/(?:^|\/)src\/index\.(jsx|tsx|js|ts)$/i,/(?:^|\/)pages\/_app\.(jsx|tsx|js|ts)$/i,/(?:^|\/)app\/layout\.(jsx|tsx|js|ts)$/i,/(?:^|\/)src\/App\.(jsx|tsx)$/i],E=new Set;for(const e of k)Y.some(r=>r.test(e.file))&&E.add(e.name);let B=0;for(const e of k){const r=`comp:${e.name}`;a.nodes[r]={id:r,name:e.name,stability:E.has(e.name)?"entry":"component",kind:"component",isEntry:E.has(e.name),file:e.file,functions:[],calls:[]},a.edges[r]=a.edges[r]||new Set,a.reverse[r]=a.reverse[r]||new Set;const x=A[e.file]?[...A[e.file]]:[];for(const m of x)a.edges[r].add(m),a.reverse[m]||(a.reverse[m]=new Set),a.reverse[m].add(r),B++}let N=0;for(const e of k){const r=`comp:${e.name}`;for(const x of e.renders||[]){const m=`comp:${x}`;a.nodes[m]&&r!==m&&(a.edges[r].add(m),a.reverse[m]||(a.reverse[m]=new Set),a.reverse[m].add(r),N++)}}const G=Array.isArray(b.uiElements)?b.uiElements:[],_={};for(const e of k)_[e.file]||(_[e.file]=e.name);const M={};for(const e of W){const r=e.codeAnalysis?.functions||[];for(const x of r){const m=x.replace(/\(\)$/,"");M[m]=e.id,M[m.toLowerCase()]=e.id}}let z=0;for(const e of G){const r=`ui:${e.tag}:${e.handler}:${e.file.replace(/[^a-z0-9]/gi,"_")}`;a.nodes[r]={id:r,name:e.label||e.handler,stability:"ui",kind:"ui",tag:e.tag,handler:e.handler,file:e.file,functions:[],calls:[]},a.edges[r]=a.edges[r]||new Set,a.reverse[r]=a.reverse[r]||new Set;const x=_[e.file];if(x){const O=`comp:${x}`;if(a.nodes[O]){a.edges[r].add(O),a.reverse[O]||(a.reverse[O]=new Set),a.reverse[O].add(r),z++;continue}}const m=M[e.handler]||M[e.handler?.toLowerCase()];m&&(a.edges[r].add(m),a.reverse[m]||(a.reverse[m]=new Set),a.reverse[m].add(r),z++)}!l&&!s&&!n&&(B>0&&console.log(p(` \u{1F9E9} Wired ${k.length} component${k.length===1?"":"s"} to capabilities.`)),N>0&&console.log(p(` \u{1F333} Found ${N} component render relationship${N===1?"":"s"} (parent \u2192 child).`)),z>0&&console.log(p(` \u26A1 Wired ${z} UI element${z===1?"":"s"}.`)),E.size>0&&console.log(p(` \u{1F6AA} Entry: ${[...E].join(", ")}`)));const V=D(f),J=Q(V,a),U={builtAt:new Date().toISOString(),capabilities:Object.keys(a.nodes).length,edges:Object.values(a.edges).reduce((e,r)=>e+r.length,0),nodes:a.nodes,deps:a.edges,dependents:a.reverse};if(l||I.writeFileSync(f,JSON.stringify(U,null,2)),l){console.log(JSON.stringify(U,null,2));return}if(s){console.log(ee(a));return}if(n){const e=v.join(g,"graph.html");I.writeFileSync(e,te(a)),console.log(j("\u2714 Interactive graph saved \u2192 inferno/graph.html")),console.log(p(` Open it: file://${e.replace(/\\/g,"/")}`));return}if(h?K(h,a):q(a),J.length>0){console.log(T(" \u26A0 Dependency changes detected:"));for(const e of J){const r=e.stability==="frozen"?S("\u{1F9CA}"):T("\u3030\uFE0F ");console.log(` ${r} ${w(e.capId)} \u2014 ${e.detail}`)}console.log(),c&&process.exit(1)}l||console.log(p(" Graph saved \u2192 inferno/graph.json"))}function ee(i){const o=[];o.push("```mermaid"),o.push("graph LR"),o.push(" classDef frozen fill:#fee,stroke:#c44,color:#900;"),o.push(" classDef stable fill:#fffbe6,stroke:#cc9,color:#840;"),o.push(" classDef experimental fill:#eef,stroke:#88c,color:#226;"),o.push(" classDef component fill:#fff3e0,stroke:#ff9800,color:#bf6d00;"),o.push(" classDef entry fill:#fce4ec,stroke:#e91e63,color:#880e4f,stroke-width:3px;"),o.push(" classDef ui fill:#e8f5e9,stroke:#4caf50,color:#2e7d32,stroke-dasharray:4 2;");for(const l of Object.keys(i.nodes)){const c=R(l),s=i.nodes[l];if(s.kind==="ui"){const y=`${ne(s.tag)} ${s.name||s.handler}<br/><small>&lt;${s.tag}&gt;</small>`;o.push(` ${c}(["${y}"]):::ui`)}else if(s.kind==="component")s.isEntry?o.push(` ${c}{{"\u{1F6AA} ${s.name} (entry)"}}:::entry`):o.push(` ${c}{{"\u{1F9E9} ${s.name}"}}:::component`);else{const n=s.functions?.length||0,y=`${s.name||l}<br/><small>${n} fn${n===1?"":"s"}</small>`;o.push(` ${c}["${y}"]:::${s.stability||"experimental"}`)}}for(const[l,c]of Object.entries(i.edges)){const s=c instanceof Set?[...c]:Array.isArray(c)?c:[];for(const n of s)o.push(` ${R(l)} --> ${R(n)}`)}return o.push("```"),o.join(`
2
+ `)}function R(i){return String(i).replace(/[^a-zA-Z0-9_]/g,"_")}function ne(i){switch(i){case"button":return"\u{1F518}";case"input":return"\u2328\uFE0F ";case"form":return"\u{1F4DD}";case"link":return"\u{1F517}";case"select":return"\u25BE";default:return"\u{1F9E9}"}}function te(i){const o=Object.keys(i.nodes).map(s=>{const n=i.nodes[s];return{id:s,name:n.name||s,stability:n.stability||"experimental",kind:n.kind||"capability",isEntry:!!n.isEntry,tag:n.tag||null,handler:n.handler||null,file:n.file||null,functions:n.functions?.length||0}}),l=[];for(const[s,n]of Object.entries(i.edges)){const y=n instanceof Set?[...n]:Array.isArray(n)?n:[];for(const h of y)l.push({source:s,target:h})}const c=JSON.stringify({nodes:o,links:l});return`<!DOCTYPE html>
3
3
  <html lang="en">
4
4
  <head>
5
5
  <meta charset="UTF-8" />
@@ -22,6 +22,8 @@ import*as O from"node:fs";import*as v from"node:path";import{bold as k,cyan as G
22
22
  .node.experimental circle { fill: #5bc0de; }
23
23
  .node.component circle { fill: #ff9800; }
24
24
  .node.component text { fill: #ffd180; }
25
+ .node.entry circle { fill: #e91e63; stroke: #ff80ab; stroke-width: 4px; }
26
+ .node.entry text { fill: #ff80ab; font-weight: 700; }
25
27
  .node.ui circle { fill: #4caf50; stroke-dasharray: 3 2; }
26
28
  .node.ui text { fill: #aef; font-weight: 400; font-style: italic; }
27
29
  .node:hover circle { stroke: #fff; stroke-width: 3px; }
@@ -36,10 +38,11 @@ import*as O from"node:fs";import*as v from"node:path";import{bold as k,cyan as G
36
38
  <span>${o.length} capabilities \xB7 ${l.length} edges</span>
37
39
  </div>
38
40
  <div class="legend">
41
+ <span><span class="swatch" style="background:#e91e63"></span>entry (App.jsx / main / index)</span>
42
+ <span><span class="swatch" style="background:#ff9800"></span>component</span>
39
43
  <span><span class="swatch" style="background:#5bc0de"></span>capability</span>
40
- <span><span class="swatch" style="background:#ff9800"></span>component (React/Vue)</span>
41
- <span><span class="swatch" style="background:#4caf50"></span>UI element (button/input/form)</span>
42
- <span><span class="swatch" style="background:#d43f3a"></span>frozen (high-risk to change)</span>
44
+ <span><span class="swatch" style="background:#4caf50"></span>UI element</span>
45
+ <span><span class="swatch" style="background:#d43f3a"></span>frozen</span>
43
46
  <span style="color:#666; margin-left:16px;">drag \xB7 scroll to zoom \xB7 hover for details</span>
44
47
  </div>
45
48
  </header>
@@ -47,7 +50,7 @@ import*as O from"node:fs";import*as v from"node:path";import{bold as k,cyan as G
47
50
  <div class="tooltip" id="tt"></div>
48
51
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
49
52
  <script>
50
- const data = ${r};
53
+ const data = ${c};
51
54
  const svg = d3.select("svg");
52
55
  const W = window.innerWidth, H = window.innerHeight - 88;
53
56
  const g = svg.append("g");
@@ -69,13 +72,14 @@ svg.append("defs").append("marker").attr("id","arrow").attr("viewBox","0 -5 10 1
69
72
 
70
73
  const node = g.append("g").selectAll(".node")
71
74
  .data(data.nodes).enter().append("g")
72
- .attr("class", d => "node " + d.stability)
75
+ .attr("class", d => "node " + (d.isEntry ? "entry" : (d.kind === "ui" ? "ui" : (d.kind === "component" ? "component" : d.stability))))
73
76
  .call(d3.drag()
74
77
  .on("start", (e,d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx=d.x; d.fy=d.y; })
75
78
  .on("drag", (e,d) => { d.fx=e.x; d.fy=e.y; })
76
79
  .on("end", (e,d) => { if (!e.active) sim.alphaTarget(0); d.fx=null; d.fy=null; }));
77
80
 
78
81
  node.append("circle").attr("r", d => {
82
+ if (d.isEntry) return 18; // entry component is biggest \u2014 root of the tree
79
83
  if (d.kind === "ui") return 7;
80
84
  if (d.kind === "component") return 11;
81
85
  return 12 + Math.min(d.functions, 8);
@@ -108,4 +112,4 @@ sim.on("tick", () => {
108
112
  });
109
113
  </script>
110
114
  </body>
111
- </html>`}function oe(a){return D(v.join(a,"graph.json"))}export{te as graphCommand,oe as loadGraph};
115
+ </html>`}function ie(i){return D(v.join(i,"graph.json"))}export{ae as graphCommand,ie as loadGraph};
@@ -1,4 +1,4 @@
1
- import*as S from"node:fs";import*as f from"node:path";import{createRequire as V}from"node:module";import{execSync as W}from"node:child_process";import{bold as P,cyan as G,gray as u,green as k,yellow as A,red as T}from"../ui/output.mjs";const z=V(import.meta.url),K=["/usr/local/lib/node_modules_global/lib/node_modules/typescript","/usr/lib/node_modules/typescript",f.join(process.env.HOME||"",".npm-global/lib/node_modules/typescript")];function Q(){for(const e of K)try{return z(f.join(e,"lib/typescript.js"))}catch{}try{return z("typescript")}catch{}return null}const a=Q(),X=[{service:"stripe",patterns:["stripe","Stripe","createPaymentIntent","charges.create"]},{service:"sendgrid",patterns:["sendgrid","@sendgrid","sgMail","sendgrid.send"]},{service:"ses",patterns:["SES","ses.sendEmail","aws-sdk/ses","nodemailer"]},{service:"s3",patterns:["S3","s3.upload","s3.getObject","PutObjectCommand","@aws-sdk/s3"]},{service:"redis",patterns:["redis","Redis","ioredis","createClient"]},{service:"jwt",patterns:["jwt","jsonwebtoken","sign(","verify(","decode("]},{service:"bcrypt",patterns:["bcrypt","argon2","scrypt","hashSync","compare("]},{service:"prisma",patterns:["prisma.","PrismaClient","@prisma/client"]},{service:"mongoose",patterns:["mongoose",".save()",".findOne(",".aggregate("]},{service:"postgres",patterns:["pg","Pool(","Client(","query(","postgres("]},{service:"mysql",patterns:["mysql","mysql2","createConnection"]},{service:"graphql",patterns:["graphql","gql`","ApolloServer","GraphQLSchema"]},{service:"firebase",patterns:["firebase","firestore","initializeApp"]},{service:"twilio",patterns:["twilio","Twilio(","messages.create"]},{service:"openai",patterns:["openai","OpenAI(","createCompletion","chat.completions"]}];function _(e){const t=new Set;for(const{service:n,patterns:s}of X)s.some(o=>e.includes(o))&&t.add(n);return[...t]}const Y=[/\.(find|findOne|findMany|findById|findAll)\s*\(/g,/\.(create|insert|insertOne|insertMany|save)\s*\(/g,/\.(update|updateOne|updateMany|updateById|upsert)\s*\(/g,/\.(delete|deleteOne|deleteMany|remove|destroy)\s*\(/g,/\.(query|execute|raw)\s*\(/g,/\.(aggregate|groupBy|count|sum)\s*\(/g,/db\.\w+\s*\(/g,/prisma\.\w+\.\w+\s*\(/g];function F(e){const t=new Set;for(const n of Y){const s=new RegExp(n.source,"g");let o;for(;(o=s.exec(e))!==null;)t.add(o[0].replace(/\s*\($/,"()"))}return[...t].slice(0,10)}const ee=[/fetch\s*\(/g,/axios\.(get|post|put|patch|delete)\s*\(/g,/http\.(get|post|request)\s*\(/g,/got\.(get|post|put|delete)\s*\(/g,/request\.(get|post|put|delete)\s*\(/g,/\$http\.(get|post|put|delete)\s*\(/g];function E(e){const t=new Set;for(const n of ee){const s=new RegExp(n.source,"g");let o;for(;(o=s.exec(e))!==null;)t.add(o[0].replace(/\s*\($/,"()"))}return[...t].slice(0,8)}const se=[{tag:"button",re:/<button[\s\S]*?on(?:Click|Press|Submit)\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>([\s\S]*?)<\/button>/gi},{tag:"input",re:/<input[\s\S]*?on(?:Change|Input|Blur)\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?\/?>/gi},{tag:"form",re:/<form[\s\S]*?onSubmit\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>/gi},{tag:"link",re:/<a[\s\S]*?onClick\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>([\s\S]*?)<\/a>/gi},{tag:"select",re:/<select[\s\S]*?onChange\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>/gi}];function te(e,t){if(!/\.(jsx|tsx|vue|svelte)$/i.test(t))return[];const n=[];for(const{tag:s,re:o}of se){const i=new RegExp(o.source,o.flags);let l;for(;(l=i.exec(e))!==null;){const c=l[1],y=(l[2]||"").replace(/\{[^}]*\}/g,"").replace(/\s+/g," ").trim().slice(0,40);if(n.push({tag:s,handler:c,label:y||c,file:t}),n.length>=50)return n}}return n}const ne=[/export\s+default\s+function\s+([A-Z][\w$]*)/g,/export\s+function\s+([A-Z][\w$]*)/g,/^function\s+([A-Z][\w$]*)\s*\([\s\S]*?\)\s*\{[\s\S]*?return\s*\(/gm,/(?:export\s+)?const\s+([A-Z][\w$]*)\s*=\s*(?:\([\s\S]*?\)|[\w$]+)\s*=>\s*[({<]/g];function oe(e,t){if(!/\.(jsx|tsx|vue|svelte)$/i.test(t))return[];const n=new Set;for(const s of ne){const o=new RegExp(s.source,s.flags);let i;for(;(i=o.exec(e))!==null;)i[1]&&n.add(i[1])}return[...n].map(s=>({name:s,file:t}))}function O(e){return a&&e.name&&a.isIdentifier(e.name)?e.name.text:null}function ie(e){const t=[],n=[];function s(o){if(a.isCallExpression(o)){const i=o.expression;a.isIdentifier(i)?t.push({pos:o.pos,end:o.end,name:i.text+"()"}):a.isPropertyAccessExpression(i)&&t.push({pos:o.pos,end:o.end,name:i.name.text+"()"})}a.isThrowStatement(o)&&o.expression&&a.isNewExpression(o.expression)&&a.isIdentifier(o.expression.expression)&&n.push({pos:o.pos,end:o.end,name:o.expression.expression.text}),o.forEachChild?.(s)}return s(e),{calls:t,throws:n}}function re(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))].slice(0,20)}function ce(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))]}function le(e){return a?a.isFunctionDeclaration(e)||a.isFunctionExpression(e)||a.isArrowFunction(e)||a.isMethodDeclaration(e):!1}function ae(e){return a&&(e.parent&&a.isVariableDeclaration(e.parent)||e.parent&&a.isPropertyAssignment(e.parent))?O(e.parent):null}function pe(e,t){if(!a)return null;let n;try{n=a.createSourceFile(e,t,a.ScriptTarget.Latest,!0)}catch{return null}const{calls:s,throws:o}=ie(n),i=[];function l(c){if(le(c)){const y=O(c)||ae(c)||"<anonymous>",d=t.slice(c.pos,c.end),m=re(s,c.pos,c.end),x=ce(o,c.pos,c.end);i.push({name:y,calls:m,throws:x,services:_(d),dbCalls:F(d),httpCalls:E(d),loc:n.getLineAndCharacterOfPosition(c.pos).line+1})}c.forEachChild?.(l)}return l(n),i}const ue=`
1
+ import*as S from"node:fs";import*as d from"node:path";import{createRequire as V}from"node:module";import{execSync as W}from"node:child_process";import{bold as P,cyan as G,gray as u,green as k,yellow as A,red as T}from"../ui/output.mjs";const z=V(import.meta.url),K=["/usr/local/lib/node_modules_global/lib/node_modules/typescript","/usr/lib/node_modules/typescript",d.join(process.env.HOME||"",".npm-global/lib/node_modules/typescript")];function Q(){for(const e of K)try{return z(d.join(e,"lib/typescript.js"))}catch{}try{return z("typescript")}catch{}return null}const a=Q(),X=[{service:"stripe",patterns:["stripe","Stripe","createPaymentIntent","charges.create"]},{service:"sendgrid",patterns:["sendgrid","@sendgrid","sgMail","sendgrid.send"]},{service:"ses",patterns:["SES","ses.sendEmail","aws-sdk/ses","nodemailer"]},{service:"s3",patterns:["S3","s3.upload","s3.getObject","PutObjectCommand","@aws-sdk/s3"]},{service:"redis",patterns:["redis","Redis","ioredis","createClient"]},{service:"jwt",patterns:["jwt","jsonwebtoken","sign(","verify(","decode("]},{service:"bcrypt",patterns:["bcrypt","argon2","scrypt","hashSync","compare("]},{service:"prisma",patterns:["prisma.","PrismaClient","@prisma/client"]},{service:"mongoose",patterns:["mongoose",".save()",".findOne(",".aggregate("]},{service:"postgres",patterns:["pg","Pool(","Client(","query(","postgres("]},{service:"mysql",patterns:["mysql","mysql2","createConnection"]},{service:"graphql",patterns:["graphql","gql`","ApolloServer","GraphQLSchema"]},{service:"firebase",patterns:["firebase","firestore","initializeApp"]},{service:"twilio",patterns:["twilio","Twilio(","messages.create"]},{service:"openai",patterns:["openai","OpenAI(","createCompletion","chat.completions"]}];function _(e){const t=new Set;for(const{service:n,patterns:s}of X)s.some(o=>e.includes(o))&&t.add(n);return[...t]}const Y=[/\.(find|findOne|findMany|findById|findAll)\s*\(/g,/\.(create|insert|insertOne|insertMany|save)\s*\(/g,/\.(update|updateOne|updateMany|updateById|upsert)\s*\(/g,/\.(delete|deleteOne|deleteMany|remove|destroy)\s*\(/g,/\.(query|execute|raw)\s*\(/g,/\.(aggregate|groupBy|count|sum)\s*\(/g,/db\.\w+\s*\(/g,/prisma\.\w+\.\w+\s*\(/g];function F(e){const t=new Set;for(const n of Y){const s=new RegExp(n.source,"g");let o;for(;(o=s.exec(e))!==null;)t.add(o[0].replace(/\s*\($/,"()"))}return[...t].slice(0,10)}const ee=[/fetch\s*\(/g,/axios\.(get|post|put|patch|delete)\s*\(/g,/http\.(get|post|request)\s*\(/g,/got\.(get|post|put|delete)\s*\(/g,/request\.(get|post|put|delete)\s*\(/g,/\$http\.(get|post|put|delete)\s*\(/g];function E(e){const t=new Set;for(const n of ee){const s=new RegExp(n.source,"g");let o;for(;(o=s.exec(e))!==null;)t.add(o[0].replace(/\s*\($/,"()"))}return[...t].slice(0,8)}const se=[{tag:"button",re:/<button[\s\S]*?on(?:Click|Press|Submit)\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>([\s\S]*?)<\/button>/gi},{tag:"input",re:/<input[\s\S]*?on(?:Change|Input|Blur)\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?\/?>/gi},{tag:"form",re:/<form[\s\S]*?onSubmit\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>/gi},{tag:"link",re:/<a[\s\S]*?onClick\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>([\s\S]*?)<\/a>/gi},{tag:"select",re:/<select[\s\S]*?onChange\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>/gi}];function te(e,t){if(!/\.(jsx|tsx|vue|svelte)$/i.test(t))return[];const n=[];for(const{tag:s,re:o}of se){const i=new RegExp(o.source,o.flags);let r;for(;(r=i.exec(e))!==null;){const l=r[1],f=(r[2]||"").replace(/\{[^}]*\}/g,"").replace(/\s+/g," ").trim().slice(0,40);if(n.push({tag:s,handler:l,label:f||l,file:t}),n.length>=50)return n}}return n}const ne=[/export\s+default\s+function\s+([A-Z][\w$]*)/g,/export\s+function\s+([A-Z][\w$]*)/g,/^function\s+([A-Z][\w$]*)\s*\([\s\S]*?\)\s*\{[\s\S]*?return\s*\(/gm,/(?:export\s+)?const\s+([A-Z][\w$]*)\s*=\s*(?:\([\s\S]*?\)|[\w$]+)\s*=>\s*[({<]/g];function oe(e,t){if(!/\.(jsx|tsx|vue|svelte)$/i.test(t))return[];const n=new Set;for(const r of ne){const l=new RegExp(r.source,r.flags);let f;for(;(f=l.exec(e))!==null;)f[1]&&n.add(f[1])}const s=new Set,o=/<([A-Z][\w$]*)/g;let i;for(;(i=o.exec(e))!==null;){const r=i[1];r&&!n.has(r)&&s.add(r)}return[...n].map(r=>({name:r,file:t,renders:[...s]}))}function O(e){return a&&e.name&&a.isIdentifier(e.name)?e.name.text:null}function ie(e){const t=[],n=[];function s(o){if(a.isCallExpression(o)){const i=o.expression;a.isIdentifier(i)?t.push({pos:o.pos,end:o.end,name:i.text+"()"}):a.isPropertyAccessExpression(i)&&t.push({pos:o.pos,end:o.end,name:i.name.text+"()"})}a.isThrowStatement(o)&&o.expression&&a.isNewExpression(o.expression)&&a.isIdentifier(o.expression.expression)&&n.push({pos:o.pos,end:o.end,name:o.expression.expression.text}),o.forEachChild?.(s)}return s(e),{calls:t,throws:n}}function re(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))].slice(0,20)}function ce(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))]}function le(e){return a?a.isFunctionDeclaration(e)||a.isFunctionExpression(e)||a.isArrowFunction(e)||a.isMethodDeclaration(e):!1}function ae(e){return a&&(e.parent&&a.isVariableDeclaration(e.parent)||e.parent&&a.isPropertyAssignment(e.parent))?O(e.parent):null}function pe(e,t){if(!a)return null;let n;try{n=a.createSourceFile(e,t,a.ScriptTarget.Latest,!0)}catch{return null}const{calls:s,throws:o}=ie(n),i=[];function r(l){if(le(l)){const f=O(l)||ae(l)||"<anonymous>",g=t.slice(l.pos,l.end),h=re(s,l.pos,l.end),x=ce(o,l.pos,l.end);i.push({name:f,calls:h,throws:x,services:_(g),dbCalls:F(g),httpCalls:E(g),loc:n.getLineAndCharacterOfPosition(l.pos).line+1})}l.forEachChild?.(r)}return r(n),i}const ue=`
2
2
  import ast, json, sys
3
3
 
4
4
  def get_calls(node):
@@ -36,7 +36,7 @@ try:
36
36
  print(json.dumps(functions))
37
37
  except Exception as e:
38
38
  print(json.dumps([]))
39
- `;function fe(e){try{const t=W(`python3 -c ${JSON.stringify(ue)} ${JSON.stringify(e)}`,{timeout:8e3,encoding:"utf8",stdio:["pipe","pipe","pipe"]}),n=JSON.parse(t.trim()||"[]"),s=S.readFileSync(e,"utf8");return n.map(o=>({...o,services:_(s),dbCalls:F(s),httpCalls:E(s)}))}catch{return null}}const de=[{re:/^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/gm,lang:"go"},{re:/^\s*(?:def|async def)\s+(\w+)\s*\(/gm,lang:"py"},{re:/^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)?(\w+)\s*\(/gm,lang:"java"},{re:/^\s*def\s+(\w+)\s*[\(\|]/gm,lang:"rb"}];function N(e,t){const n=f.extname(e).slice(1),s=de.find(c=>c.lang===n);if(!s)return null;const o=[],i=new RegExp(s.re.source,"gm");let l;for(;(l=i.exec(t))!==null;){const c=l.index,y=Math.min(c+2e3,t.length),d=t.slice(c,y);o.push({name:l[1],calls:[],throws:[],services:_(d),dbCalls:F(d),httpCalls:E(d),loc:t.slice(0,c).split(`
40
- `).length})}return o.length>0?o:null}const ge=new Set(["node_modules",".git","dist","build","out",".next",".nuxt","coverage","__pycache__",".pytest_cache","vendor","tmp",".turbo","target",".gradle","public","static","assets"]),me=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".go",".rb",".java"]),he=/\.(test|spec)\.[jt]sx?$|_test\.(go|py|rb)|spec\.(rb|js|ts)$/;function*D(e){let t;try{t=S.readdirSync(e,{withFileTypes:!0})}catch{return}for(const n of t)if(n.isDirectory())ge.has(n.name)||(yield*D(f.join(e,n.name)));else if(n.isFile()){const s=f.extname(n.name);me.has(s)&&!he.test(n.name)&&(yield f.join(e,n.name))}}function ye(e){let t;try{t=S.readFileSync(e,"utf8")}catch{return[]}const n=f.extname(e);return[".ts",".tsx",".js",".jsx",".mjs",".cjs"].includes(n)?pe(e,t)||N(e,t)||[]:n===".py"?fe(e)||N(e,t)||[]:N(e,t)||[]}function R(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").toLowerCase().split(/[\s_\-/.]+/).filter(t=>t.length>1)}function q(e,t){const n=new Set(e),s=new Set(t);let o=0;for(const l of n)s.has(l)&&o++;const i=n.size+s.size-o;return i===0?0:o/i}function we(e,t){const n=R(e.name);let s=null,o=0;for(const i of t){const l=Math.max(q(n,R(i.id||"")),q(n,R(i.name||i.title||"")));l>o&&(o=l,s=i)}return o>=.2?{cap:s,score:o}:null}function Se(e={},t,n,s){const o=f.relative(s,n),i=(l=[],c=[])=>[...new Set([...l,...c])];return{functions:i(e.functions,[t.name]),sourceFiles:i(e.sourceFiles,[o]),calls:i(e.calls,t.calls),throws:i(e.throws,t.throws),services:i(e.services,t.services),dbCalls:i(e.dbCalls,t.dbCalls),httpCalls:i(e.httpCalls,t.httpCalls),scannedAt:new Date().toISOString()}}function xe(e){console.log(),console.log(P(" Scan Results")),console.log(u(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));for(const[t,n]of Object.entries(e)){const{codeAnalysis:s}=n;s&&(console.log(),console.log(` ${k("\u25CF")} ${P(t)}`),s.sourceFiles?.length&&console.log(u(" files: ")+s.sourceFiles.join(", ")),s.functions?.length&&console.log(u(" funcs: ")+s.functions.join(", ")),s.services?.length&&console.log(u(" services: ")+G(s.services.join(", "))),s.dbCalls?.length&&console.log(u(" db: ")+s.dbCalls.slice(0,4).join(", ")),s.httpCalls?.length&&console.log(u(" http: ")+s.httpCalls.slice(0,4).join(", ")),s.throws?.length&&console.log(u(" throws: ")+A(s.throws.join(", "))))}console.log(),console.log(u(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"))}async function Ae(e){const t=e||[],n=t.includes("--dry-run"),s=t.includes("--json"),o=t.indexOf("--dir"),i=o!==-1?[t[o+1]]:[],l=(()=>{const r=t.indexOf("--capability");return r!==-1?t[r+1]:null})(),c=process.cwd(),y=f.join(c,"inferno"),d=f.join(y,"capabilities.json");S.existsSync(d)||(console.error(T("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let m;try{m=JSON.parse(S.readFileSync(d,"utf8"))}catch(r){console.error(T("\u2717 Failed to parse capabilities.json: "+r.message)),process.exit(1)}Array.isArray(m)||(m.capabilities?m=m.capabilities:(console.error(T("\u2717 Unexpected capabilities.json format.")),process.exit(1)));const x=l?m.filter(r=>r.id===l||(r.name||"").toLowerCase()===l.toLowerCase()):m;x.length===0&&(console.log(A(l?`No capability matched: ${l}`:"No capabilities found.")),process.exit(0));const M=[c,...i];s||process.stdout.write(u(" Walking source files\u2026"));const w=[];for(const r of M)for(const p of D(r))w.push(p);s||process.stdout.write(`\r Found ${w.length} source files.
41
- `),s||process.stdout.write(u(" Analyzing\u2026"));const b=[];let C=0;for(const r of w){const p=ye(r);for(const g of p)b.push({fn:g,filePath:r});C++,!s&&C%20===0&&process.stdout.write(`\r Analyzed ${C}/${w.length} files\u2026`)}s||process.stdout.write(`\r Analyzed ${w.length} files, found ${b.length} functions.
42
- `);const j=[],v=[];for(const r of w)if(/\.(jsx|tsx|vue|svelte)$/i.test(r))try{const p=S.readFileSync(r,"utf8"),g=f.relative(c,r).replace(/\\/g,"/");j.push(...te(p,g)),v.push(...oe(p,g))}catch{}s||(v.length>0&&console.log(` Found ${v.length} components (React/Vue/Svelte).`),j.length>0&&console.log(` Found ${j.length} UI elements (buttons, inputs, forms, links).`));const h={};for(const r of x)h[r.id]={...r,codeAnalysis:null};for(const{fn:r,filePath:p}of b){const g=we(r,x);if(!g)continue;const{cap:I}=g,H=h[I.id]?.codeAnalysis||{};h[I.id].codeAnalysis=Se(H,r,p,c)}const Z=Object.keys(h).length,J=Object.values(h).filter(r=>r.codeAnalysis).length;if(s){const r={scannedAt:new Date().toISOString(),files:w.length,functions:b.length,capabilities:Object.entries(h).map(([p,g])=>({id:p,name:g.name||g.title,codeAnalysis:g.codeAnalysis}))};console.log(JSON.stringify(r,null,2));return}if(xe(h),console.log(` ${k("\u2714")} Matched ${J}/${Z} capabilities to source functions`),console.log(),n){console.log(A(" --dry-run: no files written."));return}const L={scannedAt:new Date().toISOString(),files:w.length,functions:b.length,capabilities:Object.entries(h).map(([r,p])=>({id:r,name:p.name||p.title,codeAnalysis:p.codeAnalysis})),uiElements:j,components:v},U=f.join(y,"scan.json");S.writeFileSync(U,JSON.stringify(L,null,2)),console.log(u(" Saved \u2192 inferno/scan.json"));let $=0;const B=m.map(r=>{const p=h[r.id]?.codeAnalysis;return p?($++,{...r,codeAnalysis:p}):r});$>0&&(S.writeFileSync(d,JSON.stringify(B,null,2)),console.log(u(` Updated ${$} capability entries in capabilities.json`))),console.log(),a||(console.log(A(" \u26A0 TypeScript compiler not found \u2014 JS/TS analyzed with regex fallback.")),console.log(u(" For deeper analysis: npm install -g typescript")),console.log())}export{Ae as scanCommand};
39
+ `;function fe(e){try{const t=W(`python3 -c ${JSON.stringify(ue)} ${JSON.stringify(e)}`,{timeout:8e3,encoding:"utf8",stdio:["pipe","pipe","pipe"]}),n=JSON.parse(t.trim()||"[]"),s=S.readFileSync(e,"utf8");return n.map(o=>({...o,services:_(s),dbCalls:F(s),httpCalls:E(s)}))}catch{return null}}const de=[{re:/^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/gm,lang:"go"},{re:/^\s*(?:def|async def)\s+(\w+)\s*\(/gm,lang:"py"},{re:/^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)?(\w+)\s*\(/gm,lang:"java"},{re:/^\s*def\s+(\w+)\s*[\(\|]/gm,lang:"rb"}];function N(e,t){const n=d.extname(e).slice(1),s=de.find(l=>l.lang===n);if(!s)return null;const o=[],i=new RegExp(s.re.source,"gm");let r;for(;(r=i.exec(t))!==null;){const l=r.index,f=Math.min(l+2e3,t.length),g=t.slice(l,f);o.push({name:r[1],calls:[],throws:[],services:_(g),dbCalls:F(g),httpCalls:E(g),loc:t.slice(0,l).split(`
40
+ `).length})}return o.length>0?o:null}const ge=new Set(["node_modules",".git","dist","build","out",".next",".nuxt","coverage","__pycache__",".pytest_cache","vendor","tmp",".turbo","target",".gradle","public","static","assets"]),me=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".go",".rb",".java"]),he=/\.(test|spec)\.[jt]sx?$|_test\.(go|py|rb)|spec\.(rb|js|ts)$/;function*D(e){let t;try{t=S.readdirSync(e,{withFileTypes:!0})}catch{return}for(const n of t)if(n.isDirectory())ge.has(n.name)||(yield*D(d.join(e,n.name)));else if(n.isFile()){const s=d.extname(n.name);me.has(s)&&!he.test(n.name)&&(yield d.join(e,n.name))}}function ye(e){let t;try{t=S.readFileSync(e,"utf8")}catch{return[]}const n=d.extname(e);return[".ts",".tsx",".js",".jsx",".mjs",".cjs"].includes(n)?pe(e,t)||N(e,t)||[]:n===".py"?fe(e)||N(e,t)||[]:N(e,t)||[]}function R(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").toLowerCase().split(/[\s_\-/.]+/).filter(t=>t.length>1)}function q(e,t){const n=new Set(e),s=new Set(t);let o=0;for(const r of n)s.has(r)&&o++;const i=n.size+s.size-o;return i===0?0:o/i}function we(e,t){const n=R(e.name);let s=null,o=0;for(const i of t){const r=Math.max(q(n,R(i.id||"")),q(n,R(i.name||i.title||"")));r>o&&(o=r,s=i)}return o>=.2?{cap:s,score:o}:null}function Se(e={},t,n,s){const o=d.relative(s,n),i=(r=[],l=[])=>[...new Set([...r,...l])];return{functions:i(e.functions,[t.name]),sourceFiles:i(e.sourceFiles,[o]),calls:i(e.calls,t.calls),throws:i(e.throws,t.throws),services:i(e.services,t.services),dbCalls:i(e.dbCalls,t.dbCalls),httpCalls:i(e.httpCalls,t.httpCalls),scannedAt:new Date().toISOString()}}function xe(e){console.log(),console.log(P(" Scan Results")),console.log(u(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));for(const[t,n]of Object.entries(e)){const{codeAnalysis:s}=n;s&&(console.log(),console.log(` ${k("\u25CF")} ${P(t)}`),s.sourceFiles?.length&&console.log(u(" files: ")+s.sourceFiles.join(", ")),s.functions?.length&&console.log(u(" funcs: ")+s.functions.join(", ")),s.services?.length&&console.log(u(" services: ")+G(s.services.join(", "))),s.dbCalls?.length&&console.log(u(" db: ")+s.dbCalls.slice(0,4).join(", ")),s.httpCalls?.length&&console.log(u(" http: ")+s.httpCalls.slice(0,4).join(", ")),s.throws?.length&&console.log(u(" throws: ")+A(s.throws.join(", "))))}console.log(),console.log(u(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"))}async function Ae(e){const t=e||[],n=t.includes("--dry-run"),s=t.includes("--json"),o=t.indexOf("--dir"),i=o!==-1?[t[o+1]]:[],r=(()=>{const c=t.indexOf("--capability");return c!==-1?t[c+1]:null})(),l=process.cwd(),f=d.join(l,"inferno"),g=d.join(f,"capabilities.json");S.existsSync(g)||(console.error(T("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let h;try{h=JSON.parse(S.readFileSync(g,"utf8"))}catch(c){console.error(T("\u2717 Failed to parse capabilities.json: "+c.message)),process.exit(1)}Array.isArray(h)||(h.capabilities?h=h.capabilities:(console.error(T("\u2717 Unexpected capabilities.json format.")),process.exit(1)));const x=r?h.filter(c=>c.id===r||(c.name||"").toLowerCase()===r.toLowerCase()):h;x.length===0&&(console.log(A(r?`No capability matched: ${r}`:"No capabilities found.")),process.exit(0));const M=[l,...i];s||process.stdout.write(u(" Walking source files\u2026"));const w=[];for(const c of M)for(const p of D(c))w.push(p);s||process.stdout.write(`\r Found ${w.length} source files.
41
+ `),s||process.stdout.write(u(" Analyzing\u2026"));const b=[];let C=0;for(const c of w){const p=ye(c);for(const m of p)b.push({fn:m,filePath:c});C++,!s&&C%20===0&&process.stdout.write(`\r Analyzed ${C}/${w.length} files\u2026`)}s||process.stdout.write(`\r Analyzed ${w.length} files, found ${b.length} functions.
42
+ `);const j=[],v=[];for(const c of w)if(/\.(jsx|tsx|vue|svelte)$/i.test(c))try{const p=S.readFileSync(c,"utf8"),m=d.relative(l,c).replace(/\\/g,"/");j.push(...te(p,m)),v.push(...oe(p,m))}catch{}s||(v.length>0&&console.log(` Found ${v.length} components (React/Vue/Svelte).`),j.length>0&&console.log(` Found ${j.length} UI elements (buttons, inputs, forms, links).`));const y={};for(const c of x)y[c.id]={...c,codeAnalysis:null};for(const{fn:c,filePath:p}of b){const m=we(c,x);if(!m)continue;const{cap:I}=m,H=y[I.id]?.codeAnalysis||{};y[I.id].codeAnalysis=Se(H,c,p,l)}const Z=Object.keys(y).length,J=Object.values(y).filter(c=>c.codeAnalysis).length;if(s){const c={scannedAt:new Date().toISOString(),files:w.length,functions:b.length,capabilities:Object.entries(y).map(([p,m])=>({id:p,name:m.name||m.title,codeAnalysis:m.codeAnalysis}))};console.log(JSON.stringify(c,null,2));return}if(xe(y),console.log(` ${k("\u2714")} Matched ${J}/${Z} capabilities to source functions`),console.log(),n){console.log(A(" --dry-run: no files written."));return}const L={scannedAt:new Date().toISOString(),files:w.length,functions:b.length,capabilities:Object.entries(y).map(([c,p])=>({id:c,name:p.name||p.title,codeAnalysis:p.codeAnalysis})),uiElements:j,components:v},U=d.join(f,"scan.json");S.writeFileSync(U,JSON.stringify(L,null,2)),console.log(u(" Saved \u2192 inferno/scan.json"));let $=0;const B=h.map(c=>{const p=y[c.id]?.codeAnalysis;return p?($++,{...c,codeAnalysis:p}):c});$>0&&(S.writeFileSync(g,JSON.stringify(B,null,2)),console.log(u(` Updated ${$} capability entries in capabilities.json`))),console.log(),a||(console.log(A(" \u26A0 TypeScript compiler not found \u2014 JS/TS analyzed with regex fallback.")),console.log(u(" For deeper analysis: npm install -g typescript")),console.log())}export{Ae as scanCommand};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.43.1",
3
+ "version": "0.43.2",
4
4
  "description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
5
5
  "type": "module",
6
6
  "bin": {