infernoflow 0.32.6 → 0.32.8
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.
|
@@ -102,7 +102,9 @@ export async function checkCommand(args) {
|
|
|
102
102
|
console.log(JSON.stringify({ ok: false, errors, warnings }, null, 2));
|
|
103
103
|
process.exit(1);
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
// Support both bare array format (written by scan) and { capabilities: [...] } object format
|
|
106
|
+
const regArray = Array.isArray(registry) ? registry : (registry.capabilities || []);
|
|
107
|
+
const registryIds = new Set(regArray.map(c => c?.id).filter(Boolean));
|
|
106
108
|
|
|
107
109
|
const missingInRegistry = caps.filter(c => !registryIds.has(c));
|
|
108
110
|
if (missingInRegistry.length > 0) {
|
|
@@ -127,9 +129,12 @@ export async function checkCommand(args) {
|
|
|
127
129
|
if (!jsonOut) ok(`${scenarioFiles.length} scenario file(s) found`);
|
|
128
130
|
|
|
129
131
|
if (uncovered.length > 0 && requireCoverage) {
|
|
132
|
+
// Scenario coverage is advisory — missing coverage is a warning, not a hard error.
|
|
133
|
+
// This allows `infernoflow_apply` to add new capabilities without immediately
|
|
134
|
+
// requiring scenarios (which can't exist for brand-new capabilities yet).
|
|
130
135
|
uncovered.forEach(c => {
|
|
131
|
-
if (!jsonOut)
|
|
132
|
-
|
|
136
|
+
if (!jsonOut) warn(`"${c}" has no scenario coverage`, `Add to capabilitiesCovered in a scenario file`);
|
|
137
|
+
warnings.push(`"${c}" uncovered`);
|
|
133
138
|
});
|
|
134
139
|
} else if (!jsonOut) {
|
|
135
140
|
ok(`All capabilities covered by scenarios`);
|
|
@@ -343,7 +343,7 @@ export async function explainCommand(rawArgs) {
|
|
|
343
343
|
|
|
344
344
|
if (!jsonMode) {
|
|
345
345
|
console.log(gray(`\n infernoflow explain → ${bold(arg)}`));
|
|
346
|
-
console.log(gray(` Found ${capIdsToExplain.length} mapped
|
|
346
|
+
console.log(gray(` Found ${capIdsToExplain.length} mapped ${capIdsToExplain.length > 1 ? "capabilities" : "capability"}: ${capIdsToExplain.join(", ")}`));
|
|
347
347
|
console.log();
|
|
348
348
|
}
|
|
349
349
|
} else {
|
|
@@ -48,7 +48,8 @@ function buildGraph(scanCaps, allCaps) {
|
|
|
48
48
|
const reverse = {};
|
|
49
49
|
|
|
50
50
|
// Build function → capId index
|
|
51
|
-
|
|
51
|
+
// Use Map (not plain object) to avoid collisions with inherited properties like toString, constructor, etc.
|
|
52
|
+
const funcIndex = new Map(); // functionName → capId
|
|
52
53
|
for (const entry of scanCaps) {
|
|
53
54
|
const capFull = allCaps.find(c => c.id === entry.id) || {};
|
|
54
55
|
nodes[entry.id] = {
|
|
@@ -66,8 +67,8 @@ function buildGraph(scanCaps, allCaps) {
|
|
|
66
67
|
|
|
67
68
|
for (const fn of (entry.codeAnalysis?.functions || [])) {
|
|
68
69
|
const bare = fn.replace(/\(\)$/, "");
|
|
69
|
-
funcIndex
|
|
70
|
-
funcIndex
|
|
70
|
+
funcIndex.set(bare, entry.id);
|
|
71
|
+
funcIndex.set(bare.toLowerCase(), entry.id);
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -75,7 +76,7 @@ function buildGraph(scanCaps, allCaps) {
|
|
|
75
76
|
for (const [capId, node] of Object.entries(nodes)) {
|
|
76
77
|
for (const call of node.calls) {
|
|
77
78
|
const bare = call.replace(/\(\)$/, "");
|
|
78
|
-
const target = funcIndex
|
|
79
|
+
const target = funcIndex.get(bare) || funcIndex.get(bare.toLowerCase());
|
|
79
80
|
if (target && target !== capId) {
|
|
80
81
|
edges[capId].add(target);
|
|
81
82
|
reverse[target].add(capId);
|
|
@@ -462,12 +462,18 @@ export async function scanCommand(rawArgs) {
|
|
|
462
462
|
process.exit(1);
|
|
463
463
|
}
|
|
464
464
|
let capabilities;
|
|
465
|
+
let capsFileIsObject = false; // track original format so we can write it back correctly
|
|
466
|
+
let capsFileWrapper = null;
|
|
465
467
|
try { capabilities = JSON.parse(fs.readFileSync(capsPath, "utf8")); }
|
|
466
468
|
catch (e) { console.error(red("✗ Failed to parse capabilities.json: " + e.message)); process.exit(1); }
|
|
467
469
|
|
|
468
470
|
if (!Array.isArray(capabilities)) {
|
|
469
471
|
// handle object format { capabilities: [...] }
|
|
470
|
-
if (capabilities.capabilities)
|
|
472
|
+
if (capabilities.capabilities) {
|
|
473
|
+
capsFileIsObject = true;
|
|
474
|
+
capsFileWrapper = capabilities; // preserve other top-level keys
|
|
475
|
+
capabilities = capabilities.capabilities;
|
|
476
|
+
}
|
|
471
477
|
else { console.error(red("✗ Unexpected capabilities.json format.")); process.exit(1); }
|
|
472
478
|
}
|
|
473
479
|
|
|
@@ -572,7 +578,11 @@ export async function scanCommand(rawArgs) {
|
|
|
572
578
|
});
|
|
573
579
|
|
|
574
580
|
if (changed > 0) {
|
|
575
|
-
|
|
581
|
+
// Preserve the original file format: object wrapper or bare array
|
|
582
|
+
const toWrite = capsFileIsObject
|
|
583
|
+
? { ...capsFileWrapper, capabilities: updatedCaps }
|
|
584
|
+
: updatedCaps;
|
|
585
|
+
fs.writeFileSync(capsPath, JSON.stringify(toWrite, null, 2));
|
|
576
586
|
console.log(gray(` Updated ${changed} capability entries in capabilities.json`));
|
|
577
587
|
}
|
|
578
588
|
|