infernoflow 0.32.8 → 0.32.9

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.
Files changed (78) hide show
  1. package/dist/bin/infernoflow.mjs +84 -255
  2. package/dist/lib/adopters/angular.mjs +1 -128
  3. package/dist/lib/adopters/css.mjs +1 -111
  4. package/dist/lib/adopters/react.mjs +1 -104
  5. package/dist/lib/ai/ideDetection.mjs +1 -31
  6. package/dist/lib/ai/localProvider.mjs +1 -88
  7. package/dist/lib/ai/providerRouter.mjs +2 -295
  8. package/dist/lib/commands/adopt.mjs +20 -869
  9. package/dist/lib/commands/adoptWizard.mjs +9 -320
  10. package/dist/lib/commands/agent.mjs +5 -191
  11. package/dist/lib/commands/ai.mjs +2 -407
  12. package/dist/lib/commands/audit.mjs +13 -300
  13. package/dist/lib/commands/changelog.mjs +26 -594
  14. package/dist/lib/commands/check.mjs +3 -184
  15. package/dist/lib/commands/ci.mjs +3 -208
  16. package/dist/lib/commands/claudeMd.mjs +25 -130
  17. package/dist/lib/commands/cloud.mjs +5 -521
  18. package/dist/lib/commands/context.mjs +31 -287
  19. package/dist/lib/commands/coverage.mjs +2 -282
  20. package/dist/lib/commands/dashboard.mjs +123 -635
  21. package/dist/lib/commands/demo.mjs +8 -465
  22. package/dist/lib/commands/diff.mjs +5 -274
  23. package/dist/lib/commands/docGate.mjs +2 -81
  24. package/dist/lib/commands/doctor.mjs +3 -321
  25. package/dist/lib/commands/explain.mjs +8 -438
  26. package/dist/lib/commands/export.mjs +10 -239
  27. package/dist/lib/commands/generateSkills.mjs +38 -163
  28. package/dist/lib/commands/graph.mjs +203 -321
  29. package/dist/lib/commands/health.mjs +2 -309
  30. package/dist/lib/commands/impact.mjs +2 -325
  31. package/dist/lib/commands/implement.mjs +7 -103
  32. package/dist/lib/commands/init.mjs +23 -475
  33. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  34. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  35. package/dist/lib/commands/link.mjs +2 -342
  36. package/dist/lib/commands/monorepo.mjs +4 -428
  37. package/dist/lib/commands/notify.mjs +4 -258
  38. package/dist/lib/commands/onboard.mjs +4 -296
  39. package/dist/lib/commands/prComment.mjs +2 -361
  40. package/dist/lib/commands/prImpact.mjs +2 -157
  41. package/dist/lib/commands/publish.mjs +15 -316
  42. package/dist/lib/commands/report.mjs +28 -272
  43. package/dist/lib/commands/review.mjs +9 -223
  44. package/dist/lib/commands/run.mjs +8 -336
  45. package/dist/lib/commands/scaffold.mjs +54 -419
  46. package/dist/lib/commands/scan.mjs +5 -558
  47. package/dist/lib/commands/scout.mjs +2 -291
  48. package/dist/lib/commands/setup.mjs +5 -310
  49. package/dist/lib/commands/share.mjs +13 -196
  50. package/dist/lib/commands/snapshot.mjs +3 -383
  51. package/dist/lib/commands/stability.mjs +2 -293
  52. package/dist/lib/commands/status.mjs +4 -172
  53. package/dist/lib/commands/suggest.mjs +21 -563
  54. package/dist/lib/commands/syncAuto.mjs +1 -96
  55. package/dist/lib/commands/synthesize.mjs +10 -228
  56. package/dist/lib/commands/teamSync.mjs +2 -388
  57. package/dist/lib/commands/test.mjs +6 -363
  58. package/dist/lib/commands/version.mjs +2 -282
  59. package/dist/lib/commands/vibe.mjs +7 -357
  60. package/dist/lib/commands/watch.mjs +4 -203
  61. package/dist/lib/commands/why.mjs +4 -358
  62. package/dist/lib/cursorHooksInstall.mjs +1 -60
  63. package/dist/lib/draftToolingInstall.mjs +7 -68
  64. package/dist/lib/git/detect-drift.mjs +4 -208
  65. package/dist/lib/learning/adapt.mjs +6 -101
  66. package/dist/lib/learning/observe.mjs +1 -119
  67. package/dist/lib/learning/patternDetector.mjs +1 -298
  68. package/dist/lib/learning/profile.mjs +2 -279
  69. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  70. package/dist/lib/templates/index.mjs +1 -131
  71. package/dist/lib/ui/errors.mjs +1 -142
  72. package/dist/lib/ui/output.mjs +6 -72
  73. package/dist/lib/ui/prompts.mjs +6 -147
  74. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  75. package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
  76. package/dist/templates/github-app/GITHUB_APP.md +67 -0
  77. package/dist/templates/github-app/app-manifest.json +20 -0
  78. package/package.json +1 -1
