infernoflow 0.43.0 → 0.43.1
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 +10 -0
- package/dist/lib/commands/graph.mjs +25 -13
- package/dist/lib/commands/scan.mjs +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog — infernoflow
|
|
2
2
|
|
|
3
|
+
## 0.43.1 — 2026-05-06
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **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.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **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.
|
|
10
|
+
- **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.
|
|
11
|
+
- **Better legend** in HTML output covering all 4 node kinds (capability / component / UI / frozen).
|
|
12
|
+
|
|
3
13
|
## 0.43.0 — 2026-05-06
|
|
4
14
|
|
|
5
15
|
### Added
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`)}function
|
|
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><${s.tag}></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>
|
|
3
3
|
<html lang="en">
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8" />
|
|
@@ -20,6 +20,8 @@ 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; }
|
|
23
25
|
.node.ui circle { fill: #4caf50; stroke-dasharray: 3 2; }
|
|
24
26
|
.node.ui text { fill: #aef; font-weight: 400; font-style: italic; }
|
|
25
27
|
.node:hover circle { stroke: #fff; stroke-width: 3px; }
|
|
@@ -31,21 +33,21 @@ import*as A from"node:fs";import*as k from"node:path";import{bold as v,cyan as B
|
|
|
31
33
|
<h1>\u{1F525} infernoflow \u2014 capability graph</h1>
|
|
32
34
|
<div class="meta">
|
|
33
35
|
<span>Generated: ${new Date().toLocaleString()}</span>
|
|
34
|
-
<span>${
|
|
36
|
+
<span>${o.length} capabilities \xB7 ${l.length} edges</span>
|
|
35
37
|
</div>
|
|
36
38
|
<div class="legend">
|
|
37
|
-
<span><span class="swatch" style="background:#
|
|
38
|
-
<span><span class="swatch" style="background:#
|
|
39
|
-
<span><span class="swatch" style="background:#5bc0de"></span>experimental</span>
|
|
39
|
+
<span><span class="swatch" style="background:#5bc0de"></span>capability</span>
|
|
40
|
+
<span><span class="swatch" style="background:#ff9800"></span>component (React/Vue)</span>
|
|
40
41
|
<span><span class="swatch" style="background:#4caf50"></span>UI element (button/input/form)</span>
|
|
41
|
-
<span style="
|
|
42
|
+
<span><span class="swatch" style="background:#d43f3a"></span>frozen (high-risk to change)</span>
|
|
43
|
+
<span style="color:#666; margin-left:16px;">drag \xB7 scroll to zoom \xB7 hover for details</span>
|
|
42
44
|
</div>
|
|
43
45
|
</header>
|
|
44
46
|
<svg></svg>
|
|
45
47
|
<div class="tooltip" id="tt"></div>
|
|
46
48
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
|
|
47
49
|
<script>
|
|
48
|
-
const data = ${
|
|
50
|
+
const data = ${r};
|
|
49
51
|
const svg = d3.select("svg");
|
|
50
52
|
const W = window.innerWidth, H = window.innerHeight - 88;
|
|
51
53
|
const g = svg.append("g");
|
|
@@ -73,20 +75,30 @@ const node = g.append("g").selectAll(".node")
|
|
|
73
75
|
.on("drag", (e,d) => { d.fx=e.x; d.fy=e.y; })
|
|
74
76
|
.on("end", (e,d) => { if (!e.active) sim.alphaTarget(0); d.fx=null; d.fy=null; }));
|
|
75
77
|
|
|
76
|
-
node.append("circle").attr("r", d =>
|
|
78
|
+
node.append("circle").attr("r", d => {
|
|
79
|
+
if (d.kind === "ui") return 7;
|
|
80
|
+
if (d.kind === "component") return 11;
|
|
81
|
+
return 12 + Math.min(d.functions, 8);
|
|
82
|
+
});
|
|
77
83
|
node.append("text").attr("dx", 18).attr("dy", 4).text(d => {
|
|
78
84
|
if (d.kind === "ui") {
|
|
79
85
|
const emoji = { button: "\u{1F518}", input: "\u2328\uFE0F", form: "\u{1F4DD}", link: "\u{1F517}", select: "\u25BE" }[d.tag] || "\u{1F9E9}";
|
|
80
86
|
return emoji + " " + d.name;
|
|
81
87
|
}
|
|
88
|
+
if (d.kind === "component") return "\u{1F9E9} " + d.name;
|
|
82
89
|
return d.name;
|
|
83
90
|
});
|
|
84
91
|
|
|
85
92
|
const tt = d3.select("#tt");
|
|
86
93
|
node.on("mouseover", (e,d) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
let html;
|
|
95
|
+
if (d.kind === "ui") {
|
|
96
|
+
html = \`<strong>\${d.name}</strong><br/>UI element: <\${d.tag}><br/>Handler: \${d.handler || "\u2014"}\`;
|
|
97
|
+
} else if (d.kind === "component") {
|
|
98
|
+
html = \`<strong>\u{1F9E9} \${d.name}</strong><br/>Component<br/>\${d.file || ""}\`;
|
|
99
|
+
} else {
|
|
100
|
+
html = \`<strong>\${d.name}</strong><br/>Capability \xB7 \${d.stability}<br/>Functions: \${d.functions}\`;
|
|
101
|
+
}
|
|
90
102
|
tt.html(html).style("left", (e.pageX+12)+"px").style("top", (e.pageY+12)+"px").style("opacity", 1);
|
|
91
103
|
}).on("mouseout", () => tt.style("opacity", 0));
|
|
92
104
|
|
|
@@ -96,4 +108,4 @@ sim.on("tick", () => {
|
|
|
96
108
|
});
|
|
97
109
|
</script>
|
|
98
110
|
</body>
|
|
99
|
-
</html>`}function
|
|
111
|
+
</html>`}function oe(a){return D(v.join(a,"graph.json"))}export{te as graphCommand,oe as loadGraph};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as
|
|
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=`
|
|
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
|
|
40
|
-
`).length})}return
|
|
41
|
-
`),s||process.stdout.write(u(" Analyzing\u2026"));const
|
|
42
|
-
`);const j=[];for(const
|
|
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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infernoflow",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.1",
|
|
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": {
|