infernoflow 0.43.0 → 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,24 @@
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
+
12
+ ## 0.43.1 — 2026-05-06
13
+
14
+ ### Fixed
15
+ - **UI element regex broke on multi-line JSX attributes**. `<button\n onClick={handler}\n>` was being missed because `[^>]*` doesn't span newlines. Switched to `[\s\S]*?` so multi-line attributes work. UI elements should now actually appear in the graph.
16
+
17
+ ### Added
18
+ - **Component layer in scan + graph**. `infernoflow scan` now also detects React/Vue/Svelte function components by Capitalized-name pattern (`function ComponentName`, `export default function`, `const Component = (...) =>`, etc.). Each component becomes a hexagon-shaped node in Mermaid output and an orange circle in HTML output, sitting **between** UI elements and capabilities. Three-tier visual: UI → Component → Capability.
19
+ - **Component-aware UI wiring**. UI elements now prefer wiring through their containing component's hexagon (so you see "Add Task button → TaskComposer component → CreateTask capability") instead of jumping directly to capabilities.
20
+ - **Better legend** in HTML output covering all 4 node kinds (capability / component / UI / frozen).
21
+
3
22
  ## 0.43.0 — 2026-05-06
4
23
 
5
24
  ### Added
@@ -1,5 +1,5 @@
1
- import*as A from"node:fs";import*as k from"node:path";import{bold as v,cyan as B,gray as d,green as j,yellow as D,red as C}from"../ui/output.mjs";function O(o){try{return JSON.parse(A.readFileSync(o,"utf8"))}catch{return null}}function Z(o){return o?.stability||"experimental"}const S={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},L={frozen:C,stable:D,experimental:j};function J(o,n){const a={},l={},s={},e={};for(const t of o){const c=n.find(i=>i.id===t.id)||{};a[t.id]={id:t.id,name:t.name||c.name||c.title||t.id,stability:c.stability||"experimental",functions:t.codeAnalysis?.functions||[],calls:t.codeAnalysis?.calls||[],services:t.codeAnalysis?.services||[],dbCalls:t.codeAnalysis?.dbCalls||[],httpCalls:t.codeAnalysis?.httpCalls||[]},l[t.id]=new Set,s[t.id]=new Set;for(const i of t.codeAnalysis?.functions||[]){const r=i.replace(/\(\)$/,"");e[r]=t.id,e[r.toLowerCase()]=t.id}}for(const[t,c]of Object.entries(a))for(const i of c.calls){const r=i.replace(/\(\)$/,""),p=e[r]||e[r.toLowerCase()];p&&p!==t&&l[t]&&s[p]&&(l[t].add(p),s[p].add(t))}const h={},f={};for(const t of Object.keys(a))h[t]=[...l[t]],f[t]=[...s[t]];return{nodes:a,edges:h,reverse:f}}function P(o){const{nodes:n,edges:a,reverse:l}=o,s=Object.keys(n).sort();console.log(),console.log(v(" Capability Dependency Graph")),console.log(d(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 f of s){const t=n[f],c=a[f]||[],i=l[f]||[],r=S[t.stability]||"\u{1F30A}",p=L[t.stability]||j;if(!(c.length===0&&i.length===0)){if(e=!0,console.log(` ${r} ${v(p(f))}`),c.length>0){console.log(d(" calls \u2192"));for(const b of c){const y=n[b],E=S[y?.stability]||"\u{1F30A}";console.log(d(` ${E} ${b}`))}}if(i.length>0){console.log(d(" called by \u2190"));for(const b of i){const y=S[n[b]?.stability]||"\u{1F30A}";console.log(d(` ${y} ${b}`))}}console.log()}}e||(console.log(d(" No inter-capability dependencies detected.")),console.log(d(" Run `infernoflow scan` first to populate call data.")),console.log());const h=Object.values(o.edges).reduce((f,t)=>f+t.length,0);console.log(d(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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(d(` ${s.length} capabilities \xB7 ${h} dependency edge(s)`)),console.log()}function U(o,n){const{nodes:a,edges:l,reverse:s}=n,e=a[o];e||(console.error(C(`\u2717 Capability "${o}" not found in graph.`)),process.exit(1));const h=S[e.stability]||"\u{1F30A}",f=L[e.stability]||j;console.log(),console.log(v(` ${h} ${f(o)}`)+d(` (${e.stability})`)),e.services?.length&&console.log(d(" external: ")+B(e.services.join(", "))),console.log();const t=l[o]||[],c=s[o]||[];if(t.length>0){console.log(v(" Calls (downstream dependencies):"));for(const i of t){const r=a[i],p=L[r?.stability]||j,b=S[r?.stability]||"\u{1F30A}";console.log(` ${b} ${p(i)}`+d(r?.services?.length?` [${r.services.join(", ")}]`:""))}console.log()}else console.log(d(" No downstream dependencies.")),console.log();if(c.length>0){console.log(v(" Called by (upstream dependents):"));for(const i of c){const r=a[i],p=L[r?.stability]||j,b=S[r?.stability]||"\u{1F30A}";console.log(` ${b} ${p(i)}`)}console.log()}else console.log(d(" No capabilities call this one.")),console.log();if((e.stability==="frozen"||e.stability==="stable")&&c.length>0){const i=e.stability==="frozen"?C:D;console.log(i(` \u26A0 This capability is ${e.stability}. Changing it may break:`));for(const r of c)console.log(i(` \u2022 ${r}`));console.log()}}function _(o,n){const a=[];if(!o||!n)return a;for(const[l,s]of Object.entries(n.nodes)){if(s.stability==="experimental")continue;const e=new Set(o.reverse?.[l]||[]),f=[...new Set(n.reverse[l]||[])].filter(t=>!e.has(t));if(f.length>0&&a.push({type:"new-dependents",capId:l,stability:s.stability,detail:`${f.join(", ")} now depend on this`}),s.stability==="frozen"){const t=new Set(o.edges?.[l]||[]),c=new Set(n.edges[l]||[]),i=[...c].filter(p=>!t.has(p)),r=[...t].filter(p=>!c.has(p));(i.length>0||r.length>0)&&a.push({type:"frozen-internals-changed",capId:l,stability:s.stability,detail:[i.length?`added calls: ${i.join(", ")}`:"",r.length?`removed calls: ${r.join(", ")}`:""].filter(Boolean).join("; ")})}}return a}async function q(o){const n=(o||[]).slice(1),a=n.includes("--json"),l=n.includes("--check"),s=n.includes("--mermaid"),e=n.includes("--html"),h=n.indexOf("--cap"),f=h!==-1?n[h+1]:null,t=process.cwd(),c=k.join(t,"inferno"),i=k.join(c,"scan.json"),r=k.join(c,"graph.json"),p=k.join(c,"capabilities.json"),b=300*1e3;let y=O(i);if(!y||!Array.isArray(y.capabilities)||y.capabilities.length===0||A.existsSync(i)&&Date.now()-A.statSync(i).mtimeMs>b){console.log(d(" \u27F3 Running infernoflow scan first (scan.json missing or stale)\u2026"));try{const{scanCommand:g}=await import("./scan.mjs");await g(["scan"]),y=O(i)}catch(g){console.error(C(`\u2717 Could not run scan automatically: ${g.message}`)),console.error(d(" Run `infernoflow scan` manually and try again.")),process.exit(1)}}(!y||!Array.isArray(y.capabilities)||y.capabilities.length===0)&&(console.error(C("\u2717 inferno/scan.json still empty after scan.")),console.error(d(" Make sure your contract has at least one capability and your code matches.")),process.exit(1));let M=[];const z=O(p);z&&(M=Array.isArray(z)?z:z.capabilities||[]);const F=y.capabilities||[],u=J(F,M),N=Array.isArray(y.uiElements)?y.uiElements:[];if(N.length>0){const g={};for(const m of F){const w=m.codeAnalysis?.functions||[];for(const $ of w){const R=$.replace(/\(\)$/,"");g[R]=m.id,g[R.toLowerCase()]=m.id}}let x=0;for(const m of N){const w=g[m.handler]||g[m.handler?.toLowerCase()];if(!w)continue;const $=`ui:${m.tag}:${m.handler}`;u.nodes[$]={id:$,name:m.label||m.handler,stability:"ui",kind:"ui",tag:m.tag,handler:m.handler,file:m.file,functions:[],calls:[]},u.edges[$]=new Set([w]),u.reverse[$]=new Set,u.reverse[w]||(u.reverse[w]=new Set),u.reverse[w].add($),x++}x>0&&!a&&!s&&!e&&console.log(d(` \u26A1 Wired ${x} UI element${x===1?"":"s"} to capabilities.`))}const W=O(r),T=_(W,u),H={builtAt:new Date().toISOString(),capabilities:Object.keys(u.nodes).length,edges:Object.values(u.edges).reduce((g,x)=>g+x.length,0),nodes:u.nodes,deps:u.edges,dependents:u.reverse};if(a||A.writeFileSync(r,JSON.stringify(H,null,2)),a){console.log(JSON.stringify(H,null,2));return}if(s){console.log(G(u));return}if(e){const g=k.join(c,"graph.html");A.writeFileSync(g,V(u)),console.log(j("\u2714 Interactive graph saved \u2192 inferno/graph.html")),console.log(d(` Open it: file://${g.replace(/\\/g,"/")}`));return}if(f?U(f,u):P(u),T.length>0){console.log(D(" \u26A0 Dependency changes detected:"));for(const g of T){const x=g.stability==="frozen"?C("\u{1F9CA}"):D("\u3030\uFE0F ");console.log(` ${x} ${v(g.capId)} \u2014 ${g.detail}`)}console.log(),l&&process.exit(1)}a||console.log(d(" Graph saved \u2192 inferno/graph.json"))}function G(o){const n=[];n.push("```mermaid"),n.push("graph LR"),n.push(" classDef frozen fill:#fee,stroke:#c44,color:#900;"),n.push(" classDef stable fill:#fffbe6,stroke:#cc9,color:#840;"),n.push(" classDef experimental fill:#eef,stroke:#88c,color:#226;"),n.push(" classDef ui fill:#e8f5e9,stroke:#4caf50,color:#2e7d32,stroke-dasharray:4 2;");for(const a of Object.keys(o.nodes)){const l=I(a),s=o.nodes[a];if(s.kind==="ui"){const h=`${Y(s.tag)} ${s.name||s.handler}<br/><small>&lt;${s.tag}&gt;</small>`;n.push(` ${l}(["${h}"]):::ui`)}else{const e=s.functions?.length||0,h=`${s.name||a}<br/><small>${e} fn${e===1?"":"s"}</small>`;n.push(` ${l}["${h}"]:::${s.stability||"experimental"}`)}}for(const[a,l]of Object.entries(o.edges)){const s=l instanceof Set?[...l]:Array.isArray(l)?l:[];for(const e of s)n.push(` ${I(a)} --> ${I(e)}`)}return n.push("```"),n.join(`
2
- `)}function I(o){return String(o).replace(/[^a-zA-Z0-9_]/g,"_")}function Y(o){switch(o){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 V(o){const n=Object.keys(o.nodes).map(s=>{const e=o.nodes[s];return{id:s,name:e.name||s,stability:e.stability||"experimental",kind:e.kind||"capability",tag:e.tag||null,functions:e.functions?.length||0}}),a=[];for(const[s,e]of Object.entries(o.edges)){const h=e instanceof Set?[...e]:Array.isArray(e)?e:[];for(const f of h)a.push({source:s,target:f})}const l=JSON.stringify({nodes:n,links:a});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" />
@@ -20,6 +20,10 @@ import*as A from"node:fs";import*as k from"node:path";import{bold as v,cyan as B
20
20
  .node.frozen circle { fill: #d43f3a; }
21
21
  .node.stable circle { fill: #f0ad4e; }
22
22
  .node.experimental circle { fill: #5bc0de; }
23
+ .node.component circle { fill: #ff9800; }
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; }
23
27
  .node.ui circle { fill: #4caf50; stroke-dasharray: 3 2; }
24
28
  .node.ui text { fill: #aef; font-weight: 400; font-style: italic; }
25
29
  .node:hover circle { stroke: #fff; stroke-width: 3px; }
@@ -31,21 +35,22 @@ import*as A from"node:fs";import*as k from"node:path";import{bold as v,cyan as B
31
35
  <h1>\u{1F525} infernoflow \u2014 capability graph</h1>
32
36
  <div class="meta">
33
37
  <span>Generated: ${new Date().toLocaleString()}</span>
34
- <span>${n.length} capabilities \xB7 ${a.length} edges</span>
38
+ <span>${o.length} capabilities \xB7 ${l.length} edges</span>
35
39
  </div>
36
40
  <div class="legend">
37
- <span><span class="swatch" style="background:#d43f3a"></span>frozen (high-risk to change)</span>
38
- <span><span class="swatch" style="background:#f0ad4e"></span>stable</span>
39
- <span><span class="swatch" style="background:#5bc0de"></span>experimental</span>
40
- <span><span class="swatch" style="background:#4caf50"></span>UI element (button/input/form)</span>
41
- <span style="color:#666; margin-left:16px;">drag nodes \xB7 scroll to zoom \xB7 hover for details</span>
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>
43
+ <span><span class="swatch" style="background:#5bc0de"></span>capability</span>
44
+ <span><span class="swatch" style="background:#4caf50"></span>UI element</span>
45
+ <span><span class="swatch" style="background:#d43f3a"></span>frozen</span>
46
+ <span style="color:#666; margin-left:16px;">drag \xB7 scroll to zoom \xB7 hover for details</span>
42
47
  </div>
43
48
  </header>
44
49
  <svg></svg>
45
50
  <div class="tooltip" id="tt"></div>
46
51
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
47
52
  <script>
48
- const data = ${l};
53
+ const data = ${c};
49
54
  const svg = d3.select("svg");
50
55
  const W = window.innerWidth, H = window.innerHeight - 88;
51
56
  const g = svg.append("g");
@@ -67,26 +72,37 @@ svg.append("defs").append("marker").attr("id","arrow").attr("viewBox","0 -5 10 1
67
72
 
68
73
  const node = g.append("g").selectAll(".node")
69
74
  .data(data.nodes).enter().append("g")
70
- .attr("class", d => "node " + d.stability)
75
+ .attr("class", d => "node " + (d.isEntry ? "entry" : (d.kind === "ui" ? "ui" : (d.kind === "component" ? "component" : d.stability))))
71
76
  .call(d3.drag()
72
77
  .on("start", (e,d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx=d.x; d.fy=d.y; })
73
78
  .on("drag", (e,d) => { d.fx=e.x; d.fy=e.y; })
74
79
  .on("end", (e,d) => { if (!e.active) sim.alphaTarget(0); d.fx=null; d.fy=null; }));
75
80
 
76
- node.append("circle").attr("r", d => d.kind === "ui" ? 8 : 12 + Math.min(d.functions, 8));
81
+ node.append("circle").attr("r", d => {
82
+ if (d.isEntry) return 18; // entry component is biggest \u2014 root of the tree
83
+ if (d.kind === "ui") return 7;
84
+ if (d.kind === "component") return 11;
85
+ return 12 + Math.min(d.functions, 8);
86
+ });
77
87
  node.append("text").attr("dx", 18).attr("dy", 4).text(d => {
78
88
  if (d.kind === "ui") {
79
89
  const emoji = { button: "\u{1F518}", input: "\u2328\uFE0F", form: "\u{1F4DD}", link: "\u{1F517}", select: "\u25BE" }[d.tag] || "\u{1F9E9}";
80
90
  return emoji + " " + d.name;
81
91
  }
92
+ if (d.kind === "component") return "\u{1F9E9} " + d.name;
82
93
  return d.name;
83
94
  });
84
95
 
85
96
  const tt = d3.select("#tt");
86
97
  node.on("mouseover", (e,d) => {
87
- const html = d.kind === "ui"
88
- ? \`<strong>\${d.name}</strong><br/>UI element: &lt;\${d.tag}&gt;<br/>Handler wires to a capability\`
89
- : \`<strong>\${d.name}</strong><br/>Stability: \${d.stability}<br/>Functions: \${d.functions}\`;
98
+ let html;
99
+ if (d.kind === "ui") {
100
+ html = \`<strong>\${d.name}</strong><br/>UI element: &lt;\${d.tag}&gt;<br/>Handler: \${d.handler || "\u2014"}\`;
101
+ } else if (d.kind === "component") {
102
+ html = \`<strong>\u{1F9E9} \${d.name}</strong><br/>Component<br/>\${d.file || ""}\`;
103
+ } else {
104
+ html = \`<strong>\${d.name}</strong><br/>Capability \xB7 \${d.stability}<br/>Functions: \${d.functions}\`;
105
+ }
90
106
  tt.html(html).style("left", (e.pageX+12)+"px").style("top", (e.pageY+12)+"px").style("opacity", 1);
91
107
  }).on("mouseout", () => tt.style("opacity", 0));
92
108
 
@@ -96,4 +112,4 @@ sim.on("tick", () => {
96
112
  });
97
113
  </script>
98
114
  </body>
99
- </html>`}function K(o){return O(k.join(o,"graph.json"))}export{q as graphCommand,K 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 b from"node:fs";import*as f from"node:path";import{createRequire as H}from"node:module";import{execSync as V}from"node:child_process";import{bold as R,cyan as W,gray as u,green as P,yellow as v,red as $}from"../ui/output.mjs";const k=H(import.meta.url),G=["/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 K(){for(const e of G)try{return k(f.join(e,"lib/typescript.js"))}catch{}try{return k("typescript")}catch{}return null}const a=K(),Q=[{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 Q)s.some(i=>e.includes(i))&&t.add(n);return[...t]}const X=[/\.(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 X){const s=new RegExp(n.source,"g");let i;for(;(i=s.exec(e))!==null;)t.add(i[0].replace(/\s*\($/,"()"))}return[...t].slice(0,10)}const Y=[/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 T(e){const t=new Set;for(const n of Y){const s=new RegExp(n.source,"g");let i;for(;(i=s.exec(e))!==null;)t.add(i[0].replace(/\s*\($/,"()"))}return[...t].slice(0,8)}const ee=[{tag:"button",re:/<button\s+[^>]*on(?:Click|Press|Submit)\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[^>]*>\s*([^<{]*)/gi},{tag:"input",re:/<input\s+[^>]*on(?:Change|Input|Blur)\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[^>]*(?:placeholder=["']([^"']*)["'])?/gi},{tag:"form",re:/<form\s+[^>]*onSubmit\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[^>]*>/gi},{tag:"link",re:/<a\s+[^>]*onClick\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[^>]*>\s*([^<{]*)/gi},{tag:"select",re:/<select\s+[^>]*onChange\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[^>]*>/gi}];function se(e,t){if(!/\.(jsx|tsx|vue|svelte)$/i.test(t))return[];const n=[];for(const{tag:s,re:i}of ee){const r=new RegExp(i.source,i.flags);let l;for(;(l=r.exec(e))!==null;){const c=l[1],y=(l[2]||"").trim().slice(0,40);if(n.push({tag:s,handler:c,label:y||c,file:t}),n.length>=50)return n}}return n}function O(e){return a&&e.name&&a.isIdentifier(e.name)?e.name.text:null}function te(e){const t=[],n=[];function s(i){if(a.isCallExpression(i)){const r=i.expression;a.isIdentifier(r)?t.push({pos:i.pos,end:i.end,name:r.text+"()"}):a.isPropertyAccessExpression(r)&&t.push({pos:i.pos,end:i.end,name:r.name.text+"()"})}a.isThrowStatement(i)&&i.expression&&a.isNewExpression(i.expression)&&a.isIdentifier(i.expression.expression)&&n.push({pos:i.pos,end:i.end,name:i.expression.expression.text}),i.forEachChild?.(s)}return s(e),{calls:t,throws:n}}function ne(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))].slice(0,20)}function ie(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))]}function oe(e){return a?a.isFunctionDeclaration(e)||a.isFunctionExpression(e)||a.isArrowFunction(e)||a.isMethodDeclaration(e):!1}function re(e){return a&&(e.parent&&a.isVariableDeclaration(e.parent)||e.parent&&a.isPropertyAssignment(e.parent))?O(e.parent):null}function ce(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:i}=te(n),r=[];function l(c){if(oe(c)){const y=O(c)||re(c)||"<anonymous>",d=t.slice(c.pos,c.end),g=ne(s,c.pos,c.end),S=ie(i,c.pos,c.end);r.push({name:y,calls:g,throws:S,services:_(d),dbCalls:F(d),httpCalls:T(d),loc:n.getLineAndCharacterOfPosition(c.pos).line+1})}c.forEachChild?.(l)}return l(n),r}const le=`
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 ae(e){try{const t=V(`python3 -c ${JSON.stringify(le)} ${JSON.stringify(e)}`,{timeout:8e3,encoding:"utf8",stdio:["pipe","pipe","pipe"]}),n=JSON.parse(t.trim()||"[]"),s=b.readFileSync(e,"utf8");return n.map(i=>({...i,services:_(s),dbCalls:F(s),httpCalls:T(s)}))}catch{return null}}const pe=[{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 E(e,t){const n=f.extname(e).slice(1),s=pe.find(c=>c.lang===n);if(!s)return null;const i=[],r=new RegExp(s.re.source,"gm");let l;for(;(l=r.exec(t))!==null;){const c=l.index,y=Math.min(c+2e3,t.length),d=t.slice(c,y);i.push({name:l[1],calls:[],throws:[],services:_(d),dbCalls:F(d),httpCalls:T(d),loc:t.slice(0,c).split(`
40
- `).length})}return i.length>0?i:null}const ue=new Set(["node_modules",".git","dist","build","out",".next",".nuxt","coverage","__pycache__",".pytest_cache","vendor","tmp",".turbo","target",".gradle","public","static","assets"]),fe=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".go",".rb",".java"]),de=/\.(test|spec)\.[jt]sx?$|_test\.(go|py|rb)|spec\.(rb|js|ts)$/;function*z(e){let t;try{t=b.readdirSync(e,{withFileTypes:!0})}catch{return}for(const n of t)if(n.isDirectory())ue.has(n.name)||(yield*z(f.join(e,n.name)));else if(n.isFile()){const s=f.extname(n.name);fe.has(s)&&!de.test(n.name)&&(yield f.join(e,n.name))}}function ge(e){let t;try{t=b.readFileSync(e,"utf8")}catch{return[]}const n=f.extname(e);return[".ts",".tsx",".js",".jsx",".mjs",".cjs"].includes(n)?ce(e,t)||E(e,t)||[]:n===".py"?ae(e)||E(e,t)||[]:E(e,t)||[]}function N(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").toLowerCase().split(/[\s_\-/.]+/).filter(t=>t.length>1)}function D(e,t){const n=new Set(e),s=new Set(t);let i=0;for(const l of n)s.has(l)&&i++;const r=n.size+s.size-i;return r===0?0:i/r}function me(e,t){const n=N(e.name);let s=null,i=0;for(const r of t){const l=Math.max(D(n,N(r.id||"")),D(n,N(r.name||r.title||"")));l>i&&(i=l,s=r)}return i>=.2?{cap:s,score:i}:null}function he(e={},t,n,s){const i=f.relative(s,n),r=(l=[],c=[])=>[...new Set([...l,...c])];return{functions:r(e.functions,[t.name]),sourceFiles:r(e.sourceFiles,[i]),calls:r(e.calls,t.calls),throws:r(e.throws,t.throws),services:r(e.services,t.services),dbCalls:r(e.dbCalls,t.dbCalls),httpCalls:r(e.httpCalls,t.httpCalls),scannedAt:new Date().toISOString()}}function ye(e){console.log(),console.log(R(" 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(` ${P("\u25CF")} ${R(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: ")+W(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: ")+v(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 xe(e){const t=e||[],n=t.includes("--dry-run"),s=t.includes("--json"),i=t.indexOf("--dir"),r=i!==-1?[t[i+1]]:[],l=(()=>{const o=t.indexOf("--capability");return o!==-1?t[o+1]:null})(),c=process.cwd(),y=f.join(c,"inferno"),d=f.join(y,"capabilities.json");b.existsSync(d)||(console.error($("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let g;try{g=JSON.parse(b.readFileSync(d,"utf8"))}catch(o){console.error($("\u2717 Failed to parse capabilities.json: "+o.message)),process.exit(1)}Array.isArray(g)||(g.capabilities?g=g.capabilities:(console.error($("\u2717 Unexpected capabilities.json format.")),process.exit(1)));const S=l?g.filter(o=>o.id===l||(o.name||"").toLowerCase()===l.toLowerCase()):g;S.length===0&&(console.log(v(l?`No capability matched: ${l}`:"No capabilities found.")),process.exit(0));const q=[c,...r];s||process.stdout.write(u(" Walking source files\u2026"));const w=[];for(const o of q)for(const p of z(o))w.push(p);s||process.stdout.write(`\r Found ${w.length} source files.
41
- `),s||process.stdout.write(u(" Analyzing\u2026"));const x=[];let C=0;for(const o of w){const p=ge(o);for(const m of p)x.push({fn:m,filePath:o});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 ${x.length} functions.
42
- `);const j=[];for(const o of w)if(/\.(jsx|tsx|vue|svelte)$/i.test(o))try{const p=b.readFileSync(o,"utf8"),m=se(p,f.relative(c,o).replace(/\\/g,"/"));j.push(...m)}catch{}!s&&j.length>0&&console.log(` Found ${j.length} UI elements (buttons, inputs, forms, links).`);const h={};for(const o of S)h[o.id]={...o,codeAnalysis:null};for(const{fn:o,filePath:p}of x){const m=me(o,S);if(!m)continue;const{cap:I}=m,B=h[I.id]?.codeAnalysis||{};h[I.id].codeAnalysis=he(B,o,p,c)}const M=Object.keys(h).length,J=Object.values(h).filter(o=>o.codeAnalysis).length;if(s){const o={scannedAt:new Date().toISOString(),files:w.length,functions:x.length,capabilities:Object.entries(h).map(([p,m])=>({id:p,name:m.name||m.title,codeAnalysis:m.codeAnalysis}))};console.log(JSON.stringify(o,null,2));return}if(ye(h),console.log(` ${P("\u2714")} Matched ${J}/${M} capabilities to source functions`),console.log(),n){console.log(v(" --dry-run: no files written."));return}const L={scannedAt:new Date().toISOString(),files:w.length,functions:x.length,capabilities:Object.entries(h).map(([o,p])=>({id:o,name:p.name||p.title,codeAnalysis:p.codeAnalysis})),uiElements:j},U=f.join(y,"scan.json");b.writeFileSync(U,JSON.stringify(L,null,2)),console.log(u(" Saved \u2192 inferno/scan.json"));let A=0;const Z=g.map(o=>{const p=h[o.id]?.codeAnalysis;return p?(A++,{...o,codeAnalysis:p}):o});A>0&&(b.writeFileSync(d,JSON.stringify(Z,null,2)),console.log(u(` Updated ${A} capability entries in capabilities.json`))),console.log(),a||(console.log(v(" \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{xe 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.0",
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": {