infernoflow 0.10.4 → 0.10.6

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 CHANGED
@@ -56,6 +56,18 @@ Non-interactive adoption:
56
56
  infernoflow init --adopt --yes
57
57
  ```
58
58
 
59
+ Override detected stack during adoption:
60
+
61
+ ```bash
62
+ infernoflow init --adopt --lang ts --framework angular --project-type frontend
63
+ ```
64
+
65
+ C# / ASP.NET example:
66
+
67
+ ```bash
68
+ infernoflow init --adopt --lang cs --framework aspnet --project-type backend --report-human-only
69
+ ```
70
+
59
71
  JSON report for CI/logging:
60
72
 
61
73
  ```bash
@@ -79,6 +91,8 @@ What adoption creates:
79
91
  - `inferno/capabilities.json` (inferred registry)
80
92
  - `inferno/scenarios/adoption_baseline.json` (coverage baseline)
81
93
  - `inferno/adoption_profile.json` (detected components, display fields, external libraries, UI layout, styling hints)
94
+ - `inferno/adoption_profile.json` (detected components, display fields, external libraries, UI layout, styling hints, API call map)
95
+ - `inferno/context-state.json` (saved development profile: language/framework/project type)
82
96
  - `inferno/CHANGELOG.md` (adoption entry)
83
97
 
84
98
  Safety:
@@ -162,6 +176,7 @@ infernoflow doc-gate --json
162
176
  infernoflow init --force # overwrite existing files
163
177
  infernoflow init --yes # skip prompts, use defaults
164
178
  infernoflow init --adopt # infer baseline from existing project
179
+ infernoflow init --adopt --lang ts --framework react --project-type frontend
165
180
  infernoflow init --adopt --report-human-only
166
181
  infernoflow suggest "..." # describe what changed
167
182
  infernoflow implement "..." --mode both
@@ -45,6 +45,9 @@ ${formatCommandsHelp()}
45
45
 
46
46
  ${bold("init options:")}
47
47
  --adopt Infer capabilities from an existing codebase
48
+ --lang <name> Override detected language (e.g. ts, js, py)
49
+ --framework <name> Override detected framework (e.g. react, angular, express)
50
+ --project-type <t> Override project type (frontend|backend|fullstack|cli|library)
48
51
  --report-json Print inferred adoption report as JSON
49
52
  --report-json-only Print JSON report only (no human-readable logs)
50
53
  --report-human-only Print only human-readable adoption report (no JSON block)
