infernoflow 0.10.2 → 0.10.4
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/README.md +8 -0
- package/bin/infernoflow.mjs +1 -0
- package/lib/commands/adopt.mjs +256 -7
- package/lib/commands/init.mjs +48 -15
- package/package.json +43 -43
package/README.md
CHANGED
|
@@ -68,10 +68,17 @@ JSON-only output (clean machine output, no text logs):
|
|
|
68
68
|
infernoflow init --adopt --yes --report-json-only
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
Human-only output (visual report only, no JSON block):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
infernoflow init --adopt --yes --report-human-only
|
|
75
|
+
```
|
|
76
|
+
|
|
71
77
|
What adoption creates:
|
|
72
78
|
- `inferno/contract.json` (inferred capability baseline)
|
|
73
79
|
- `inferno/capabilities.json` (inferred registry)
|
|
74
80
|
- `inferno/scenarios/adoption_baseline.json` (coverage baseline)
|
|
81
|
+
- `inferno/adoption_profile.json` (detected components, display fields, external libraries, UI layout, styling hints)
|
|
75
82
|
- `inferno/CHANGELOG.md` (adoption entry)
|
|
76
83
|
|
|
77
84
|
Safety:
|
|
@@ -155,6 +162,7 @@ infernoflow doc-gate --json
|
|
|
155
162
|
infernoflow init --force # overwrite existing files
|
|
156
163
|
infernoflow init --yes # skip prompts, use defaults
|
|
157
164
|
infernoflow init --adopt # infer baseline from existing project
|
|
165
|
+
infernoflow init --adopt --report-human-only
|
|
158
166
|
infernoflow suggest "..." # describe what changed
|
|
159
167
|
infernoflow implement "..." --mode both
|
|
160
168
|
infernoflow implement "..." --mode cursor
|
package/bin/infernoflow.mjs
CHANGED
|
@@ -47,6 +47,7 @@ ${formatCommandsHelp()}
|
|
|
47
47
|
--adopt Infer capabilities from an existing codebase
|
|
48
48
|
--report-json Print inferred adoption report as JSON
|
|
49
49
|
--report-json-only Print JSON report only (no human-readable logs)
|
|
50
|
+
--report-human-only Print only human-readable adoption report (no JSON block)
|
|
50
51
|
--yes, -y Skip prompts and accept inferred/default values
|
|
51
52
|
--force, -f Overwrite existing inferno/ files
|
|
52
53
|
|
package/lib/commands/adopt.mjs
CHANGED
|
@@ -38,6 +38,10 @@ const HEURISTICS = [
|
|
|
38
38
|
];
|
|
39
39
|
|
|
40
40
|
export function discoverCapabilities(cwd) {
|
|
41
|
+
return discoverProjectSignals(cwd).capabilities;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function collectCodeFiles(cwd) {
|
|
41
45
|
const files = [];
|
|
42
46
|
const roots = ["src", "server", "app", "backend", "frontend", "api"];
|
|
43
47
|
for (const r of roots) {
|
|
@@ -51,13 +55,171 @@ export function discoverCapabilities(cwd) {
|
|
|
51
55
|
if (entry.isDirectory()) {
|
|
52
56
|
if (["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
|
|
53
57
|
stack.push(p);
|
|
54
|
-
} else if (/\.(js|jsx|ts|tsx|mjs|cjs|json|md)$/.test(entry.name)) {
|
|
58
|
+
} else if (/\.(js|jsx|ts|tsx|mjs|cjs|json|md|html|htm)$/.test(entry.name)) {
|
|
55
59
|
files.push(p);
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
}
|
|
64
|
+
return files;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function detectComponents(files, cwd) {
|
|
68
|
+
const names = new Set();
|
|
69
|
+
for (const filePath of files) {
|
|
70
|
+
const rel = path.relative(cwd, filePath);
|
|
71
|
+
const text = safeRead(filePath);
|
|
72
|
+
const classMatches = text.matchAll(/\bclass\s+([A-Z][A-Za-z0-9_]*?(?:Component|Page|View|Widget|Card))\b/g);
|
|
73
|
+
for (const m of classMatches) names.add(m[1]);
|
|
74
|
+
const selectorMatches = text.matchAll(/\bselector\s*:\s*["']([^"']+)["']/g);
|
|
75
|
+
for (const m of selectorMatches) names.add(m[1]);
|
|
76
|
+
const reactFnMatches = text.matchAll(/\bfunction\s+([A-Z][A-Za-z0-9_]*)\s*\(/g);
|
|
77
|
+
for (const m of reactFnMatches) {
|
|
78
|
+
if (/component|page|view|card|chart|dashboard/i.test(m[1])) names.add(m[1]);
|
|
79
|
+
}
|
|
80
|
+
const relMatch = rel.match(/([^/\\]+)\.(component|page|view|widget|card)\.(ts|tsx|js|jsx)$/i);
|
|
81
|
+
if (relMatch) names.add(relMatch[1]);
|
|
82
|
+
}
|
|
83
|
+
return Array.from(names).sort();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function detectDisplayFields(files) {
|
|
87
|
+
const fields = new Set();
|
|
88
|
+
const methodNames = new Set();
|
|
89
|
+
const stopWords = new Set([
|
|
90
|
+
"if", "for", "while", "const", "let", "var", "return", "function", "class", "import", "export",
|
|
91
|
+
"null", "undefined", "true", "false", "string", "number", "boolean", "any", "unknown", "never",
|
|
92
|
+
"selector", "templateUrl", "styleUrl", "standalone", "imports", "providers", "providedIn",
|
|
93
|
+
"options", "scales", "responsive", "display", "title", "type", "label",
|
|
94
|
+
"component", "service", "routes", "appConfig", "ApplicationConfig",
|
|
95
|
+
]);
|
|
96
|
+
const add = (v) => {
|
|
97
|
+
if (!v) return;
|
|
98
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(v)) return;
|
|
99
|
+
if (v.length <= 1) return;
|
|
100
|
+
if (stopWords.has(v)) return;
|
|
101
|
+
if (/^[A-Z0-9_]+$/.test(v)) return;
|
|
102
|
+
fields.add(v);
|
|
103
|
+
};
|
|
104
|
+
for (const filePath of files) {
|
|
105
|
+
const text = safeRead(filePath);
|
|
106
|
+
if (/\.(html|htm)$/i.test(filePath)) {
|
|
107
|
+
const angularInterpolations = text.matchAll(/\{\{\s*(?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)/g);
|
|
108
|
+
for (const m of angularInterpolations) add(m[1]);
|
|
109
|
+
const ngModels = text.matchAll(/\[\(ngModel\)\]\s*=\s*["']([a-zA-Z_][a-zA-Z0-9_]*)["']/g);
|
|
110
|
+
for (const m of ngModels) add(m[1]);
|
|
111
|
+
const ngInputs = text.matchAll(/\[[a-zA-Z0-9_-]+\]\s*=\s*["'](?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)["']/g);
|
|
112
|
+
for (const m of ngInputs) add(m[1]);
|
|
113
|
+
const ngIfs = text.matchAll(/\*ngIf\s*=\s*["'](?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)/g);
|
|
114
|
+
for (const m of ngIfs) add(m[1]);
|
|
115
|
+
}
|
|
116
|
+
if (/\.(ts|tsx|js|jsx|mjs|cjs)$/i.test(filePath)) {
|
|
117
|
+
const methodDecl = text.matchAll(
|
|
118
|
+
/(?:^|\n)\s*(?:public|private|protected)?\s*(?:async\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)\s*\{/g
|
|
119
|
+
);
|
|
120
|
+
for (const m of methodDecl) methodNames.add(m[1]);
|
|
121
|
+
|
|
122
|
+
const thisRefs = text.matchAll(/\bthis\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g);
|
|
123
|
+
for (const m of thisRefs) add(m[1]);
|
|
124
|
+
|
|
125
|
+
const classProps = text.matchAll(
|
|
126
|
+
/(?:^|\n)\s*(?:public|private|protected)?\s*(?:readonly\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*(?::|=)/g
|
|
127
|
+
);
|
|
128
|
+
for (const m of classProps) {
|
|
129
|
+
add(m[1]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const inputProps = text.matchAll(/@Input\([^)]*\)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*[:=]/g);
|
|
133
|
+
for (const m of inputProps) add(m[1]);
|
|
134
|
+
|
|
135
|
+
const forEachParams = text.matchAll(/forEach\(\((\w+)\)\s*=>/g);
|
|
136
|
+
for (const m of forEachParams) {
|
|
137
|
+
const item = m[1];
|
|
138
|
+
const propAccess = new RegExp(`\\b${item}\\.([a-zA-Z_][a-zA-Z0-9_]*)\\b`, "g");
|
|
139
|
+
for (const p of text.matchAll(propAccess)) add(p[1]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return Array.from(fields)
|
|
144
|
+
.filter((name) => !methodNames.has(name))
|
|
145
|
+
.sort()
|
|
146
|
+
.slice(0, 80);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function detectExternalLibraries(cwd) {
|
|
150
|
+
const libs = new Set();
|
|
151
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
152
|
+
if (!fs.existsSync(pkgPath)) return [];
|
|
153
|
+
try {
|
|
154
|
+
const pkg = JSON.parse(safeRead(pkgPath) || "{}");
|
|
155
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
156
|
+
for (const name of Object.keys(deps)) libs.add(name);
|
|
157
|
+
} catch {}
|
|
158
|
+
return Array.from(libs).sort();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function detectStyling(cwd, files, externalLibraries) {
|
|
162
|
+
const styleFiles = files
|
|
163
|
+
.filter((f) => /\.(css|scss|sass|less|styl)$/i.test(f))
|
|
164
|
+
.map((f) => path.relative(cwd, f))
|
|
165
|
+
.sort();
|
|
166
|
+
|
|
167
|
+
const frameworks = [];
|
|
168
|
+
const hasDep = (name) => externalLibraries.includes(name);
|
|
169
|
+
if (hasDep("tailwindcss")) frameworks.push("Tailwind CSS");
|
|
170
|
+
if (hasDep("bootstrap")) frameworks.push("Bootstrap");
|
|
171
|
+
if (externalLibraries.some((lib) => lib.startsWith("@angular/material"))) frameworks.push("Angular Material");
|
|
172
|
+
if (hasDep("antd")) frameworks.push("Ant Design");
|
|
173
|
+
if (hasDep("styled-components")) frameworks.push("styled-components");
|
|
174
|
+
if (hasDep("emotion") || hasDep("@emotion/react")) frameworks.push("Emotion");
|
|
175
|
+
|
|
176
|
+
const tokenVars = new Set();
|
|
177
|
+
for (const filePath of files) {
|
|
178
|
+
if (!/\.(css|scss|sass|less|styl|html|htm|ts|tsx|js|jsx|mjs|cjs)$/i.test(filePath)) continue;
|
|
179
|
+
const text = safeRead(filePath);
|
|
180
|
+
for (const m of text.matchAll(/--([a-zA-Z][a-zA-Z0-9_-]*)/g)) tokenVars.add(`--${m[1]}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
cssFrameworks: frameworks,
|
|
185
|
+
styleFileCount: styleFiles.length,
|
|
186
|
+
styleFilesSample: styleFiles.slice(0, 12),
|
|
187
|
+
designTokens: Array.from(tokenVars).sort().slice(0, 24),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function detectUiLayout(files) {
|
|
192
|
+
let usesGrid = false;
|
|
193
|
+
let usesFlex = false;
|
|
194
|
+
const sections = new Set();
|
|
195
|
+
|
|
196
|
+
for (const filePath of files) {
|
|
197
|
+
if (!/\.(html|htm|tsx|jsx|ts|js|mjs|cjs)$/i.test(filePath)) continue;
|
|
198
|
+
const text = safeRead(filePath);
|
|
199
|
+
|
|
200
|
+
if (/\bgrid\b|grid-template|grid-cols-|display:\s*grid/i.test(text)) usesGrid = true;
|
|
201
|
+
if (/\bflex\b|display:\s*flex|flex-row|flex-col|justify-|items-/i.test(text)) usesFlex = true;
|
|
202
|
+
|
|
203
|
+
for (const m of text.matchAll(/<(main|header|footer|section|aside|nav)\b/gi)) {
|
|
204
|
+
sections.add(m[1].toLowerCase());
|
|
205
|
+
}
|
|
206
|
+
for (const m of text.matchAll(/class(?:Name)?\s*=\s*["'`][^"'`]*(dashboard|chart|card|sidebar|content|toolbar|filter|panel|table)[^"'`]*["'`]/gi)) {
|
|
207
|
+
const hit = m[1].toLowerCase();
|
|
208
|
+
sections.add(hit === "filter" ? "filters" : hit);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const layoutType = usesGrid && usesFlex ? "mixed" : usesGrid ? "grid" : usesFlex ? "flex" : "unknown";
|
|
213
|
+
return {
|
|
214
|
+
layoutType,
|
|
215
|
+
usesGrid,
|
|
216
|
+
usesFlex,
|
|
217
|
+
sections: Array.from(sections).sort(),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
60
220
|
|
|
221
|
+
export function discoverProjectSignals(cwd) {
|
|
222
|
+
const files = collectCodeFiles(cwd);
|
|
61
223
|
const inferred = new Map();
|
|
62
224
|
const addHit = (cap, filePath) => {
|
|
63
225
|
if (!inferred.has(cap.id)) {
|
|
@@ -96,10 +258,19 @@ export function discoverCapabilities(cwd) {
|
|
|
96
258
|
inferred.set("ReadItems", { id: "ReadItems", title: "Read Items", reason: "Fallback default", sourceFiles: new Set() });
|
|
97
259
|
}
|
|
98
260
|
|
|
99
|
-
|
|
261
|
+
const capabilities = Array.from(inferred.values()).map((c) => ({
|
|
100
262
|
...c,
|
|
101
263
|
sourceFiles: Array.from(c.sourceFiles || []),
|
|
102
264
|
}));
|
|
265
|
+
const externalLibraries = detectExternalLibraries(cwd);
|
|
266
|
+
return {
|
|
267
|
+
capabilities,
|
|
268
|
+
components: detectComponents(files, cwd),
|
|
269
|
+
displayFields: detectDisplayFields(files),
|
|
270
|
+
externalLibraries,
|
|
271
|
+
uiLayout: detectUiLayout(files),
|
|
272
|
+
styling: detectStyling(cwd, files, externalLibraries),
|
|
273
|
+
};
|
|
103
274
|
}
|
|
104
275
|
|
|
105
276
|
export async function reviewCapabilitiesInteractive(capabilities, yes = false) {
|
|
@@ -122,19 +293,83 @@ export async function reviewCapabilitiesInteractive(capabilities, yes = false) {
|
|
|
122
293
|
|
|
123
294
|
export function buildAdoptionReport(capabilities) {
|
|
124
295
|
if (!capabilities.length) return "No capabilities inferred.";
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
296
|
+
const summarized = summarizeCapabilities(capabilities);
|
|
297
|
+
const totalSignals = summarized.reduce((acc, c) => acc + c.signalCount, 0);
|
|
298
|
+
const byConfidence = {
|
|
299
|
+
high: summarized.filter((c) => c.confidence === "high").length,
|
|
300
|
+
medium: summarized.filter((c) => c.confidence === "medium").length,
|
|
301
|
+
low: summarized.filter((c) => c.confidence === "low").length,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const lines = [];
|
|
305
|
+
lines.push("Adoption Analysis");
|
|
306
|
+
lines.push("=".repeat(56));
|
|
307
|
+
lines.push(`Capabilities detected : ${summarized.length}`);
|
|
308
|
+
lines.push(`Signal hits total : ${totalSignals}`);
|
|
309
|
+
lines.push(
|
|
310
|
+
`Confidence mix : high=${byConfidence.high}, medium=${byConfidence.medium}, low=${byConfidence.low}`
|
|
311
|
+
);
|
|
312
|
+
lines.push("-".repeat(56));
|
|
313
|
+
lines.push("Capability Breakdown");
|
|
314
|
+
lines.push("-".repeat(56));
|
|
315
|
+
lines.push("Confidence Signals Capability");
|
|
316
|
+
lines.push("-".repeat(56));
|
|
317
|
+
for (const c of summarized) {
|
|
318
|
+
const confidence = c.confidence.toUpperCase().padEnd(10, " ");
|
|
319
|
+
const signals = String(c.signalCount).padEnd(7, " ");
|
|
320
|
+
lines.push(`${confidence} ${signals} ${c.id} (${c.title})`);
|
|
128
321
|
if (c.signalCount > 0) {
|
|
129
322
|
const sample = c.sourceFiles.slice(0, 3).join(", ");
|
|
130
323
|
lines.push(` sources: ${sample}`);
|
|
324
|
+
if (c.sourceFiles.length > 3) {
|
|
325
|
+
lines.push(` more : +${c.sourceFiles.length - 3} additional files`);
|
|
326
|
+
}
|
|
131
327
|
} else {
|
|
132
|
-
lines.push(
|
|
328
|
+
lines.push(" sources: inferred fallback (no strong code signal)");
|
|
133
329
|
}
|
|
134
330
|
}
|
|
331
|
+
lines.push("=".repeat(56));
|
|
135
332
|
return lines.join("\n");
|
|
136
333
|
}
|
|
137
334
|
|
|
335
|
+
export function buildSignalsReport(signals) {
|
|
336
|
+
const formatList = (title, items, limit = 10) => {
|
|
337
|
+
const lines = [`${title} (${items.length})`];
|
|
338
|
+
lines.push("-".repeat(56));
|
|
339
|
+
if (!items.length) {
|
|
340
|
+
lines.push(" - none");
|
|
341
|
+
return lines.join("\n");
|
|
342
|
+
}
|
|
343
|
+
for (const item of items.slice(0, limit)) {
|
|
344
|
+
lines.push(` - ${item}`);
|
|
345
|
+
}
|
|
346
|
+
if (items.length > limit) {
|
|
347
|
+
lines.push(` - ... +${items.length - limit} more`);
|
|
348
|
+
}
|
|
349
|
+
return lines.join("\n");
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
return [
|
|
353
|
+
"Project Structure Signals",
|
|
354
|
+
"=".repeat(56),
|
|
355
|
+
formatList("Components", signals.components || []),
|
|
356
|
+
formatList("Display fields", signals.displayFields || []),
|
|
357
|
+
formatList("External libraries", signals.externalLibraries || []),
|
|
358
|
+
"UI layout",
|
|
359
|
+
"-".repeat(56),
|
|
360
|
+
` - layout type: ${signals.uiLayout?.layoutType || "unknown"}`,
|
|
361
|
+
` - uses grid : ${signals.uiLayout?.usesGrid ? "yes" : "no"}`,
|
|
362
|
+
` - uses flex : ${signals.uiLayout?.usesFlex ? "yes" : "no"}`,
|
|
363
|
+
` - sections : ${(signals.uiLayout?.sections || []).slice(0, 10).join(", ") || "none"}`,
|
|
364
|
+
"Styling",
|
|
365
|
+
"-".repeat(56),
|
|
366
|
+
` - frameworks : ${(signals.styling?.cssFrameworks || []).join(", ") || "none detected"}`,
|
|
367
|
+
` - style files: ${signals.styling?.styleFileCount ?? 0}`,
|
|
368
|
+
` - tokens : ${(signals.styling?.designTokens || []).slice(0, 8).join(", ") || "none detected"}`,
|
|
369
|
+
"=".repeat(56),
|
|
370
|
+
].join("\n");
|
|
371
|
+
}
|
|
372
|
+
|
|
138
373
|
export function summarizeCapabilities(capabilities) {
|
|
139
374
|
return capabilities.map((c) => {
|
|
140
375
|
const hits = c.sourceFiles?.length || 0;
|
|
@@ -150,7 +385,7 @@ export function summarizeCapabilities(capabilities) {
|
|
|
150
385
|
});
|
|
151
386
|
}
|
|
152
387
|
|
|
153
|
-
export function writeAdoptionBaseline(infernoDir, policyId, capabilities) {
|
|
388
|
+
export function writeAdoptionBaseline(infernoDir, policyId, capabilities, signals = null) {
|
|
154
389
|
const capIds = capabilities.map((c) => c.id);
|
|
155
390
|
const contract = {
|
|
156
391
|
policyId,
|
|
@@ -179,11 +414,25 @@ export function writeAdoptionBaseline(infernoDir, policyId, capabilities) {
|
|
|
179
414
|
};
|
|
180
415
|
fs.writeFileSync(path.join(infernoDir, "scenarios", "adoption_baseline.json"), JSON.stringify(scenario, null, 2) + "\n");
|
|
181
416
|
|
|
417
|
+
if (signals) {
|
|
418
|
+
const profile = {
|
|
419
|
+
profileId: "adoption_profile",
|
|
420
|
+
generatedAt: new Date().toISOString(),
|
|
421
|
+
components: signals.components || [],
|
|
422
|
+
displayFields: signals.displayFields || [],
|
|
423
|
+
externalLibraries: signals.externalLibraries || [],
|
|
424
|
+
uiLayout: signals.uiLayout || { layoutType: "unknown", usesGrid: false, usesFlex: false, sections: [] },
|
|
425
|
+
styling: signals.styling || { cssFrameworks: [], styleFileCount: 0, styleFilesSample: [], designTokens: [] },
|
|
426
|
+
};
|
|
427
|
+
fs.writeFileSync(path.join(infernoDir, "adoption_profile.json"), JSON.stringify(profile, null, 2) + "\n");
|
|
428
|
+
}
|
|
429
|
+
|
|
182
430
|
const changelog = `# Changelog — ${policyId}
|
|
183
431
|
|
|
184
432
|
## Unreleased
|
|
185
433
|
|
|
186
434
|
- Adopted infernoflow into an existing project and generated baseline capabilities.
|
|
435
|
+
- Captured detected components, display fields, and external libraries in adoption profile.
|
|
187
436
|
|
|
188
437
|
## 0.1.0 — Adoption baseline
|
|
189
438
|
|
package/lib/commands/init.mjs
CHANGED
|
@@ -3,7 +3,14 @@ import * as path from "node:path";
|
|
|
3
3
|
import * as readline from "node:readline";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { header, ok, warn, done, nextSteps, cyan, yellow, gray } from "../ui/output.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
discoverProjectSignals,
|
|
8
|
+
reviewCapabilitiesInteractive,
|
|
9
|
+
writeAdoptionBaseline,
|
|
10
|
+
buildAdoptionReport,
|
|
11
|
+
summarizeCapabilities,
|
|
12
|
+
buildSignalsReport,
|
|
13
|
+
} from "./adopt.mjs";
|
|
7
14
|
|
|
8
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
16
|
|
|
@@ -136,14 +143,21 @@ export async function initCommand(args) {
|
|
|
136
143
|
const adopt = args.includes("--adopt");
|
|
137
144
|
const reportJson = args.includes("--report-json");
|
|
138
145
|
const reportJsonOnly = args.includes("--report-json-only");
|
|
146
|
+
const reportHumanOnly = args.includes("--report-human-only");
|
|
147
|
+
const silent = reportJsonOnly;
|
|
139
148
|
|
|
140
|
-
if (
|
|
149
|
+
if (reportJsonOnly && reportHumanOnly) {
|
|
150
|
+
console.error("Error: --report-json-only and --report-human-only cannot be used together.");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!silent) {
|
|
141
155
|
header("init");
|
|
142
156
|
}
|
|
143
157
|
|
|
144
158
|
const infernoDir = path.join(cwd, "inferno");
|
|
145
159
|
if (fs.existsSync(infernoDir) && !force) {
|
|
146
|
-
if (
|
|
160
|
+
if (silent) {
|
|
147
161
|
console.log(JSON.stringify({ ok: false, error: "inferno_exists", hint: "Use --force to overwrite" }, null, 2));
|
|
148
162
|
process.exit(1);
|
|
149
163
|
}
|
|
@@ -159,12 +173,22 @@ export async function initCommand(args) {
|
|
|
159
173
|
let capabilities = defaultCaps.split(",").map(c => c.trim());
|
|
160
174
|
|
|
161
175
|
if (adopt) {
|
|
162
|
-
const
|
|
176
|
+
const signals = discoverProjectSignals(cwd);
|
|
177
|
+
const inferred = signals.capabilities;
|
|
163
178
|
const summarized = summarizeCapabilities(inferred);
|
|
164
179
|
if (reportJsonOnly) {
|
|
165
180
|
console.log(
|
|
166
181
|
JSON.stringify(
|
|
167
|
-
{
|
|
182
|
+
{
|
|
183
|
+
mode: "adopt",
|
|
184
|
+
policyId: detectedName,
|
|
185
|
+
inferredCapabilities: summarized,
|
|
186
|
+
components: signals.components,
|
|
187
|
+
displayFields: signals.displayFields,
|
|
188
|
+
externalLibraries: signals.externalLibraries,
|
|
189
|
+
uiLayout: signals.uiLayout,
|
|
190
|
+
styling: signals.styling,
|
|
191
|
+
},
|
|
168
192
|
null,
|
|
169
193
|
2
|
|
170
194
|
)
|
|
@@ -173,13 +197,20 @@ export async function initCommand(args) {
|
|
|
173
197
|
console.log();
|
|
174
198
|
console.log(gray(buildAdoptionReport(inferred)));
|
|
175
199
|
console.log();
|
|
176
|
-
|
|
200
|
+
console.log(gray(buildSignalsReport(signals)));
|
|
201
|
+
console.log();
|
|
202
|
+
if (reportJson && !reportHumanOnly) {
|
|
177
203
|
console.log(
|
|
178
204
|
JSON.stringify(
|
|
179
205
|
{
|
|
180
206
|
mode: "adopt",
|
|
181
207
|
policyId: detectedName,
|
|
182
208
|
inferredCapabilities: summarized,
|
|
209
|
+
components: signals.components,
|
|
210
|
+
displayFields: signals.displayFields,
|
|
211
|
+
externalLibraries: signals.externalLibraries,
|
|
212
|
+
uiLayout: signals.uiLayout,
|
|
213
|
+
styling: signals.styling,
|
|
183
214
|
},
|
|
184
215
|
null,
|
|
185
216
|
2
|
|
@@ -209,36 +240,38 @@ export async function initCommand(args) {
|
|
|
209
240
|
id,
|
|
210
241
|
title: id.replace(/([A-Z])/g, " $1").trim(),
|
|
211
242
|
}));
|
|
212
|
-
|
|
213
|
-
|
|
243
|
+
const signals = discoverProjectSignals(cwd);
|
|
244
|
+
writeAdoptionBaseline(infernoDir, policyId, capDetails, signals);
|
|
245
|
+
if (!silent) {
|
|
214
246
|
ok("Created: " + cyan("inferno/contract.json"));
|
|
215
247
|
ok("Created: " + cyan("inferno/capabilities.json"));
|
|
216
248
|
ok("Created: " + cyan("inferno/scenarios/adoption_baseline.json"));
|
|
249
|
+
ok("Created: " + cyan("inferno/adoption_profile.json"));
|
|
217
250
|
ok("Created: " + cyan("inferno/CHANGELOG.md"));
|
|
218
251
|
}
|
|
219
252
|
} else {
|
|
220
253
|
writeContract(path.join(infernoDir, "contract.json"), policyId, capabilities);
|
|
221
|
-
if (!
|
|
254
|
+
if (!silent) ok("Created: " + cyan("inferno/contract.json"));
|
|
222
255
|
|
|
223
256
|
writeCapabilities(path.join(infernoDir, "capabilities.json"), capabilities);
|
|
224
|
-
if (!
|
|
257
|
+
if (!silent) ok("Created: " + cyan("inferno/capabilities.json"));
|
|
225
258
|
|
|
226
259
|
writeScenario(path.join(infernoDir, "scenarios"), capabilities);
|
|
227
|
-
if (!
|
|
260
|
+
if (!silent) ok("Created: " + cyan("inferno/scenarios/happy_path.json"));
|
|
228
261
|
|
|
229
262
|
writeChangelog(path.join(infernoDir, "CHANGELOG.md"), policyId);
|
|
230
|
-
if (!
|
|
263
|
+
if (!silent) ok("Created: " + cyan("inferno/CHANGELOG.md"));
|
|
231
264
|
}
|
|
232
265
|
|
|
233
266
|
// Copy doc-gate script
|
|
234
267
|
const templates = getTemplatesRoot();
|
|
235
268
|
const srcScript = path.join(templates, "scripts", "inferno-doc-gate.mjs");
|
|
236
269
|
const dstScript = path.join(cwd, "scripts", "inferno-doc-gate.mjs");
|
|
237
|
-
copyFile(srcScript, dstScript, force,
|
|
270
|
+
copyFile(srcScript, dstScript, force, silent);
|
|
238
271
|
|
|
239
|
-
upsertScripts(cwd,
|
|
272
|
+
upsertScripts(cwd, silent);
|
|
240
273
|
|
|
241
|
-
if (!
|
|
274
|
+
if (!silent) {
|
|
242
275
|
done("infernoflow initialized!");
|
|
243
276
|
|
|
244
277
|
nextSteps([
|
package/package.json
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "infernoflow",
|
|
3
|
-
"version": "0.10.
|
|
4
|
-
"description": "The forge for liquid code — keep capabilities, contracts, and docs in sync.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"infernoflow": "bin/infernoflow.mjs"
|
|
8
|
-
},
|
|
9
|
-
"engines": {
|
|
10
|
-
"node": ">=18"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"bin",
|
|
14
|
-
"lib",
|
|
15
|
-
"templates",
|
|
16
|
-
"README.md"
|
|
17
|
-
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"test": "node scripts/smoke.mjs && node scripts/json-smoke.mjs && node scripts/json-negative-smoke.mjs && node scripts/implement-smoke.mjs",
|
|
20
|
-
"test:help": "node bin/infernoflow.mjs --help"
|
|
21
|
-
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
"cli",
|
|
24
|
-
"capabilities",
|
|
25
|
-
"contract",
|
|
26
|
-
"documentation",
|
|
27
|
-
"ai",
|
|
28
|
-
"liquid-code",
|
|
29
|
-
"dx",
|
|
30
|
-
"developer-tools"
|
|
31
|
-
],
|
|
32
|
-
"author": "infernoflow",
|
|
33
|
-
"license": "MIT",
|
|
34
|
-
"repository": {
|
|
35
|
-
"type": "git",
|
|
36
|
-
"url": "git+https://github.com/ronmiz/infernoflow.git"
|
|
37
|
-
},
|
|
38
|
-
"homepage": "https://github.com/ronmiz/infernoflow#readme",
|
|
39
|
-
"bugs": {
|
|
40
|
-
"url": "https://github.com/ronmiz/infernoflow/issues"
|
|
41
|
-
},
|
|
42
|
-
"dependencies": {}
|
|
43
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "infernoflow",
|
|
3
|
+
"version": "0.10.4",
|
|
4
|
+
"description": "The forge for liquid code — keep capabilities, contracts, and docs in sync.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"infernoflow": "bin/infernoflow.mjs"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"lib",
|
|
15
|
+
"templates",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "node scripts/smoke.mjs && node scripts/json-smoke.mjs && node scripts/json-negative-smoke.mjs && node scripts/implement-smoke.mjs",
|
|
20
|
+
"test:help": "node bin/infernoflow.mjs --help"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"cli",
|
|
24
|
+
"capabilities",
|
|
25
|
+
"contract",
|
|
26
|
+
"documentation",
|
|
27
|
+
"ai",
|
|
28
|
+
"liquid-code",
|
|
29
|
+
"dx",
|
|
30
|
+
"developer-tools"
|
|
31
|
+
],
|
|
32
|
+
"author": "infernoflow",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/ronmiz/infernoflow.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/ronmiz/infernoflow#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/ronmiz/infernoflow/issues"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {}
|
|
43
|
+
}
|