infernoflow 0.32.5 → 0.32.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/dist/lib/commands/explain.mjs +124 -59
- package/package.json +1 -1
|
@@ -283,11 +283,13 @@ export async function explainCommand(rawArgs) {
|
|
|
283
283
|
const dryRun = args.includes("--dry-run");
|
|
284
284
|
const jsonMode = args.includes("--json");
|
|
285
285
|
|
|
286
|
-
const
|
|
286
|
+
const arg = args.find(a => !a.startsWith("--"));
|
|
287
287
|
|
|
288
|
-
if (!
|
|
289
|
-
console.error(red("✗ Usage: infernoflow explain <capability-id> [--dry-run] [--json]"));
|
|
290
|
-
console.error(gray("
|
|
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"));
|
|
291
293
|
process.exit(1);
|
|
292
294
|
}
|
|
293
295
|
|
|
@@ -299,75 +301,138 @@ export async function explainCommand(rawArgs) {
|
|
|
299
301
|
const rawCaps = loadJson(path.join(infernoDir, "capabilities.json"));
|
|
300
302
|
if (rawCaps) allCaps = Array.isArray(rawCaps) ? rawCaps : (rawCaps.capabilities || []);
|
|
301
303
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
console.error(red(`✗ Capability "${capId}" not found in capabilities.json`));
|
|
305
|
-
console.error(gray(" Run: infernoflow stability — to list all capability IDs"));
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
304
|
+
// Detect if arg is a file path and resolve to capability IDs
|
|
305
|
+
const isFilePath = arg.includes("/") || arg.includes("\\") || /\.\w+$/.test(arg);
|
|
308
306
|
|
|
309
|
-
|
|
310
|
-
const scanData = loadJson(path.join(infernoDir, "scan.json"));
|
|
311
|
-
const graph = loadJson(path.join(infernoDir, "graph.json"));
|
|
312
|
-
const scanEntry = scanData?.capabilities?.find(c => c.id === capId);
|
|
307
|
+
let capIdsToExplain = [];
|
|
313
308
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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));
|
|
318
314
|
|
|
319
|
-
|
|
320
|
-
|
|
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
|
+
}
|
|
321
322
|
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
}
|
|
324
336
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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 capability${capIdsToExplain.length > 1 ? "ies" : "y"}: ${capIdsToExplain.join(", ")}`));
|
|
347
|
+
console.log();
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
capIdsToExplain = [arg];
|
|
334
351
|
}
|
|
335
352
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
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
|
+
}
|
|
339
364
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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"));
|
|
346
388
|
}
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
}
|
|
349
411
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
}
|
|
354
430
|
}
|
|
355
431
|
|
|
356
432
|
if (jsonMode) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
name: cap.name || cap.title,
|
|
360
|
-
stability: stability(cap),
|
|
361
|
-
narrative,
|
|
362
|
-
provider: provider || "fallback",
|
|
363
|
-
sourceFiles: files,
|
|
364
|
-
scenarios: scenarios.map(s => s.scenarioId || s.description),
|
|
365
|
-
firstCommit,
|
|
366
|
-
}, null, 2));
|
|
433
|
+
const out = capIdsToExplain.length === 1 ? jsonResults[0] : jsonResults;
|
|
434
|
+
console.log(JSON.stringify(out, null, 2));
|
|
367
435
|
return;
|
|
368
436
|
}
|
|
369
437
|
|
|
370
|
-
console.log(gray(`\n infernoflow explain → ${bold(capId)}`));
|
|
371
|
-
console.log(gray(" ──────────────────────────────────────────────────────────────"));
|
|
372
|
-
printExplain(capId, cap, narrative, provider, false);
|
|
373
438
|
}
|