infernoflow 0.32.8 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/infernoflow.mjs +84 -255
- package/dist/lib/adopters/angular.mjs +1 -128
- package/dist/lib/adopters/css.mjs +1 -111
- package/dist/lib/adopters/react.mjs +1 -104
- package/dist/lib/ai/ideDetection.mjs +1 -31
- package/dist/lib/ai/localProvider.mjs +1 -88
- package/dist/lib/ai/providerRouter.mjs +2 -295
- package/dist/lib/commands/adopt.mjs +20 -869
- package/dist/lib/commands/adoptWizard.mjs +9 -320
- package/dist/lib/commands/agent.mjs +5 -191
- package/dist/lib/commands/ai.mjs +2 -407
- package/dist/lib/commands/audit.mjs +13 -300
- package/dist/lib/commands/changelog.mjs +26 -594
- package/dist/lib/commands/check.mjs +3 -184
- package/dist/lib/commands/ci.mjs +3 -208
- package/dist/lib/commands/claudeMd.mjs +25 -130
- package/dist/lib/commands/cloud.mjs +5 -521
- package/dist/lib/commands/context.mjs +34 -287
- package/dist/lib/commands/coverage.mjs +2 -282
- package/dist/lib/commands/dashboard.mjs +123 -635
- package/dist/lib/commands/demo.mjs +8 -465
- package/dist/lib/commands/diff.mjs +5 -274
- package/dist/lib/commands/docGate.mjs +2 -81
- package/dist/lib/commands/doctor.mjs +3 -321
- package/dist/lib/commands/explain.mjs +8 -438
- package/dist/lib/commands/export.mjs +10 -239
- package/dist/lib/commands/generateSkills.mjs +38 -163
- package/dist/lib/commands/graph.mjs +203 -321
- package/dist/lib/commands/health.mjs +2 -309
- package/dist/lib/commands/impact.mjs +2 -325
- package/dist/lib/commands/implement.mjs +7 -103
- package/dist/lib/commands/init.mjs +23 -475
- package/dist/lib/commands/installCursorHooks.mjs +1 -36
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
- package/dist/lib/commands/link.mjs +2 -342
- package/dist/lib/commands/log.mjs +16 -0
- package/dist/lib/commands/monorepo.mjs +4 -428
- package/dist/lib/commands/notify.mjs +4 -258
- package/dist/lib/commands/onboard.mjs +4 -296
- package/dist/lib/commands/prComment.mjs +2 -361
- package/dist/lib/commands/prImpact.mjs +2 -157
- package/dist/lib/commands/publish.mjs +15 -316
- package/dist/lib/commands/report.mjs +28 -272
- package/dist/lib/commands/review.mjs +9 -223
- package/dist/lib/commands/run.mjs +8 -336
- package/dist/lib/commands/scaffold.mjs +54 -419
- package/dist/lib/commands/scan.mjs +5 -558
- package/dist/lib/commands/scout.mjs +2 -291
- package/dist/lib/commands/setup.mjs +5 -310
- package/dist/lib/commands/share.mjs +13 -196
- package/dist/lib/commands/snapshot.mjs +3 -383
- package/dist/lib/commands/stability.mjs +2 -293
- package/dist/lib/commands/status.mjs +4 -172
- package/dist/lib/commands/suggest.mjs +21 -563
- package/dist/lib/commands/syncAuto.mjs +1 -96
- package/dist/lib/commands/synthesize.mjs +10 -228
- package/dist/lib/commands/teamSync.mjs +2 -388
- package/dist/lib/commands/test.mjs +6 -363
- package/dist/lib/commands/theme.mjs +18 -0
- package/dist/lib/commands/version.mjs +2 -282
- package/dist/lib/commands/vibe.mjs +7 -357
- package/dist/lib/commands/watch.mjs +4 -203
- package/dist/lib/commands/why.mjs +4 -358
- package/dist/lib/cursorHooksInstall.mjs +1 -60
- package/dist/lib/draftToolingInstall.mjs +7 -68
- package/dist/lib/git/detect-drift.mjs +4 -208
- package/dist/lib/learning/adapt.mjs +6 -101
- package/dist/lib/learning/observe.mjs +1 -119
- package/dist/lib/learning/patternDetector.mjs +1 -298
- package/dist/lib/learning/profile.mjs +2 -279
- package/dist/lib/learning/skillSynthesizer.mjs +24 -145
- package/dist/lib/templates/index.mjs +1 -131
- package/dist/lib/theme/scanner.mjs +4 -0
- package/dist/lib/ui/errors.mjs +1 -142
- package/dist/lib/ui/output.mjs +6 -72
- package/dist/lib/ui/prompts.mjs +6 -147
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
- package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
- package/dist/templates/github-app/GITHUB_APP.md +67 -0
- package/dist/templates/github-app/app-manifest.json +20 -0
- package/package.json +1 -1
|
@@ -1,231 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* infernoflow scan
|
|
3
|
-
*
|
|
4
|
-
* Deep AST-based code analysis. Reads actual function bodies — not just names.
|
|
5
|
-
* Extracts: external calls, DB operations, HTTP calls, auth patterns, error types,
|
|
6
|
-
* external service usage (Stripe, S3, SendGrid, etc.).
|
|
7
|
-
*
|
|
8
|
-
* Enriches capabilities.json with a `codeAnalysis` block on each capability,
|
|
9
|
-
* and saves the full scan report to inferno/scan.json.
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* infernoflow scan Scan project, enrich capabilities
|
|
13
|
-
* infernoflow scan --dir src/ Scan specific directory
|
|
14
|
-
* infernoflow scan --json Print scan.json to stdout
|
|
15
|
-
* infernoflow scan --dry-run Print without writing files
|
|
16
|
-
* infernoflow scan --capability auth-login Scan one capability only
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import * as fs from "node:fs";
|
|
20
|
-
import * as path from "node:path";
|
|
21
|
-
import { createRequire } from "node:module";
|
|
22
|
-
import { execSync } from "node:child_process";
|
|
23
|
-
import { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
|
|
24
|
-
|
|
25
|
-
const require = createRequire(import.meta.url);
|
|
26
|
-
|
|
27
|
-
// ── TypeScript compiler API (global install) ──────────────────────────────────
|
|
28
|
-
|
|
29
|
-
const TS_PATHS = [
|
|
30
|
-
"/usr/local/lib/node_modules_global/lib/node_modules/typescript",
|
|
31
|
-
"/usr/lib/node_modules/typescript",
|
|
32
|
-
path.join(process.env.HOME || "", ".npm-global/lib/node_modules/typescript"),
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
function loadTypeScript() {
|
|
36
|
-
for (const p of TS_PATHS) {
|
|
37
|
-
try { return require(path.join(p, "lib/typescript.js")); } catch {}
|
|
38
|
-
}
|
|
39
|
-
try { return require("typescript"); } catch {}
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const ts = loadTypeScript();
|
|
44
|
-
|
|
45
|
-
// ── external service fingerprints ────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
const SERVICE_PATTERNS = [
|
|
48
|
-
{ service: "stripe", patterns: ["stripe", "Stripe", "createPaymentIntent", "charges.create"] },
|
|
49
|
-
{ service: "sendgrid", patterns: ["sendgrid", "@sendgrid", "sgMail", "sendgrid.send"] },
|
|
50
|
-
{ service: "ses", patterns: ["SES", "ses.sendEmail", "aws-sdk/ses", "nodemailer"] },
|
|
51
|
-
{ service: "s3", patterns: ["S3", "s3.upload", "s3.getObject", "PutObjectCommand", "@aws-sdk/s3"] },
|
|
52
|
-
{ service: "redis", patterns: ["redis", "Redis", "ioredis", "createClient"] },
|
|
53
|
-
{ service: "jwt", patterns: ["jwt", "jsonwebtoken", "sign(", "verify(", "decode("] },
|
|
54
|
-
{ service: "bcrypt", patterns: ["bcrypt", "argon2", "scrypt", "hashSync", "compare("] },
|
|
55
|
-
{ service: "prisma", patterns: ["prisma.", "PrismaClient", "@prisma/client"] },
|
|
56
|
-
{ service: "mongoose", patterns: ["mongoose", ".save()", ".findOne(", ".aggregate("] },
|
|
57
|
-
{ service: "postgres", patterns: ["pg", "Pool(", "Client(", "query(", "postgres("] },
|
|
58
|
-
{ service: "mysql", patterns: ["mysql", "mysql2", "createConnection"] },
|
|
59
|
-
{ service: "graphql", patterns: ["graphql", "gql`", "ApolloServer", "GraphQLSchema"] },
|
|
60
|
-
{ service: "firebase", patterns: ["firebase", "firestore", "initializeApp"] },
|
|
61
|
-
{ service: "twilio", patterns: ["twilio", "Twilio(", "messages.create"] },
|
|
62
|
-
{ service: "openai", patterns: ["openai", "OpenAI(", "createCompletion", "chat.completions"] },
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
function detectServices(text) {
|
|
66
|
-
const found = new Set();
|
|
67
|
-
for (const { service, patterns } of SERVICE_PATTERNS) {
|
|
68
|
-
if (patterns.some(p => text.includes(p))) found.add(service);
|
|
69
|
-
}
|
|
70
|
-
return [...found];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ── DB call patterns ──────────────────────────────────────────────────────────
|
|
74
|
-
|
|
75
|
-
const DB_PATTERNS = [
|
|
76
|
-
/\.(find|findOne|findMany|findById|findAll)\s*\(/g,
|
|
77
|
-
/\.(create|insert|insertOne|insertMany|save)\s*\(/g,
|
|
78
|
-
/\.(update|updateOne|updateMany|updateById|upsert)\s*\(/g,
|
|
79
|
-
/\.(delete|deleteOne|deleteMany|remove|destroy)\s*\(/g,
|
|
80
|
-
/\.(query|execute|raw)\s*\(/g,
|
|
81
|
-
/\.(aggregate|groupBy|count|sum)\s*\(/g,
|
|
82
|
-
/db\.\w+\s*\(/g,
|
|
83
|
-
/prisma\.\w+\.\w+\s*\(/g,
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
function detectDbCalls(text) {
|
|
87
|
-
const calls = new Set();
|
|
88
|
-
for (const re of DB_PATTERNS) {
|
|
89
|
-
const r = new RegExp(re.source, "g");
|
|
90
|
-
let m;
|
|
91
|
-
while ((m = r.exec(text)) !== null) calls.add(m[0].replace(/\s*\($/, "()"));
|
|
92
|
-
}
|
|
93
|
-
return [...calls].slice(0, 10);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ── HTTP call patterns ────────────────────────────────────────────────────────
|
|
97
|
-
|
|
98
|
-
const HTTP_PATTERNS = [
|
|
99
|
-
/fetch\s*\(/g,
|
|
100
|
-
/axios\.(get|post|put|patch|delete)\s*\(/g,
|
|
101
|
-
/http\.(get|post|request)\s*\(/g,
|
|
102
|
-
/got\.(get|post|put|delete)\s*\(/g,
|
|
103
|
-
/request\.(get|post|put|delete)\s*\(/g,
|
|
104
|
-
/\$http\.(get|post|put|delete)\s*\(/g,
|
|
105
|
-
];
|
|
106
|
-
|
|
107
|
-
function detectHttpCalls(text) {
|
|
108
|
-
const calls = new Set();
|
|
109
|
-
for (const re of HTTP_PATTERNS) {
|
|
110
|
-
const r = new RegExp(re.source, "g");
|
|
111
|
-
let m;
|
|
112
|
-
while ((m = r.exec(text)) !== null) calls.add(m[0].replace(/\s*\($/, "()"));
|
|
113
|
-
}
|
|
114
|
-
return [...calls].slice(0, 8);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ── TypeScript / JavaScript AST analysis ─────────────────────────────────────
|
|
118
|
-
|
|
119
|
-
function getNodeName(node) {
|
|
120
|
-
if (!ts) return null;
|
|
121
|
-
if (node.name && ts.isIdentifier(node.name)) return node.name.text;
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Walk all descendants of root using node.forEachChild (instance method).
|
|
127
|
-
* Collects all call expressions and throw statements globally,
|
|
128
|
-
* then assigns them to containing functions by source position range.
|
|
129
|
-
*/
|
|
130
|
-
function collectAllNodes(root) {
|
|
131
|
-
const calls = []; // { pos, end, name }
|
|
132
|
-
const throws = []; // { pos, end, name }
|
|
133
|
-
|
|
134
|
-
function walk(node) {
|
|
135
|
-
if (ts.isCallExpression(node)) {
|
|
136
|
-
const expr = node.expression;
|
|
137
|
-
if (ts.isIdentifier(expr)) {
|
|
138
|
-
calls.push({ pos: node.pos, end: node.end, name: expr.text + "()" });
|
|
139
|
-
} else if (ts.isPropertyAccessExpression(expr)) {
|
|
140
|
-
calls.push({ pos: node.pos, end: node.end, name: expr.name.text + "()" });
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (ts.isThrowStatement(node) && node.expression) {
|
|
144
|
-
if (ts.isNewExpression(node.expression) && ts.isIdentifier(node.expression.expression)) {
|
|
145
|
-
throws.push({ pos: node.pos, end: node.end, name: node.expression.expression.text });
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
node.forEachChild?.(walk);
|
|
149
|
-
}
|
|
150
|
-
walk(root);
|
|
151
|
-
return { calls, throws };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function callsInRange(allCalls, pos, end) {
|
|
155
|
-
return [...new Set(
|
|
156
|
-
allCalls.filter(c => c.pos >= pos && c.end <= end).map(c => c.name)
|
|
157
|
-
)].slice(0, 20);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function throwsInRange(allThrows, pos, end) {
|
|
161
|
-
return [...new Set(
|
|
162
|
-
allThrows.filter(t => t.pos >= pos && t.end <= end).map(t => t.name)
|
|
163
|
-
)];
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function isFunctionNode(node) {
|
|
167
|
-
if (!ts) return false;
|
|
168
|
-
return (
|
|
169
|
-
ts.isFunctionDeclaration(node) ||
|
|
170
|
-
ts.isFunctionExpression(node) ||
|
|
171
|
-
ts.isArrowFunction(node) ||
|
|
172
|
-
ts.isMethodDeclaration(node)
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function getParentVariableName(node) {
|
|
177
|
-
// For arrow functions assigned to const: const foo = () => {}
|
|
178
|
-
if (!ts) return null;
|
|
179
|
-
if (node.parent && ts.isVariableDeclaration(node.parent)) {
|
|
180
|
-
return getNodeName(node.parent);
|
|
181
|
-
}
|
|
182
|
-
if (node.parent && ts.isPropertyAssignment(node.parent)) {
|
|
183
|
-
return getNodeName(node.parent);
|
|
184
|
-
}
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function analyzeJsTs(filePath, code) {
|
|
189
|
-
if (!ts) return null;
|
|
190
|
-
|
|
191
|
-
let srcFile;
|
|
192
|
-
try {
|
|
193
|
-
srcFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, /*setParentNodes*/ true);
|
|
194
|
-
} catch {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Collect ALL call/throw nodes in one pass from root
|
|
199
|
-
const { calls: allCalls, throws: allThrows } = collectAllNodes(srcFile);
|
|
200
|
-
|
|
201
|
-
const functions = [];
|
|
202
|
-
|
|
203
|
-
function visit(node) {
|
|
204
|
-
if (isFunctionNode(node)) {
|
|
205
|
-
const name = getNodeName(node) || getParentVariableName(node) || "<anonymous>";
|
|
206
|
-
const text = code.slice(node.pos, node.end);
|
|
207
|
-
const calls = callsInRange(allCalls, node.pos, node.end);
|
|
208
|
-
const throws = throwsInRange(allThrows, node.pos, node.end);
|
|
209
|
-
functions.push({
|
|
210
|
-
name,
|
|
211
|
-
calls,
|
|
212
|
-
throws,
|
|
213
|
-
services: detectServices(text),
|
|
214
|
-
dbCalls: detectDbCalls(text),
|
|
215
|
-
httpCalls: detectHttpCalls(text),
|
|
216
|
-
loc: srcFile.getLineAndCharacterOfPosition(node.pos).line + 1,
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
node.forEachChild?.(visit);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
visit(srcFile);
|
|
223
|
-
return functions;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ── Python AST analysis via child_process ─────────────────────────────────────
|
|
227
|
-
|
|
228
|
-
const PYTHON_SCRIPT = `
|
|
1
|
+
import*as w from"node:fs";import*as g from"node:path";import{createRequire as V}from"node:module";import{execSync as G}from"node:child_process";import{bold as P,cyan as K,gray as u,green as k,yellow as j,red as F}from"../ui/output.mjs";const z=V(import.meta.url),Q=["/usr/local/lib/node_modules_global/lib/node_modules/typescript","/usr/lib/node_modules/typescript",g.join(process.env.HOME||"",".npm-global/lib/node_modules/typescript")];function X(){for(const e of Q)try{return z(g.join(e,"lib/typescript.js"))}catch{}try{return z("typescript")}catch{}return null}const l=X(),Y=[{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 O(e){const t=new Set;for(const{service:n,patterns:s}of Y)s.some(i=>e.includes(i))&&t.add(n);return[...t]}const Z=[/\.(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 T(e){const t=new Set;for(const n of Z){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 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){const t=new Set;for(const n of ee){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)}function N(e){return l&&e.name&&l.isIdentifier(e.name)?e.name.text:null}function se(e){const t=[],n=[];function s(i){if(l.isCallExpression(i)){const r=i.expression;l.isIdentifier(r)?t.push({pos:i.pos,end:i.end,name:r.text+"()"}):l.isPropertyAccessExpression(r)&&t.push({pos:i.pos,end:i.end,name:r.name.text+"()"})}l.isThrowStatement(i)&&i.expression&&l.isNewExpression(i.expression)&&l.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 te(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))].slice(0,20)}function ne(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))]}function ie(e){return l?l.isFunctionDeclaration(e)||l.isFunctionExpression(e)||l.isArrowFunction(e)||l.isMethodDeclaration(e):!1}function oe(e){return l&&(e.parent&&l.isVariableDeclaration(e.parent)||e.parent&&l.isPropertyAssignment(e.parent))?N(e.parent):null}function re(e,t){if(!l)return null;let n;try{n=l.createSourceFile(e,t,l.ScriptTarget.Latest,!0)}catch{return null}const{calls:s,throws:i}=se(n),r=[];function a(c){if(ie(c)){const b=N(c)||oe(c)||"<anonymous>",f=t.slice(c.pos,c.end),d=te(s,c.pos,c.end),x=ne(i,c.pos,c.end);r.push({name:b,calls:d,throws:x,services:O(f),dbCalls:T(f),httpCalls:_(f),loc:n.getLineAndCharacterOfPosition(c.pos).line+1})}c.forEachChild?.(a)}return a(n),r}const ce=`
|
|
229
2
|
import ast, json, sys
|
|
230
3
|
|
|
231
4
|
def get_calls(node):
|
|
@@ -263,333 +36,7 @@ try:
|
|
|
263
36
|
print(json.dumps(functions))
|
|
264
37
|
except Exception as e:
|
|
265
38
|
print(json.dumps([]))
|
|
266
|
-
`;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const result = execSync(
|
|
271
|
-
`python3 -c ${JSON.stringify(PYTHON_SCRIPT)} ${JSON.stringify(filePath)}`,
|
|
272
|
-
{ timeout: 8000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
273
|
-
);
|
|
274
|
-
const fns = JSON.parse(result.trim() || "[]");
|
|
275
|
-
// add service/db/http detection from raw file text
|
|
276
|
-
const code = fs.readFileSync(filePath, "utf8");
|
|
277
|
-
return fns.map(f => ({
|
|
278
|
-
...f,
|
|
279
|
-
services: detectServices(code),
|
|
280
|
-
dbCalls: detectDbCalls(code),
|
|
281
|
-
httpCalls: detectHttpCalls(code),
|
|
282
|
-
}));
|
|
283
|
-
} catch {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// ── regex fallback (Go, Ruby, Java, other) ────────────────────────────────────
|
|
289
|
-
|
|
290
|
-
const FUNC_PATTERNS = [
|
|
291
|
-
{ re: /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/gm, lang: "go" },
|
|
292
|
-
{ re: /^\s*(?:def|async def)\s+(\w+)\s*\(/gm, lang: "py" },
|
|
293
|
-
{ re: /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)?(\w+)\s*\(/gm, lang: "java" },
|
|
294
|
-
{ re: /^\s*def\s+(\w+)\s*[\(\|]/gm, lang: "rb" },
|
|
295
|
-
];
|
|
296
|
-
|
|
297
|
-
function analyzeWithRegex(filePath, code) {
|
|
298
|
-
const ext = path.extname(filePath).slice(1);
|
|
299
|
-
const pattern = FUNC_PATTERNS.find(p => p.lang === ext);
|
|
300
|
-
if (!pattern) return null;
|
|
301
|
-
|
|
302
|
-
const functions = [];
|
|
303
|
-
const r = new RegExp(pattern.re.source, "gm");
|
|
304
|
-
let m;
|
|
305
|
-
while ((m = r.exec(code)) !== null) {
|
|
306
|
-
// grab up to 60 lines after the match for context
|
|
307
|
-
const start = m.index;
|
|
308
|
-
const end = Math.min(start + 2000, code.length);
|
|
309
|
-
const chunk = code.slice(start, end);
|
|
310
|
-
functions.push({
|
|
311
|
-
name: m[1],
|
|
312
|
-
calls: [],
|
|
313
|
-
throws: [],
|
|
314
|
-
services: detectServices(chunk),
|
|
315
|
-
dbCalls: detectDbCalls(chunk),
|
|
316
|
-
httpCalls: detectHttpCalls(chunk),
|
|
317
|
-
loc: code.slice(0, start).split("\n").length,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
return functions.length > 0 ? functions : null;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// ── file walker ───────────────────────────────────────────────────────────────
|
|
324
|
-
|
|
325
|
-
const SKIP_DIRS = new Set([
|
|
326
|
-
"node_modules", ".git", "dist", "build", "out", ".next", ".nuxt",
|
|
327
|
-
"coverage", "__pycache__", ".pytest_cache", "vendor", "tmp", ".turbo",
|
|
328
|
-
"target", ".gradle", "public", "static", "assets",
|
|
329
|
-
]);
|
|
330
|
-
|
|
331
|
-
const SUPPORTED_EXTS = new Set([
|
|
332
|
-
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
|
|
333
|
-
".py", ".go", ".rb", ".java",
|
|
334
|
-
]);
|
|
335
|
-
|
|
336
|
-
const TEST_FILE = /\.(test|spec)\.[jt]sx?$|_test\.(go|py|rb)|spec\.(rb|js|ts)$/;
|
|
337
|
-
|
|
338
|
-
function* walkFiles(dir) {
|
|
339
|
-
let entries;
|
|
340
|
-
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
|
|
341
|
-
catch { return; }
|
|
342
|
-
for (const e of entries) {
|
|
343
|
-
if (e.isDirectory()) {
|
|
344
|
-
if (!SKIP_DIRS.has(e.name)) yield* walkFiles(path.join(dir, e.name));
|
|
345
|
-
} else if (e.isFile()) {
|
|
346
|
-
const ext = path.extname(e.name);
|
|
347
|
-
if (SUPPORTED_EXTS.has(ext) && !TEST_FILE.test(e.name)) {
|
|
348
|
-
yield path.join(dir, e.name);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ── per-file analyzer ─────────────────────────────────────────────────────────
|
|
355
|
-
|
|
356
|
-
function analyzeFile(filePath) {
|
|
357
|
-
let code;
|
|
358
|
-
try { code = fs.readFileSync(filePath, "utf8"); }
|
|
359
|
-
catch { return []; }
|
|
360
|
-
|
|
361
|
-
const ext = path.extname(filePath);
|
|
362
|
-
|
|
363
|
-
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
|
|
364
|
-
return analyzeJsTs(filePath, code) || analyzeWithRegex(filePath, code) || [];
|
|
365
|
-
}
|
|
366
|
-
if (ext === ".py") {
|
|
367
|
-
return analyzePython(filePath) || analyzeWithRegex(filePath, code) || [];
|
|
368
|
-
}
|
|
369
|
-
return analyzeWithRegex(filePath, code) || [];
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// ── capability matcher ────────────────────────────────────────────────────────
|
|
373
|
-
|
|
374
|
-
function tokenise(str) {
|
|
375
|
-
return str.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
376
|
-
.toLowerCase().split(/[\s_\-/.]+/).filter(t => t.length > 1);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function overlap(a, b) {
|
|
380
|
-
const sa = new Set(a), sb = new Set(b);
|
|
381
|
-
let n = 0;
|
|
382
|
-
for (const t of sa) if (sb.has(t)) n++;
|
|
383
|
-
const u = sa.size + sb.size - n;
|
|
384
|
-
return u === 0 ? 0 : n / u;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function matchFunctionToCapability(fn, capabilities) {
|
|
388
|
-
const fnTokens = tokenise(fn.name);
|
|
389
|
-
let best = null, bestScore = 0;
|
|
390
|
-
for (const cap of capabilities) {
|
|
391
|
-
const score = Math.max(
|
|
392
|
-
overlap(fnTokens, tokenise(cap.id || "")),
|
|
393
|
-
overlap(fnTokens, tokenise(cap.name || cap.title || "")),
|
|
394
|
-
);
|
|
395
|
-
if (score > bestScore) { bestScore = score; best = cap; }
|
|
396
|
-
}
|
|
397
|
-
return bestScore >= 0.2 ? { cap: best, score: bestScore } : null;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// ── merge analysis into capability ────────────────────────────────────────────
|
|
401
|
-
|
|
402
|
-
function mergeAnalysis(existing = {}, fn, filePath, cwd) {
|
|
403
|
-
const rel = path.relative(cwd, filePath);
|
|
404
|
-
|
|
405
|
-
// merge arrays without duplicates
|
|
406
|
-
const merge = (a = [], b = []) => [...new Set([...a, ...b])];
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
functions: merge(existing.functions, [fn.name]),
|
|
410
|
-
sourceFiles: merge(existing.sourceFiles, [rel]),
|
|
411
|
-
calls: merge(existing.calls, fn.calls),
|
|
412
|
-
throws: merge(existing.throws, fn.throws),
|
|
413
|
-
services: merge(existing.services, fn.services),
|
|
414
|
-
dbCalls: merge(existing.dbCalls, fn.dbCalls),
|
|
415
|
-
httpCalls: merge(existing.httpCalls, fn.httpCalls),
|
|
416
|
-
scannedAt: new Date().toISOString(),
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// ── reporters ─────────────────────────────────────────────────────────────────
|
|
421
|
-
|
|
422
|
-
function printReport(enriched) {
|
|
423
|
-
console.log();
|
|
424
|
-
console.log(bold(" Scan Results"));
|
|
425
|
-
console.log(gray(" ─────────────────────────────────────────────────────────────────"));
|
|
426
|
-
|
|
427
|
-
for (const [capId, analysis] of Object.entries(enriched)) {
|
|
428
|
-
const { codeAnalysis: a } = analysis;
|
|
429
|
-
if (!a) continue;
|
|
430
|
-
|
|
431
|
-
console.log();
|
|
432
|
-
console.log(` ${green("●")} ${bold(capId)}`);
|
|
433
|
-
if (a.sourceFiles?.length) console.log(gray(` files: `) + a.sourceFiles.join(", "));
|
|
434
|
-
if (a.functions?.length) console.log(gray(` funcs: `) + a.functions.join(", "));
|
|
435
|
-
if (a.services?.length) console.log(gray(` services: `) + cyan(a.services.join(", ")));
|
|
436
|
-
if (a.dbCalls?.length) console.log(gray(` db: `) + a.dbCalls.slice(0, 4).join(", "));
|
|
437
|
-
if (a.httpCalls?.length) console.log(gray(` http: `) + a.httpCalls.slice(0, 4).join(", "));
|
|
438
|
-
if (a.throws?.length) console.log(gray(` throws: `) + yellow(a.throws.join(", ")));
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
console.log();
|
|
442
|
-
console.log(gray(" ─────────────────────────────────────────────────────────────────"));
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// ── entry point ───────────────────────────────────────────────────────────────
|
|
446
|
-
|
|
447
|
-
export async function scanCommand(rawArgs) {
|
|
448
|
-
const args = rawArgs || [];
|
|
449
|
-
const dryRun = args.includes("--dry-run");
|
|
450
|
-
const jsonMode = args.includes("--json");
|
|
451
|
-
const dirIdx = args.indexOf("--dir");
|
|
452
|
-
const extraDirs = dirIdx !== -1 ? [args[dirIdx + 1]] : [];
|
|
453
|
-
const capFilter = (() => { const i = args.indexOf("--capability"); return i !== -1 ? args[i + 1] : null; })();
|
|
454
|
-
|
|
455
|
-
const cwd = process.cwd();
|
|
456
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
457
|
-
|
|
458
|
-
// Load capabilities
|
|
459
|
-
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
460
|
-
if (!fs.existsSync(capsPath)) {
|
|
461
|
-
console.error(red("✗ inferno/capabilities.json not found — run `infernoflow init` first."));
|
|
462
|
-
process.exit(1);
|
|
463
|
-
}
|
|
464
|
-
let capabilities;
|
|
465
|
-
let capsFileIsObject = false; // track original format so we can write it back correctly
|
|
466
|
-
let capsFileWrapper = null;
|
|
467
|
-
try { capabilities = JSON.parse(fs.readFileSync(capsPath, "utf8")); }
|
|
468
|
-
catch (e) { console.error(red("✗ Failed to parse capabilities.json: " + e.message)); process.exit(1); }
|
|
469
|
-
|
|
470
|
-
if (!Array.isArray(capabilities)) {
|
|
471
|
-
// handle object format { capabilities: [...] }
|
|
472
|
-
if (capabilities.capabilities) {
|
|
473
|
-
capsFileIsObject = true;
|
|
474
|
-
capsFileWrapper = capabilities; // preserve other top-level keys
|
|
475
|
-
capabilities = capabilities.capabilities;
|
|
476
|
-
}
|
|
477
|
-
else { console.error(red("✗ Unexpected capabilities.json format.")); process.exit(1); }
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Filter by --capability flag
|
|
481
|
-
const targetCaps = capFilter
|
|
482
|
-
? capabilities.filter(c => c.id === capFilter || (c.name || "").toLowerCase() === capFilter.toLowerCase())
|
|
483
|
-
: capabilities;
|
|
484
|
-
|
|
485
|
-
if (targetCaps.length === 0) {
|
|
486
|
-
console.log(yellow(capFilter ? `No capability matched: ${capFilter}` : "No capabilities found."));
|
|
487
|
-
process.exit(0);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Walk source files
|
|
491
|
-
const scanDirs = [cwd, ...extraDirs];
|
|
492
|
-
if (!jsonMode) process.stdout.write(gray(" Walking source files…"));
|
|
493
|
-
const files = [];
|
|
494
|
-
for (const dir of scanDirs) {
|
|
495
|
-
for (const f of walkFiles(dir)) files.push(f);
|
|
496
|
-
}
|
|
497
|
-
if (!jsonMode) process.stdout.write(`\r Found ${files.length} source files. \n`);
|
|
498
|
-
|
|
499
|
-
// Analyze files
|
|
500
|
-
if (!jsonMode) process.stdout.write(gray(" Analyzing…"));
|
|
501
|
-
const allFunctions = []; // { fn, filePath }
|
|
502
|
-
let analyzed = 0;
|
|
503
|
-
for (const filePath of files) {
|
|
504
|
-
const fns = analyzeFile(filePath);
|
|
505
|
-
for (const fn of fns) allFunctions.push({ fn, filePath });
|
|
506
|
-
analyzed++;
|
|
507
|
-
if (!jsonMode && analyzed % 20 === 0) {
|
|
508
|
-
process.stdout.write(`\r Analyzed ${analyzed}/${files.length} files…`);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
if (!jsonMode) process.stdout.write(`\r Analyzed ${files.length} files, found ${allFunctions.length} functions. \n`);
|
|
512
|
-
|
|
513
|
-
// Map functions to capabilities
|
|
514
|
-
const enriched = {}; // capId → { ...cap, codeAnalysis: {...} }
|
|
515
|
-
|
|
516
|
-
for (const cap of targetCaps) {
|
|
517
|
-
enriched[cap.id] = { ...cap, codeAnalysis: null };
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
for (const { fn, filePath } of allFunctions) {
|
|
521
|
-
const match = matchFunctionToCapability(fn, targetCaps);
|
|
522
|
-
if (!match) continue;
|
|
523
|
-
const { cap } = match;
|
|
524
|
-
const existing = enriched[cap.id]?.codeAnalysis || {};
|
|
525
|
-
enriched[cap.id].codeAnalysis = mergeAnalysis(existing, fn, filePath, cwd);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Compute stats
|
|
529
|
-
const total = Object.keys(enriched).length;
|
|
530
|
-
const matched = Object.values(enriched).filter(e => e.codeAnalysis).length;
|
|
531
|
-
|
|
532
|
-
if (jsonMode) {
|
|
533
|
-
const out = {
|
|
534
|
-
scannedAt: new Date().toISOString(),
|
|
535
|
-
files: files.length,
|
|
536
|
-
functions: allFunctions.length,
|
|
537
|
-
capabilities: Object.entries(enriched).map(([id, data]) => ({
|
|
538
|
-
id,
|
|
539
|
-
name: data.name || data.title,
|
|
540
|
-
codeAnalysis: data.codeAnalysis,
|
|
541
|
-
})),
|
|
542
|
-
};
|
|
543
|
-
console.log(JSON.stringify(out, null, 2));
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
printReport(enriched);
|
|
548
|
-
console.log(` ${green("✔")} Matched ${matched}/${total} capabilities to source functions`);
|
|
549
|
-
console.log();
|
|
550
|
-
|
|
551
|
-
if (dryRun) {
|
|
552
|
-
console.log(yellow(" --dry-run: no files written."));
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Write scan.json
|
|
557
|
-
const scanData = {
|
|
558
|
-
scannedAt: new Date().toISOString(),
|
|
559
|
-
files: files.length,
|
|
560
|
-
functions: allFunctions.length,
|
|
561
|
-
capabilities: Object.entries(enriched).map(([id, data]) => ({
|
|
562
|
-
id,
|
|
563
|
-
name: data.name || data.title,
|
|
564
|
-
codeAnalysis: data.codeAnalysis,
|
|
565
|
-
})),
|
|
566
|
-
};
|
|
567
|
-
const scanPath = path.join(infernoDir, "scan.json");
|
|
568
|
-
fs.writeFileSync(scanPath, JSON.stringify(scanData, null, 2));
|
|
569
|
-
console.log(gray(` Saved → inferno/scan.json`));
|
|
570
|
-
|
|
571
|
-
// Enrich capabilities.json
|
|
572
|
-
let changed = 0;
|
|
573
|
-
const updatedCaps = capabilities.map(cap => {
|
|
574
|
-
const analysis = enriched[cap.id]?.codeAnalysis;
|
|
575
|
-
if (!analysis) return cap;
|
|
576
|
-
changed++;
|
|
577
|
-
return { ...cap, codeAnalysis: analysis };
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
if (changed > 0) {
|
|
581
|
-
// Preserve the original file format: object wrapper or bare array
|
|
582
|
-
const toWrite = capsFileIsObject
|
|
583
|
-
? { ...capsFileWrapper, capabilities: updatedCaps }
|
|
584
|
-
: updatedCaps;
|
|
585
|
-
fs.writeFileSync(capsPath, JSON.stringify(toWrite, null, 2));
|
|
586
|
-
console.log(gray(` Updated ${changed} capability entries in capabilities.json`));
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
console.log();
|
|
590
|
-
if (!ts) {
|
|
591
|
-
console.log(yellow(" ⚠ TypeScript compiler not found — JS/TS analyzed with regex fallback."));
|
|
592
|
-
console.log(gray(` For deeper analysis: npm install -g typescript`));
|
|
593
|
-
console.log();
|
|
594
|
-
}
|
|
595
|
-
}
|
|
39
|
+
`;function le(e){try{const t=G(`python3 -c ${JSON.stringify(ce)} ${JSON.stringify(e)}`,{timeout:8e3,encoding:"utf8",stdio:["pipe","pipe","pipe"]}),n=JSON.parse(t.trim()||"[]"),s=w.readFileSync(e,"utf8");return n.map(i=>({...i,services:O(s),dbCalls:T(s),httpCalls:_(s)}))}catch{return null}}const ae=[{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=g.extname(e).slice(1),s=ae.find(c=>c.lang===n);if(!s)return null;const i=[],r=new RegExp(s.re.source,"gm");let a;for(;(a=r.exec(t))!==null;){const c=a.index,b=Math.min(c+2e3,t.length),f=t.slice(c,b);i.push({name:a[1],calls:[],throws:[],services:O(f),dbCalls:T(f),httpCalls:_(f),loc:t.slice(0,c).split(`
|
|
40
|
+
`).length})}return i.length>0?i:null}const pe=new Set(["node_modules",".git","dist","build","out",".next",".nuxt","coverage","__pycache__",".pytest_cache","vendor","tmp",".turbo","target",".gradle","public","static","assets"]),ue=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".go",".rb",".java"]),fe=/\.(test|spec)\.[jt]sx?$|_test\.(go|py|rb)|spec\.(rb|js|ts)$/;function*q(e){let t;try{t=w.readdirSync(e,{withFileTypes:!0})}catch{return}for(const n of t)if(n.isDirectory())pe.has(n.name)||(yield*q(g.join(e,n.name)));else if(n.isFile()){const s=g.extname(n.name);ue.has(s)&&!fe.test(n.name)&&(yield g.join(e,n.name))}}function de(e){let t;try{t=w.readFileSync(e,"utf8")}catch{return[]}const n=g.extname(e);return[".ts",".tsx",".js",".jsx",".mjs",".cjs"].includes(n)?re(e,t)||E(e,t)||[]:n===".py"?le(e)||E(e,t)||[]:E(e,t)||[]}function $(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").toLowerCase().split(/[\s_\-/.]+/).filter(t=>t.length>1)}function M(e,t){const n=new Set(e),s=new Set(t);let i=0;for(const a of n)s.has(a)&&i++;const r=n.size+s.size-i;return r===0?0:i/r}function ge(e,t){const n=$(e.name);let s=null,i=0;for(const r of t){const a=Math.max(M(n,$(r.id||"")),M(n,$(r.name||r.title||"")));a>i&&(i=a,s=r)}return i>=.2?{cap:s,score:i}:null}function me(e={},t,n,s){const i=g.relative(s,n),r=(a=[],c=[])=>[...new Set([...a,...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(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: ")+K(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: ")+j(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 Se(e){const t=e||[],n=t.includes("--dry-run"),s=t.includes("--json"),i=t.indexOf("--dir"),r=i!==-1?[t[i+1]]:[],a=(()=>{const o=t.indexOf("--capability");return o!==-1?t[o+1]:null})(),c=process.cwd(),b=g.join(c,"inferno"),f=g.join(b,"capabilities.json");w.existsSync(f)||(console.error(F("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let d,x=!1,I=null;try{d=JSON.parse(w.readFileSync(f,"utf8"))}catch(o){console.error(F("\u2717 Failed to parse capabilities.json: "+o.message)),process.exit(1)}Array.isArray(d)||(d.capabilities?(x=!0,I=d,d=d.capabilities):(console.error(F("\u2717 Unexpected capabilities.json format.")),process.exit(1)));const v=a?d.filter(o=>o.id===a||(o.name||"").toLowerCase()===a.toLowerCase()):d;v.length===0&&(console.log(j(a?`No capability matched: ${a}`:"No capabilities found.")),process.exit(0));const J=[c,...r];s||process.stdout.write(u(" Walking source files\u2026"));const h=[];for(const o of J)for(const p of q(o))h.push(p);s||process.stdout.write(`\r Found ${h.length} source files.
|
|
41
|
+
`),s||process.stdout.write(u(" Analyzing\u2026"));const S=[];let C=0;for(const o of h){const p=de(o);for(const y of p)S.push({fn:y,filePath:o});C++,!s&&C%20===0&&process.stdout.write(`\r Analyzed ${C}/${h.length} files\u2026`)}s||process.stdout.write(`\r Analyzed ${h.length} files, found ${S.length} functions.
|
|
42
|
+
`);const m={};for(const o of v)m[o.id]={...o,codeAnalysis:null};for(const{fn:o,filePath:p}of S){const y=ge(o,v);if(!y)continue;const{cap:D}=y,W=m[D.id]?.codeAnalysis||{};m[D.id].codeAnalysis=me(W,o,p,c)}const L=Object.keys(m).length,H=Object.values(m).filter(o=>o.codeAnalysis).length;if(s){const o={scannedAt:new Date().toISOString(),files:h.length,functions:S.length,capabilities:Object.entries(m).map(([p,y])=>({id:p,name:y.name||y.title,codeAnalysis:y.codeAnalysis}))};console.log(JSON.stringify(o,null,2));return}if(ye(m),console.log(` ${k("\u2714")} Matched ${H}/${L} capabilities to source functions`),console.log(),n){console.log(j(" --dry-run: no files written."));return}const B={scannedAt:new Date().toISOString(),files:h.length,functions:S.length,capabilities:Object.entries(m).map(([o,p])=>({id:o,name:p.name||p.title,codeAnalysis:p.codeAnalysis}))},U=g.join(b,"scan.json");w.writeFileSync(U,JSON.stringify(B,null,2)),console.log(u(" Saved \u2192 inferno/scan.json"));let A=0;const R=d.map(o=>{const p=m[o.id]?.codeAnalysis;return p?(A++,{...o,codeAnalysis:p}):o});if(A>0){const o=x?{...I,capabilities:R}:R;w.writeFileSync(f,JSON.stringify(o,null,2)),console.log(u(` Updated ${A} capability entries in capabilities.json`))}console.log(),l||(console.log(j(" \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{Se as scanCommand};
|