@@ -1,438 +1,8 @@
1
- /**
2
- * infernoflow explain
3
- *
4
- * AI-generated narrative about a capability what it does, why it exists,
5
- * what's risky about it, and what to test before changing it.
6
- *
7
- * Synthesises: stability level, git history, scenarios, callers, services,
8
- * source files — then calls the AI provider for a 3-5 sentence human narrative.
9
- *
10
- * Usage:
11
- * infernoflow explain user-auth
12
- * infernoflow explain payment-process --dry-run (print prompt only)
13
- * infernoflow explain user-auth --json
14
- */
15
-
16
- import * as fs from "node:fs";
17
- import * as path from "node:path";
18
- import { execSync } from "node:child_process";
19
- import { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
20
-
21
- // ── helpers ───────────────────────────────────────────────────────────────────
22
-
23
- function loadJson(p) {
24
- try { return JSON.parse(fs.readFileSync(p, "utf8")); }
25
- catch { return null; }
26
- }
27
-
28
- function runGit(cmd, cwd) {
29
- try {
30
- return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe","pipe","pipe"] }).trim();
31
- } catch { return ""; }
32
- }
33
-
34
- const LEVEL_ICON = { frozen: "🧊", stable: "〰️ ", experimental: "🌊" };
35
- const LEVEL_COLOR = { frozen: red, stable: yellow, experimental: green };
36
-
37
- function stability(cap) { return cap?.stability || "experimental"; }
38
-
39
- // ── git helpers ───────────────────────────────────────────────────────────────
40
-
41
- function getFirstCommit(filePath, cwd) {
42
- if (!filePath) return null;
43
- const rel = path.relative(cwd, path.resolve(cwd, filePath));
44
- const log = runGit(
45
- `git log --follow --format="%h|%aI|%ae|%s" -- ${JSON.stringify(rel)}`, cwd
46
- );
47
- if (!log) return null;
48
- const lines = log.split("\n").filter(Boolean);
49
- if (!lines.length) return null;
50
- const [hash, date, author, ...subjectParts] = lines[lines.length - 1].split("|");
51
- return {
52
- hash: hash?.trim(),
53
- date: date?.trim() ? new Date(date.trim()).toLocaleDateString() : "",
54
- author: author?.trim(),
55
- subject: subjectParts.join("|").trim(),
56
- };
57
- }
58
-
59
- function getRecentHistory(filePath, cwd, limit = 5) {
60
- if (!filePath) return [];
61
- const rel = path.relative(cwd, path.resolve(cwd, filePath));
62
- const log = runGit(
63
- `git log --follow --format="%h|%aI|%ae|%s" -${limit} -- ${JSON.stringify(rel)}`, cwd
64
- );
65
- if (!log) return [];
66
- return log.split("\n").filter(Boolean).map(line => {
67
- const [hash, date, author, ...subjectParts] = line.split("|");
68
- return {
69
- hash: hash?.trim(),
70
- date: date?.trim() ? new Date(date.trim()).toLocaleDateString() : "",
71
- author: author?.trim(),
72
- subject: subjectParts.join("|").trim(),
73
- };
74
- });
75
- }
76
-
77
- // ── scenario finder ───────────────────────────────────────────────────────────
78
-
79
- function findScenarios(capId, infernoDir) {
80
- const dir = path.join(infernoDir, "scenarios");
81
- if (!fs.existsSync(dir)) return [];
82
- const found = [];
83
- for (const f of fs.readdirSync(dir)) {
84
- if (!f.endsWith(".json")) continue;
85
- try {
86
- const s = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
87
- const covered = s.capabilitiesCovered || s.capabilities || [];
88
- if (covered.some(c => c.toLowerCase() === capId.toLowerCase())) {
89
- found.push(s);
90
- }
91
- } catch {}
92
- }
93
- return found;
94
- }
95
-
96
- // ── prompt builder ────────────────────────────────────────────────────────────
97
-
98
- function buildPrompt(capId, cap, scanEntry, graph, allCaps, scenarios, firstCommit, recentHistory) {
99
- const level = stability(cap);
100
- const files = scanEntry?.codeAnalysis?.sourceFiles || [];
101
- const functions = scanEntry?.codeAnalysis?.functions || [];
102
- const services = scanEntry?.codeAnalysis?.services || [];
103
- const throws_ = scanEntry?.codeAnalysis?.throws || [];
104
- const calls = scanEntry?.codeAnalysis?.calls || [];
105
- const deps = graph?.deps?.[capId] || [];
106
- const dependents = graph?.dependents?.[capId] || [];
107
-
108
- const lines = [
109
- `You are a senior engineer writing a brief, plain-English explanation of a software capability for a teammate who is about to modify it.`,
110
- ``,
111
- `Write 3–5 sentences covering:`,
112
- ` 1. What this capability does and why it exists`,
113
- ` 2. The most important thing to know before changing it (stability, callers, risk)`,
114
- ` 3. What to test or verify after any modification`,
115
- ``,
116
- `Be concrete and direct. Do not use bullet points. Do not repeat the capability ID verbatim in every sentence.`,
117
- ``,
118
- `=== Capability: ${capId} ===`,
119
- `Name: ${cap.name || cap.title || capId}`,
120
- `Description: ${cap.description || "(none provided)"}`,
121
- `Stability: ${level}`,
122
- ];
123
-
124
- if (files.length) lines.push(`Source files: ${files.join(", ")}`);
125
- if (functions.length) lines.push(`Functions: ${functions.join(", ")}`);
126
- if (services.length) lines.push(`External services used: ${services.join(", ")}`);
127
- if (throws_.length) lines.push(`Can throw: ${throws_.join(", ")}`);
128
- if (calls.length) lines.push(`Internal calls: ${calls.join(", ")}`);
129
-
130
- if (deps.length) {
131
- const depDetails = deps.map(d => {
132
- const dc = allCaps.find(c => c.id === d);
133
- return `${d} (${stability(dc)})`;
134
- });
135
- lines.push(`Calls capabilities: ${depDetails.join(", ")}`);
136
- }
137
-
138
- if (dependents.length) {
139
- const depDetails = dependents.map(d => {
140
- const dc = allCaps.find(c => c.id === d);
141
- return `${d} (${stability(dc)})`;
142
- });
143
- lines.push(`Called by capabilities: ${depDetails.join(", ")}`);
144
- }
145
-
146
- if (scenarios.length) {
147
- lines.push(`Test scenarios: ${scenarios.map(s => s.scenarioId || s.description || "unnamed").join(", ")}`);
148
- } else {
149
- lines.push(`Test scenarios: none registered`);
150
- }
151
-
152
- if (firstCommit) {
153
- lines.push(`First introduced: ${firstCommit.date} by ${firstCommit.author} — "${firstCommit.subject}"`);
154
- }
155
-
156
- if (recentHistory.length) {
157
- lines.push(`Recent changes:`);
158
- for (const h of recentHistory.slice(0, 3)) {
159
- lines.push(` ${h.date} — ${h.subject}`);
160
- }
161
- }
162
-
163
- if (level === "frozen") {
164
- lines.push(`IMPORTANT: This capability is FROZEN. Any modification requires explicit approval.`);
165
- } else if (level === "stable") {
166
- lines.push(`NOTE: This capability is STABLE. Prefer additive changes; avoid breaking the public API.`);
167
- }
168
-
169
- return lines.join("\n");
170
- }
171
-
172
- // ── AI caller ─────────────────────────────────────────────────────────────────
173
-
174
- async function callAI(prompt, cwd) {
175
- try {
176
- const { callAI: call } = await import("../ai/providerRouter.mjs");
177
- return await call(prompt, cwd);
178
- } catch {
179
- // Provider not available — return a structured fallback
180
- return null;
181
- }
182
- }
183
-
184
- // ── fallback narrative (no AI) ────────────────────────────────────────────────
185
-
186
- function buildFallback(capId, cap, scanEntry, graph, allCaps, scenarios) {
187
- const level = stability(cap);
188
- const name = cap.name || cap.title || capId;
189
- const services = scanEntry?.codeAnalysis?.services || [];
190
- const dependents = graph?.dependents?.[capId] || [];
191
- const deps = graph?.deps?.[capId] || [];
192
-
193
- const parts = [];
194
-
195
- // What it does
196
- if (cap.description && cap.description !== "(none provided)") {
197
- parts.push(`${name} — ${cap.description}.`);
198
- } else {
199
- parts.push(`${name} handles the ${capId} flow within this system.`);
200
- }
201
-
202
- // External services
203
- if (services.length) {
204
- parts.push(`It integrates with ${services.join(" and ")}.`);
205
- }
206
-
207
- // Dependencies
208
- if (deps.length) {
209
- parts.push(`It depends on: ${deps.join(", ")}.`);
210
- }
211
- if (dependents.length) {
212
- const frozenDeps = dependents.filter(d => stability(allCaps.find(c => c.id === d)) === "frozen");
213
- if (frozenDeps.length) {
214
- parts.push(`⚠️ ${frozenDeps.join(", ")} depend${frozenDeps.length === 1 ? "s" : ""} on this — changing it may break frozen capabilities.`);
215
- } else {
216
- parts.push(`${dependents.join(", ")} depend${dependents.length === 1 ? "s" : ""} on this capability.`);
217
- }
218
- }
219
-
220
- // Stability advice
221
- if (level === "frozen") {
222
- parts.push(`This capability is FROZEN — do not modify without explicit instruction.`);
223
- } else if (level === "stable") {
224
- parts.push(`This capability is stable — prefer additive changes and avoid breaking the existing API surface.`);
225
- } else {
226
- parts.push(`This capability is experimental — free to refactor as needed.`);
227
- }
228
-
229
- // Test advice
230
- if (scenarios.length) {
231
- parts.push(`Before shipping changes, run the registered scenarios: ${scenarios.map(s => s.scenarioId || "unnamed").join(", ")}.`);
232
- } else {
233
- parts.push(`No test scenarios are registered — consider adding one before making changes.`);
234
- }
235
-
236
- return parts.join(" ");
237
- }
238
-
239
- // ── printer ───────────────────────────────────────────────────────────────────
240
-
241
- function printExplain(capId, cap, narrative, provider, dryRun) {
242
- const level = stability(cap);
243
- const icon = LEVEL_ICON[level] || "🌊";
244
- const color = LEVEL_COLOR[level] || green;
245
-
246
- console.log();
247
- console.log(bold(` ${icon} ${color(capId)}`));
248
- if (cap.name || cap.title) console.log(gray(` ${cap.name || cap.title}`));
249
- console.log();
250
-
251
- if (dryRun) {
252
- console.log(yellow(" [dry-run] Prompt only — no AI call made"));
253
- console.log();
254
- return;
255
- }
256
-
257
- // Word-wrap narrative at ~80 chars
258
- const words = narrative.split(" ");
259
- let line = " ";
260
- const lines = [];
261
- for (const word of words) {
262
- if (line.length + word.length > 82) { lines.push(line); line = " " + word; }
263
- else line += (line === " " ? "" : " ") + word;
264
- }
265
- if (line.trim()) lines.push(line);
266
-
267
- for (const l of lines) console.log(l);
268
- console.log();
269
-
270
- if (provider) {
271
- console.log(gray(` ── via ${provider}`));
272
- } else {
273
- console.log(gray(" ── structural summary (no AI provider configured)"));
274
- console.log(` ${yellow("💡")} ${gray("For richer AI narratives:")} ${cyan("infernoflow ai setup")}`);
275
- }
276
- console.log();
277
- }
278
-
279
- // ── entry point ───────────────────────────────────────────────────────────────
280
-
281
- export async function explainCommand(rawArgs) {
282
- const args = (rawArgs || []).slice(1);
283
- const dryRun = args.includes("--dry-run");
284
- const jsonMode = args.includes("--json");
285
-
286
- const arg = args.find(a => !a.startsWith("--"));
287
-
288
- if (!arg) {
289
- console.error(red("✗ Usage: infernoflow explain <capability-id|file-path> [--dry-run] [--json]"));
290
- console.error(gray(" Examples:"));
291
- console.error(gray(" infernoflow explain CreateTask"));
292
- console.error(gray(" infernoflow explain src/components/TaskComposer.jsx"));
293
- process.exit(1);
294
- }
295
-
296
- const cwd = process.cwd();
297
- const infernoDir = path.join(cwd, "inferno");
298
-
299
- // Load capabilities
300
- let allCaps = [];
301
- const rawCaps = loadJson(path.join(infernoDir, "capabilities.json"));
302
- if (rawCaps) allCaps = Array.isArray(rawCaps) ? rawCaps : (rawCaps.capabilities || []);
303
-
304
- // Detect if arg is a file path and resolve to capability IDs
305
- const isFilePath = arg.includes("/") || arg.includes("\\") || /\.\w+$/.test(arg);
306
-
307
- let capIdsToExplain = [];
308
-
309
- if (isFilePath) {
310
- // Look up which capabilities are mapped to this file via scan.json
311
- const scanData = loadJson(path.join(infernoDir, "scan.json"));
312
- const scanCaps = scanData?.capabilities || [];
313
- const relTarget = path.relative(cwd, path.resolve(cwd, arg));
314
-
315
- for (const entry of scanCaps) {
316
- const sourceFiles = entry.codeAnalysis?.sourceFiles || [];
317
- const matched = sourceFiles.some(f =>
318
- f === relTarget || f.endsWith(relTarget) || relTarget.endsWith(f)
319
- );
320
- if (matched) capIdsToExplain.push(entry.id);
321
- }
322
-
323
- if (capIdsToExplain.length === 0) {
324
- // scan.json may not exist yet — try capability-map.json
325
- const capMap = loadJson(path.join(infernoDir, "capability-map.json"));
326
- if (capMap) {
327
- const relNorm = relTarget.replace(/\\/g, "/");
328
- for (const [capId, files] of Object.entries(capMap)) {
329
- const fileList = Array.isArray(files) ? files : [files];
330
- if (fileList.some(f => f === relNorm || f.endsWith(relNorm) || relNorm.endsWith(f))) {
331
- capIdsToExplain.push(capId);
332
- }
333
- }
334
- }
335
- }
336
-
337
- if (capIdsToExplain.length === 0) {
338
- console.error(red(`✗ No capabilities found mapped to "${arg}"`));
339
- console.error(gray(" Run: infernoflow scan — to build the source-file → capability map"));
340
- console.error(gray(" Then retry: infernoflow explain " + arg));
341
- process.exit(1);
342
- }
343
-
344
- if (!jsonMode) {
345
- console.log(gray(`\n infernoflow explain → ${bold(arg)}`));
346
- console.log(gray(` Found ${capIdsToExplain.length} mapped ${capIdsToExplain.length > 1 ? "capabilities" : "capability"}: ${capIdsToExplain.join(", ")}`));
347
- console.log();
348
- }
349
- } else {
350
- capIdsToExplain = [arg];
351
- }
352
-
353
- // Explain each capability
354
- const jsonResults = [];
355
- for (const capId of capIdsToExplain) {
356
- const cap = allCaps.find(c => c.id === capId);
357
- if (!cap) {
358
- if (!jsonMode) {
359
- console.error(red(`✗ Capability "${capId}" not found in capabilities.json`));
360
- console.error(gray(" Run: infernoflow stability — to list all capability IDs"));
361
- }
362
- continue;
363
- }
364
-
365
- // Load scan + graph for this cap
366
- const scanData = loadJson(path.join(infernoDir, "scan.json"));
367
- const graph = loadJson(path.join(infernoDir, "graph.json"));
368
- const scanEntry = scanData?.capabilities?.find(c => c.id === capId);
369
-
370
- // Git history
371
- const files = scanEntry?.codeAnalysis?.sourceFiles || [];
372
- const firstCommit = getFirstCommit(files[0], cwd);
373
- const recentHistory = getRecentHistory(files[0], cwd);
374
-
375
- // Scenarios
376
- const scenarios = findScenarios(capId, infernoDir);
377
-
378
- // Build prompt
379
- const prompt = buildPrompt(capId, cap, scanEntry, graph, allCaps, scenarios, firstCommit, recentHistory);
380
-
381
- if (dryRun && !jsonMode) {
382
- console.log(gray(` ── ${bold(capId)} ──────────────────────────────────────────────────────`));
383
- printExplain(capId, cap, "", null, true);
384
- if (capIdsToExplain.length === 1) {
385
- console.log(bold(" Prompt that would be sent to AI:"));
386
- console.log();
387
- console.log(prompt.split("\n").map(l => " " + l).join("\n"));
388
- }
389
- console.log();
390
- continue;
391
- }
392
-
393
- // Call AI
394
- let narrative = null;
395
- let provider = null;
396
-
397
- if (!dryRun) {
398
- try {
399
- const result = await callAI(prompt, cwd);
400
- if (result?.text) {
401
- narrative = result.text.trim();
402
- provider = result.provider;
403
- }
404
- } catch {}
405
- }
406
-
407
- if (!narrative) {
408
- narrative = buildFallback(capId, cap, scanEntry, graph, allCaps, scenarios);
409
- provider = null;
410
- }
411
-
412
- if (jsonMode) {
413
- jsonResults.push({
414
- capId,
415
- name: cap.name || cap.title,
416
- stability: stability(cap),
417
- narrative,
418
- provider: provider || "fallback",
419
- sourceFiles: files,
420
- scenarios: scenarios.map(s => s.scenarioId || s.description),
421
- firstCommit,
422
- });
423
- } else {
424
- if (!isFilePath) {
425
- console.log(gray(`\n infernoflow explain → ${bold(capId)}`));
426
- console.log(gray(" ──────────────────────────────────────────────────────────────"));
427
- }
428
- printExplain(capId, cap, narrative, provider, false);
429
- }
430
- }
431
-
432
- if (jsonMode) {
433
- const out = capIdsToExplain.length === 1 ? jsonResults[0] : jsonResults;
434
- console.log(JSON.stringify(out, null, 2));
435
- return;
436
- }
437
-
438
- }
1
+ import*as S from"node:fs";import*as b from"node:path";import{execSync as R}from"node:child_process";import{bold as I,cyan as W,gray as y,green as L,yellow as N,red as C}from"../ui/output.mjs";function T(n){try{return JSON.parse(S.readFileSync(n,"utf8"))}catch{return null}}function k(n,e){try{return R(n,{cwd:e,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}}const P={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},z={frozen:C,stable:N,experimental:L};function x(n){return n?.stability||"experimental"}function J(n,e){if(!n)return null;const l=b.relative(e,b.resolve(e,n)),a=k(`git log --follow --format="%h|%aI|%ae|%s" -- ${JSON.stringify(l)}`,e);if(!a)return null;const i=a.split(`
2
+ `).filter(Boolean);if(!i.length)return null;const[r,c,u,...d]=i[i.length-1].split("|");return{hash:r?.trim(),date:c?.trim()?new Date(c.trim()).toLocaleDateString():"",author:u?.trim(),subject:d.join("|").trim()}}function B(n,e,l=5){if(!n)return[];const a=b.relative(e,b.resolve(e,n)),i=k(`git log --follow --format="%h|%aI|%ae|%s" -${l} -- ${JSON.stringify(a)}`,e);return i?i.split(`
3
+ `).filter(Boolean).map(r=>{const[c,u,d,...p]=r.split("|");return{hash:c?.trim(),date:u?.trim()?new Date(u.trim()).toLocaleDateString():"",author:d?.trim(),subject:p.join("|").trim()}}):[]}function M(n,e){const l=b.join(e,"scenarios");if(!S.existsSync(l))return[];const a=[];for(const i of S.readdirSync(l))if(i.endsWith(".json"))try{const r=JSON.parse(S.readFileSync(b.join(l,i),"utf8"));(r.capabilitiesCovered||r.capabilities||[]).some(u=>u.toLowerCase()===n.toLowerCase())&&a.push(r)}catch{}return a}function _(n,e,l,a,i,r,c,u){const d=x(e),p=l?.codeAnalysis?.sourceFiles||[],f=l?.codeAnalysis?.functions||[],o=l?.codeAnalysis?.services||[],t=l?.codeAnalysis?.throws||[],m=l?.codeAnalysis?.calls||[],j=a?.deps?.[n]||[],v=a?.dependents?.[n]||[],s=["You are a senior engineer writing a brief, plain-English explanation of a software capability for a teammate who is about to modify it.","","Write 3\u20135 sentences covering:"," 1. What this capability does and why it exists"," 2. The most important thing to know before changing it (stability, callers, risk)"," 3. What to test or verify after any modification","","Be concrete and direct. Do not use bullet points. Do not repeat the capability ID verbatim in every sentence.","",`=== Capability: ${n} ===`,`Name: ${e.name||e.title||n}`,`Description: ${e.description||"(none provided)"}`,`Stability: ${d}`];if(p.length&&s.push(`Source files: ${p.join(", ")}`),f.length&&s.push(`Functions: ${f.join(", ")}`),o.length&&s.push(`External services used: ${o.join(", ")}`),t.length&&s.push(`Can throw: ${t.join(", ")}`),m.length&&s.push(`Internal calls: ${m.join(", ")}`),j.length){const h=j.map(g=>{const A=i.find(w=>w.id===g);return`${g} (${x(A)})`});s.push(`Calls capabilities: ${h.join(", ")}`)}if(v.length){const h=v.map(g=>{const A=i.find(w=>w.id===g);return`${g} (${x(A)})`});s.push(`Called by capabilities: ${h.join(", ")}`)}if(r.length?s.push(`Test scenarios: ${r.map(h=>h.scenarioId||h.description||"unnamed").join(", ")}`):s.push("Test scenarios: none registered"),c&&s.push(`First introduced: ${c.date} by ${c.author} \u2014 "${c.subject}"`),u.length){s.push("Recent changes:");for(const h of u.slice(0,3))s.push(` ${h.date} \u2014 ${h.subject}`)}return d==="frozen"?s.push("IMPORTANT: This capability is FROZEN. Any modification requires explicit approval."):d==="stable"&&s.push("NOTE: This capability is STABLE. Prefer additive changes; avoid breaking the public API."),s.join(`
4
+ `)}async function V(n,e){try{const{callAI:l}=await import("../ai/providerRouter.mjs");return await l(n,e)}catch{return null}}function Z(n,e,l,a,i,r){const c=x(e),u=e.name||e.title||n,d=l?.codeAnalysis?.services||[],p=a?.dependents?.[n]||[],f=a?.deps?.[n]||[],o=[];if(e.description&&e.description!=="(none provided)"?o.push(`${u} \u2014 ${e.description}.`):o.push(`${u} handles the ${n} flow within this system.`),d.length&&o.push(`It integrates with ${d.join(" and ")}.`),f.length&&o.push(`It depends on: ${f.join(", ")}.`),p.length){const t=p.filter(m=>x(i.find(j=>j.id===m))==="frozen");t.length?o.push(`\u26A0\uFE0F ${t.join(", ")} depend${t.length===1?"s":""} on this \u2014 changing it may break frozen capabilities.`):o.push(`${p.join(", ")} depend${p.length===1?"s":""} on this capability.`)}return c==="frozen"?o.push("This capability is FROZEN \u2014 do not modify without explicit instruction."):c==="stable"?o.push("This capability is stable \u2014 prefer additive changes and avoid breaking the existing API surface."):o.push("This capability is experimental \u2014 free to refactor as needed."),r.length?o.push(`Before shipping changes, run the registered scenarios: ${r.map(t=>t.scenarioId||"unnamed").join(", ")}.`):o.push("No test scenarios are registered \u2014 consider adding one before making changes."),o.join(" ")}function E(n,e,l,a,i){const r=x(e),c=P[r]||"\u{1F30A}",u=z[r]||L;if(console.log(),console.log(I(` ${c} ${u(n)}`)),(e.name||e.title)&&console.log(y(` ${e.name||e.title}`)),console.log(),i){console.log(N(" [dry-run] Prompt only \u2014 no AI call made")),console.log();return}const d=l.split(" ");let p=" ";const f=[];for(const o of d)p.length+o.length>82?(f.push(p),p=" "+o):p+=(p===" "?"":" ")+o;p.trim()&&f.push(p);for(const o of f)console.log(o);console.log(),a?console.log(y(` \u2500\u2500 via ${a}`)):(console.log(y(" \u2500\u2500 structural summary (no AI provider configured)")),console.log(` ${N("\u{1F4A1}")} ${y("For richer AI narratives:")} ${W("infernoflow ai setup")}`)),console.log()}async function H(n){const e=(n||[]).slice(1),l=e.includes("--dry-run"),a=e.includes("--json"),i=e.find(t=>!t.startsWith("--"));i||(console.error(C("\u2717 Usage: infernoflow explain <capability-id|file-path> [--dry-run] [--json]")),console.error(y(" Examples:")),console.error(y(" infernoflow explain CreateTask")),console.error(y(" infernoflow explain src/components/TaskComposer.jsx")),process.exit(1));const r=process.cwd(),c=b.join(r,"inferno");let u=[];const d=T(b.join(c,"capabilities.json"));d&&(u=Array.isArray(d)?d:d.capabilities||[]);const p=i.includes("/")||i.includes("\\")||/\.\w+$/.test(i);let f=[];if(p){const m=T(b.join(c,"scan.json"))?.capabilities||[],j=b.relative(r,b.resolve(r,i));for(const v of m)(v.codeAnalysis?.sourceFiles||[]).some(g=>g===j||g.endsWith(j)||j.endsWith(g))&&f.push(v.id);if(f.length===0){const v=T(b.join(c,"capability-map.json"));if(v){const s=j.replace(/\\/g,"/");for(const[h,g]of Object.entries(v))(Array.isArray(g)?g:[g]).some(w=>w===s||w.endsWith(s)||s.endsWith(w))&&f.push(h)}}f.length===0&&(console.error(C(`\u2717 No capabilities found mapped to "${i}"`)),console.error(y(" Run: infernoflow scan \u2014 to build the source-file \u2192 capability map")),console.error(y(" Then retry: infernoflow explain "+i)),process.exit(1)),a||(console.log(y(`
5
+ infernoflow explain \u2192 ${I(i)}`)),console.log(y(` Found ${f.length} mapped ${f.length>1?"capabilities":"capability"}: ${f.join(", ")}`)),console.log())}else f=[i];const o=[];for(const t of f){const m=u.find($=>$.id===t);if(!m){a||(console.error(C(`\u2717 Capability "${t}" not found in capabilities.json`)),console.error(y(" Run: infernoflow stability \u2014 to list all capability IDs")));continue}const j=T(b.join(c,"scan.json")),v=T(b.join(c,"graph.json")),s=j?.capabilities?.find($=>$.id===t),h=s?.codeAnalysis?.sourceFiles||[],g=J(h[0],r),A=B(h[0],r),w=M(t,c),O=_(t,m,s,v,u,w,g,A);if(l&&!a){console.log(y(` \u2500\u2500 ${I(t)} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`)),E(t,m,"",null,!0),f.length===1&&(console.log(I(" Prompt that would be sent to AI:")),console.log(),console.log(O.split(`
6
+ `).map($=>" "+$).join(`
7
+ `))),console.log();continue}let F=null,D=null;if(!l)try{const $=await V(O,r);$?.text&&(F=$.text.trim(),D=$.provider)}catch{}F||(F=Z(t,m,s,v,u,w),D=null),a?o.push({capId:t,name:m.name||m.title,stability:x(m),narrative:F,provider:D||"fallback",sourceFiles:h,scenarios:w.map($=>$.scenarioId||$.description),firstCommit:g}):(p||(console.log(y(`
8
+ infernoflow explain \u2192 ${I(t)}`)),console.log(y(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"))),E(t,m,F,D,!1))}if(a){const t=f.length===1?o[0]:o;console.log(JSON.stringify(t,null,2));return}}export{H as explainCommand};