infernoflow 0.37.1 → 0.37.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/bin/infernoflow.mjs +29 -277
  3. package/dist/lib/adopters/angular.mjs +1 -128
  4. package/dist/lib/adopters/css.mjs +1 -111
  5. package/dist/lib/adopters/react.mjs +1 -104
  6. package/dist/lib/ai/ideDetection.mjs +1 -31
  7. package/dist/lib/ai/localProvider.mjs +1 -88
  8. package/dist/lib/ai/providerRouter.mjs +2 -295
  9. package/dist/lib/commands/adopt.mjs +20 -869
  10. package/dist/lib/commands/adoptWizard.mjs +9 -320
  11. package/dist/lib/commands/agent.mjs +5 -191
  12. package/dist/lib/commands/ai.mjs +2 -407
  13. package/dist/lib/commands/ask.mjs +4 -299
  14. package/dist/lib/commands/audit.mjs +13 -300
  15. package/dist/lib/commands/changelog.mjs +26 -594
  16. package/dist/lib/commands/check.mjs +3 -184
  17. package/dist/lib/commands/ci.mjs +3 -208
  18. package/dist/lib/commands/claudeMd.mjs +30 -135
  19. package/dist/lib/commands/cloud.mjs +10 -773
  20. package/dist/lib/commands/context.mjs +34 -346
  21. package/dist/lib/commands/coverage.mjs +2 -282
  22. package/dist/lib/commands/dashboard.mjs +123 -635
  23. package/dist/lib/commands/demo.mjs +8 -465
  24. package/dist/lib/commands/diff.mjs +5 -274
  25. package/dist/lib/commands/docGate.mjs +2 -81
  26. package/dist/lib/commands/doctor.mjs +3 -321
  27. package/dist/lib/commands/explain.mjs +8 -438
  28. package/dist/lib/commands/export.mjs +10 -239
  29. package/dist/lib/commands/feedback.mjs +12 -216
  30. package/dist/lib/commands/generateSkills.mjs +38 -163
  31. package/dist/lib/commands/graph.mjs +11 -378
  32. package/dist/lib/commands/health.mjs +2 -309
  33. package/dist/lib/commands/impact.mjs +2 -325
  34. package/dist/lib/commands/implement.mjs +7 -103
  35. package/dist/lib/commands/init.mjs +45 -631
  36. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  37. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  38. package/dist/lib/commands/link.mjs +2 -342
  39. package/dist/lib/commands/log.mjs +18 -248
  40. package/dist/lib/commands/monorepo.mjs +4 -428
  41. package/dist/lib/commands/notify.mjs +4 -258
  42. package/dist/lib/commands/onboard.mjs +4 -296
  43. package/dist/lib/commands/prComment.mjs +2 -361
  44. package/dist/lib/commands/prImpact.mjs +2 -157
  45. package/dist/lib/commands/publish.mjs +15 -316
  46. package/dist/lib/commands/recap.mjs +6 -380
  47. package/dist/lib/commands/report.mjs +28 -272
  48. package/dist/lib/commands/review.mjs +9 -223
  49. package/dist/lib/commands/run.mjs +8 -336
  50. package/dist/lib/commands/scaffold.mjs +54 -419
  51. package/dist/lib/commands/scan.mjs +11 -1118
  52. package/dist/lib/commands/scout.mjs +2 -291
  53. package/dist/lib/commands/setup.mjs +5 -310
  54. package/dist/lib/commands/share.mjs +13 -196
  55. package/dist/lib/commands/snapshot.mjs +3 -383
  56. package/dist/lib/commands/stability.mjs +2 -293
  57. package/dist/lib/commands/stats.mjs +5 -402
  58. package/dist/lib/commands/status.mjs +4 -172
  59. package/dist/lib/commands/suggest.mjs +21 -563
  60. package/dist/lib/commands/switch.mjs +13 -520
  61. package/dist/lib/commands/syncAuto.mjs +1 -96
  62. package/dist/lib/commands/synthesize.mjs +10 -228
  63. package/dist/lib/commands/teamSync.mjs +2 -388
  64. package/dist/lib/commands/test.mjs +6 -363
  65. package/dist/lib/commands/theme.mjs +18 -195
  66. package/dist/lib/commands/uninstall.mjs +13 -406
  67. package/dist/lib/commands/upgrade.mjs +20 -153
  68. package/dist/lib/commands/version.mjs +2 -282
  69. package/dist/lib/commands/vibe.mjs +7 -357
  70. package/dist/lib/commands/watch.mjs +4 -203
  71. package/dist/lib/commands/why.mjs +4 -358
  72. package/dist/lib/cursorHooksInstall.mjs +1 -60
  73. package/dist/lib/draftToolingInstall.mjs +7 -68
  74. package/dist/lib/git/detect-drift.mjs +4 -208
  75. package/dist/lib/learning/adapt.mjs +6 -101
  76. package/dist/lib/learning/observe.mjs +1 -119
  77. package/dist/lib/learning/patternDetector.mjs +1 -298
  78. package/dist/lib/learning/profile.mjs +2 -279
  79. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  80. package/dist/lib/telemetry.mjs +19 -269
  81. package/dist/lib/templates/index.mjs +1 -131
  82. package/dist/lib/theme/scanner.mjs +4 -343
  83. package/dist/lib/ui/errors.mjs +1 -142
  84. package/dist/lib/ui/output.mjs +6 -95
  85. package/dist/lib/ui/prompts.mjs +6 -147
  86. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  87. package/package.json +2 -4
  88. package/scripts/postinstall.js +2 -2
@@ -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};