@@ -43,7 +43,7 @@ export function discoverCapabilities(cwd) {
43
43
 
44
44
  function collectCodeFiles(cwd) {
45
45
  const files = [];
46
- const roots = ["src", "server", "app", "backend", "frontend", "api"];
46
+ const roots = ["src", "server", "app", "backend", "frontend", "api", "Controllers"];
47
47
  for (const r of roots) {
48
48
  const root = path.join(cwd, r);
49
49
  if (!fs.existsSync(root)) continue;
@@ -55,12 +55,19 @@ function collectCodeFiles(cwd) {
55
55
  if (entry.isDirectory()) {
56
56
  if (["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
57
57
  stack.push(p);
58
- } else if (/\.(js|jsx|ts|tsx|mjs|cjs|json|md|html|htm)$/.test(entry.name)) {
58
+ } else if (/\.(js|jsx|ts|tsx|mjs|cjs|json|md|html|htm|cs|csproj)$/.test(entry.name)) {
59
59
  files.push(p);
60
60
  }
61
61
  }
62
62
  }
63
63
  }
64
+ // Include common root-level .NET entry files without scanning the whole repo.
65
+ for (const entry of fs.readdirSync(cwd, { withFileTypes: true })) {
66
+ if (!entry.isFile()) continue;
67
+ if (/^(Program\.cs|.+\.csproj)$/i.test(entry.name)) {
68
+ files.push(path.join(cwd, entry.name));
69
+ }
70
+ }
64
71
  return files;
65
72
  }
66
73
 
@@ -218,7 +225,248 @@ function detectUiLayout(files) {
218
225
  };
219
226
  }
220
227
 
221
- export function discoverProjectSignals(cwd) {
228
+ function detectDevelopmentProfile(cwd, files, externalLibraries, overrides = {}) {
229
+ const extCount = { ts: 0, js: 0, py: 0, java: 0, go: 0, rb: 0, rs: 0, cs: 0, php: 0 };
230
+ for (const filePath of files) {
231
+ const ext = path.extname(filePath).toLowerCase();
232
+ if (ext === ".ts" || ext === ".tsx") extCount.ts += 1;
233
+ if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") extCount.js += 1;
234
+ if (ext === ".py") extCount.py += 1;
235
+ if (ext === ".java") extCount.java += 1;
236
+ if (ext === ".go") extCount.go += 1;
237
+ if (ext === ".rb") extCount.rb += 1;
238
+ if (ext === ".rs") extCount.rs += 1;
239
+ if (ext === ".cs") extCount.cs += 1;
240
+ if (ext === ".php") extCount.php += 1;
241
+ }
242
+
243
+ const sortedLang = Object.entries(extCount).sort((a, b) => b[1] - a[1]);
244
+ const autoLanguage = sortedLang[0]?.[1] > 0 ? sortedLang[0][0] : "unknown";
245
+
246
+ let autoFramework = "unknown";
247
+ let hasDotnetWebSdk = false;
248
+ let hasMinimalApi = false;
249
+ let hasBlazor = false;
250
+ for (const filePath of files) {
251
+ const base = path.basename(filePath).toLowerCase();
252
+ if (base.endsWith(".csproj")) {
253
+ const text = safeRead(filePath);
254
+ if (/Microsoft\.NET\.Sdk\.Web/i.test(text)) hasDotnetWebSdk = true;
255
+ if (/Blazor/i.test(text) || /Microsoft\.AspNetCore\.Components/i.test(text)) hasBlazor = true;
256
+ }
257
+ if (base === "program.cs") {
258
+ const text = safeRead(filePath);
259
+ if (/app\.Map(Get|Post|Put|Delete|Patch)\s*\(/i.test(text)) hasMinimalApi = true;
260
+ }
261
+ }
262
+ const hasDep = (name) => externalLibraries.includes(name);
263
+ if (externalLibraries.some((d) => d.startsWith("@angular/"))) autoFramework = "angular";
264
+ else if (hasDep("react")) autoFramework = "react";
265
+ else if (hasDep("vue")) autoFramework = "vue";
266
+ else if (hasDep("svelte")) autoFramework = "svelte";
267
+ else if (hasDep("next")) autoFramework = "nextjs";
268
+ else if (hasDep("nuxt")) autoFramework = "nuxt";
269
+ else if (hasDep("express")) autoFramework = "express";
270
+ else if (hasDep("@nestjs/core")) autoFramework = "nestjs";
271
+ else if (hasDep("fastify")) autoFramework = "fastify";
272
+ else if (hasDep("flask")) autoFramework = "flask";
273
+ else if (hasDep("django")) autoFramework = "django";
274
+ else if (hasDep("spring-boot")) autoFramework = "spring";
275
+ else if (hasBlazor) autoFramework = "blazor";
276
+ else if (hasMinimalApi) autoFramework = "minimalapi";
277
+ else if (hasDotnetWebSdk || extCount.cs > 0) autoFramework = "aspnet";
278
+
279
+ let autoProjectType = "fullstack";
280
+ const hasClientRoots = ["src", "frontend", "app"].some((d) => fs.existsSync(path.join(cwd, d)));
281
+ const hasServerRoots = ["server", "backend", "api"].some((d) => fs.existsSync(path.join(cwd, d)));
282
+ if (["react", "angular", "vue", "svelte", "nextjs", "nuxt"].includes(autoFramework)) autoProjectType = "frontend";
283
+ if (["express", "nestjs", "fastify", "flask", "django", "spring", "aspnet", "minimalapi"].includes(autoFramework)) autoProjectType = "backend";
284
+ if (hasClientRoots && hasServerRoots) autoProjectType = "fullstack";
285
+ if (!hasClientRoots && !hasServerRoots) autoProjectType = "library";
286
+ if (autoFramework === "blazor") autoProjectType = "frontend";
287
+
288
+ return {
289
+ language: overrides.language || autoLanguage,
290
+ framework: overrides.framework || autoFramework,
291
+ projectType: overrides.projectType || autoProjectType,
292
+ detected: {
293
+ language: autoLanguage,
294
+ framework: autoFramework,
295
+ projectType: autoProjectType,
296
+ },
297
+ };
298
+ }
299
+
300
+ function detectApiCalls(cwd, files) {
301
+ const calls = [];
302
+ const seen = new Set();
303
+ const addCall = (call) => {
304
+ const key = `${call.method}|${call.endpointPattern}|${call.sourceFile}|${call.style}`;
305
+ if (seen.has(key)) return;
306
+ seen.add(key);
307
+ calls.push(call);
308
+ };
309
+
310
+ for (const filePath of files) {
311
+ if (!/\.(ts|tsx|js|jsx|mjs|cjs|cs)$/i.test(filePath)) continue;
312
+ const rel = path.relative(cwd, filePath);
313
+ const text = safeRead(filePath);
314
+ const looksLikeService = /service|api|client|controller|program\.cs/i.test(rel) || /HttpClient|fetch\(|app\.Map(Get|Post|Put|Delete|Patch)\(/i.test(text);
315
+ if (!looksLikeService) continue;
316
+
317
+ const normalized = text.replace(/\r\n/g, "\n");
318
+
319
+ const constStrings = {};
320
+ const normalizeExpr = (expr) => {
321
+ let out = String(expr || "").trim();
322
+ if (!out) return "";
323
+ out = out.replace(/;+$/, "").trim();
324
+ out = out.replace(/\(\s*$/, ""); // e.g. "this._nextPage("
325
+ // unwrap surrounding quotes/templates
326
+ while ((out.startsWith("'") && out.endsWith("'")) || (out.startsWith('"') && out.endsWith('"')) || (out.startsWith("`") && out.endsWith("`"))) {
327
+ out = out.slice(1, -1).trim();
328
+ }
329
+ return out;
330
+ };
331
+ const isLikelyEndpoint = (value) => {
332
+ const v = String(value || "").trim();
333
+ if (!v) return false;
334
+ if (/^https?:\/\//i.test(v)) return true;
335
+ if (v.startsWith("/")) return true;
336
+ if (/\bapi\b/i.test(v)) return true;
337
+ if (/\$\{[^}]+\}/.test(v)) return true;
338
+ if (/\?[^=\s]+=?/.test(v)) return true;
339
+ if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_./${}-]+$/.test(v)) return true;
340
+ return false;
341
+ };
342
+ const storeConst = (name, raw) => {
343
+ if (!name || !raw) return;
344
+ constStrings[name] = normalizeExpr(raw);
345
+ };
346
+
347
+ const constPattern = /(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*?);/g;
348
+ for (const m of normalized.matchAll(constPattern)) {
349
+ storeConst(m[1], m[2]);
350
+ }
351
+ const readonlyPattern =
352
+ /(?:public|private|protected)?\s*(?:readonly\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*?);/g;
353
+ for (const m of normalized.matchAll(readonlyPattern)) {
354
+ storeConst(m[1], m[2]);
355
+ }
356
+
357
+ const resolveExpr = (expr) => {
358
+ const trimmed = normalizeExpr(expr);
359
+ if (!trimmed) return "";
360
+ if (/^['"`][\s\S]*['"`]$/.test(trimmed)) {
361
+ return trimmed.replace(/^['"`]|['"`]$/g, "");
362
+ }
363
+ if (constStrings[trimmed] && isLikelyEndpoint(constStrings[trimmed])) return constStrings[trimmed];
364
+ const thisRef = trimmed.match(/^this\.([A-Za-z_][A-Za-z0-9_]*)$/);
365
+ if (thisRef && constStrings[thisRef[1]] && isLikelyEndpoint(constStrings[thisRef[1]])) return constStrings[thisRef[1]];
366
+ const parts = trimmed.split("+").map((s) => s.trim()).filter(Boolean);
367
+ if (parts.length > 1) {
368
+ const rebuilt = parts
369
+ .map((p) => {
370
+ if (/^['"`][\s\S]*['"`]$/.test(p)) return p.replace(/^['"`]|['"`]$/g, "");
371
+ if (constStrings[p] && isLikelyEndpoint(constStrings[p])) return constStrings[p];
372
+ const thisP = p.match(/^this\.([A-Za-z_][A-Za-z0-9_]*)$/);
373
+ if (thisP && constStrings[thisP[1]] && isLikelyEndpoint(constStrings[thisP[1]])) return constStrings[thisP[1]];
374
+ return `{${p}}`;
375
+ })
376
+ .join("");
377
+ if (rebuilt) return rebuilt;
378
+ }
379
+ const ternary = trimmed.match(/^(.+?)\?(.+?):(.+)$/s);
380
+ if (ternary) {
381
+ const left = resolveExpr(ternary[2]);
382
+ const right = resolveExpr(ternary[3]);
383
+ if (left || right) return `${left || "{optionA}"} | ${right || "{optionB}"}`;
384
+ }
385
+ return trimmed;
386
+ };
387
+
388
+ const httpClientPattern =
389
+ /\.\s*(get|post|put|patch|delete)\s*(?:<[\s\S]*?>)?\s*\(\s*([\s\S]*?)(?:,|\))/gi;
390
+ for (const m of normalized.matchAll(httpClientPattern)) {
391
+ const method = m[1].toUpperCase();
392
+ const raw = resolveExpr(m[2]);
393
+ if (!raw || !isLikelyEndpoint(raw)) continue;
394
+ addCall({
395
+ method,
396
+ endpointPattern: normalizeExpr(raw),
397
+ style: "httpClient",
398
+ sourceFile: rel,
399
+ });
400
+ }
401
+
402
+ const fetchPattern = /\bfetch\s*\(\s*([\s\S]*?)(?:,|\))/gi;
403
+ for (const m of normalized.matchAll(fetchPattern)) {
404
+ const firstArg = resolveExpr(m[1]);
405
+ if (!firstArg || !isLikelyEndpoint(firstArg)) continue;
406
+ const fromIdx = m.index || 0;
407
+ const lookahead = normalized.slice(fromIdx, fromIdx + 260);
408
+ const methodMatch = /method\s*:\s*["'](GET|POST|PUT|PATCH|DELETE)["']/i.exec(lookahead);
409
+ const method = (methodMatch?.[1] || "GET").toUpperCase();
410
+ addCall({
411
+ method,
412
+ endpointPattern: normalizeExpr(firstArg),
413
+ style: "fetch",
414
+ sourceFile: rel,
415
+ });
416
+ }
417
+
418
+ if (/\.cs$/i.test(filePath)) {
419
+ const mapPattern = /\bapp\.Map(Get|Post|Put|Delete|Patch)\s*\(\s*"([^"]+)"/gi;
420
+ for (const m of normalized.matchAll(mapPattern)) {
421
+ addCall({
422
+ method: m[1].toUpperCase(),
423
+ endpointPattern: m[2],
424
+ style: "csharp-map",
425
+ sourceFile: rel,
426
+ });
427
+ }
428
+
429
+ const classRouteMatch = /\[Route\("([^"]+)"\)\][\s\S]*?class\s+\w+/i.exec(normalized);
430
+ const classRoute = classRouteMatch ? classRouteMatch[1] : "";
431
+ const attrPattern = /\[(HttpGet|HttpPost|HttpPut|HttpDelete|HttpPatch)(?:\("([^"]*)"\))?\]/gi;
432
+ for (const m of normalized.matchAll(attrPattern)) {
433
+ const method = m[1].replace("Http", "").toUpperCase();
434
+ const attrRoute = m[2] || "";
435
+ const endpoint = [classRoute, attrRoute].filter(Boolean).join("/").replace(/\/+/g, "/");
436
+ addCall({
437
+ method,
438
+ endpointPattern: endpoint || classRoute || "{controller-route}",
439
+ style: "csharp-controller",
440
+ sourceFile: rel,
441
+ });
442
+ }
443
+
444
+ const httpClientPattern = /\b(GetAsync|PostAsync|PutAsync|DeleteAsync|SendAsync)\s*\(\s*"([^"]+)"/gi;
445
+ for (const m of normalized.matchAll(httpClientPattern)) {
446
+ const method = m[1].replace("Async", "").replace("Send", "SEND").toUpperCase();
447
+ addCall({
448
+ method,
449
+ endpointPattern: m[2],
450
+ style: "csharp-httpclient",
451
+ sourceFile: rel,
452
+ });
453
+ }
454
+ }
455
+ }
456
+
457
+ const byMethod = calls.reduce((acc, c) => {
458
+ acc[c.method] = (acc[c.method] || 0) + 1;
459
+ return acc;
460
+ }, {});
461
+
462
+ return {
463
+ totalCalls: calls.length,
464
+ byMethod,
465
+ calls: calls.slice(0, 80),
466
+ };
467
+ }
468
+
469
+ export function discoverProjectSignals(cwd, profileOverrides = {}) {
222
470
  const files = collectCodeFiles(cwd);
223
471
  const inferred = new Map();
224
472
  const addHit = (cap, filePath) => {
@@ -270,6 +518,8 @@ export function discoverProjectSignals(cwd) {
270
518
  externalLibraries,
271
519
  uiLayout: detectUiLayout(files),
272
520
  styling: detectStyling(cwd, files, externalLibraries),
521
+ developmentProfile: detectDevelopmentProfile(cwd, files, externalLibraries, profileOverrides),
522
+ apiCalls: detectApiCalls(cwd, files),
273
523
  };
274
524
  }
275
525
 
@@ -366,6 +616,19 @@ export function buildSignalsReport(signals) {
366
616
  ` - frameworks : ${(signals.styling?.cssFrameworks || []).join(", ") || "none detected"}`,
367
617
  ` - style files: ${signals.styling?.styleFileCount ?? 0}`,
368
618
  ` - tokens : ${(signals.styling?.designTokens || []).slice(0, 8).join(", ") || "none detected"}`,
619
+ "Development profile",
620
+ "-".repeat(56),
621
+ ` - language : ${signals.developmentProfile?.language || "unknown"} (auto: ${signals.developmentProfile?.detected?.language || "unknown"})`,
622
+ ` - framework : ${signals.developmentProfile?.framework || "unknown"} (auto: ${signals.developmentProfile?.detected?.framework || "unknown"})`,
623
+ ` - project type: ${signals.developmentProfile?.projectType || "unknown"} (auto: ${signals.developmentProfile?.detected?.projectType || "unknown"})`,
624
+ "API calls",
625
+ "-".repeat(56),
626
+ ` - total calls : ${signals.apiCalls?.totalCalls ?? 0}`,
627
+ ` - by method : ${Object.entries(signals.apiCalls?.byMethod || {}).map(([k, v]) => `${k}:${v}`).join(", ") || "none"}`,
628
+ ...(signals.apiCalls?.calls || []).slice(0, 6).map((c) => ` - ${c.method} ${c.endpointPattern} [${c.style}] (${c.sourceFile})`),
629
+ ...((signals.apiCalls?.calls || []).length > 6
630
+ ? [` - ... +${(signals.apiCalls?.calls || []).length - 6} more`]
631
+ : []),
369
632
  "=".repeat(56),
370
633
  ].join("\n");
371
634
  }
@@ -423,6 +686,13 @@ export function writeAdoptionBaseline(infernoDir, policyId, capabilities, signal
423
686
  externalLibraries: signals.externalLibraries || [],
424
687
  uiLayout: signals.uiLayout || { layoutType: "unknown", usesGrid: false, usesFlex: false, sections: [] },
425
688
  styling: signals.styling || { cssFrameworks: [], styleFileCount: 0, styleFilesSample: [], designTokens: [] },
689
+ developmentProfile: signals.developmentProfile || {
690
+ language: "unknown",
691
+ framework: "unknown",
692
+ projectType: "unknown",
693
+ detected: { language: "unknown", framework: "unknown", projectType: "unknown" },
694
+ },
695
+ apiCalls: signals.apiCalls || { totalCalls: 0, byMethod: {}, calls: [] },
426
696
  };
427
697
  fs.writeFileSync(path.join(infernoDir, "adoption_profile.json"), JSON.stringify(profile, null, 2) + "\n");
428
698
  }
@@ -27,6 +27,14 @@ function ask(rl, question, defaultVal = "") {
27
27
  });
28
28
  }
29
29
 
30
+ function getArgValue(args, ...flags) {
31
+ for (const flag of flags) {
32
+ const i = args.indexOf(flag);
33
+ if (i !== -1 && args[i + 1] && !args[i + 1].startsWith("-")) return args[i + 1];
34
+ }
35
+ return null;
36
+ }
37
+
30
38
  function copyFile(src, dst, force, silent = false) {
31
39
  if (fs.existsSync(dst) && !force) {
32
40
  if (!silent) warn("Skipped (exists): " + path.relative(process.cwd(), dst));
@@ -144,6 +152,9 @@ export async function initCommand(args) {
144
152
  const reportJson = args.includes("--report-json");
145
153
  const reportJsonOnly = args.includes("--report-json-only");
146
154
  const reportHumanOnly = args.includes("--report-human-only");
155
+ const langOverride = getArgValue(args, "--lang");
156
+ const frameworkOverride = getArgValue(args, "--framework");
157
+ const projectTypeOverride = getArgValue(args, "--project-type");
147
158
  const silent = reportJsonOnly;
148
159
 
149
160
  if (reportJsonOnly && reportHumanOnly) {
@@ -173,7 +184,23 @@ export async function initCommand(args) {
173
184
  let capabilities = defaultCaps.split(",").map(c => c.trim());
174
185
 
175
186
  if (adopt) {
176
- const signals = discoverProjectSignals(cwd);
187
+ const profileOverrides = {
188
+ language: langOverride || undefined,
189
+ framework: frameworkOverride || undefined,
190
+ projectType: projectTypeOverride || undefined,
191
+ };
192
+ let signals = discoverProjectSignals(cwd, profileOverrides);
193
+ if (!yes && !reportJsonOnly) {
194
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
195
+ const profile = signals.developmentProfile || {};
196
+ const detected = profile.detected || {};
197
+ console.log(gray(" Review inferred development stack (press Enter to accept detected values)\n"));
198
+ const language = await ask(rl, "Language", profile.language || detected.language || "unknown");
199
+ const framework = await ask(rl, "Framework", profile.framework || detected.framework || "unknown");
200
+ const projectType = await ask(rl, "Project type", profile.projectType || detected.projectType || "unknown");
201
+ rl.close();
202
+ signals = discoverProjectSignals(cwd, { language, framework, projectType });
203
+ }
177
204
  const inferred = signals.capabilities;
178
205
  const summarized = summarizeCapabilities(inferred);
179
206
  if (reportJsonOnly) {
@@ -188,6 +215,8 @@ export async function initCommand(args) {
188
215
  externalLibraries: signals.externalLibraries,
189
216
  uiLayout: signals.uiLayout,
190
217
  styling: signals.styling,
218
+ developmentProfile: signals.developmentProfile,
219
+ apiCalls: signals.apiCalls,
191
220
  },
192
221
  null,
193
222
  2
@@ -211,6 +240,8 @@ export async function initCommand(args) {
211
240
  externalLibraries: signals.externalLibraries,
212
241
  uiLayout: signals.uiLayout,
213
242
  styling: signals.styling,
243
+ developmentProfile: signals.developmentProfile,
244
+ apiCalls: signals.apiCalls,
214
245
  },
215
246
  null,
216
247
  2
@@ -240,7 +271,11 @@ export async function initCommand(args) {
240
271
  id,
241
272
  title: id.replace(/([A-Z])/g, " $1").trim(),
242
273
  }));
243
- const signals = discoverProjectSignals(cwd);
274
+ const signals = discoverProjectSignals(cwd, {
275
+ language: langOverride || undefined,
276
+ framework: frameworkOverride || undefined,
277
+ projectType: projectTypeOverride || undefined,
278
+ });
244
279
  writeAdoptionBaseline(infernoDir, policyId, capDetails, signals);
245
280
  if (!silent) {
246
281
  ok("Created: " + cyan("inferno/contract.json"));
@@ -271,6 +306,22 @@ export async function initCommand(args) {
271
306
 
272
307
  upsertScripts(cwd, silent);
273
308
 
309
+ if (adopt) {
310
+ const statePath = path.join(infernoDir, "context-state.json");
311
+ let state = {};
312
+ try {
313
+ state = JSON.parse(fs.readFileSync(statePath, "utf8"));
314
+ } catch {}
315
+ const signals = discoverProjectSignals(cwd, {
316
+ language: langOverride || undefined,
317
+ framework: frameworkOverride || undefined,
318
+ projectType: projectTypeOverride || undefined,
319
+ });
320
+ state.stack = signals.developmentProfile;
321
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
322
+ if (!silent) ok("Created: " + cyan("inferno/context-state.json"));
323
+ }
324
+
274
325
  if (!silent) {
275
326
  done("infernoflow initialized!");
276
327
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.10.4",
3
+ "version": "0.10.6",
4
4
  "description": "The forge for liquid code — keep capabilities, contracts, and docs in sync.",
5
5
  "type": "module",
6
6
  "bin": {