59 lines
2.6 KiB
JavaScript
59 lines
2.6 KiB
JavaScript
import { readFile } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
import {
|
|
buildMcpInventory,
|
|
buildSkillInventory,
|
|
decodeInventory,
|
|
renderContractModule,
|
|
renderDocumentation,
|
|
validateInventorySchema,
|
|
validateInventories,
|
|
} from "./lib/capability-inventory.mjs";
|
|
|
|
const packageRoot = resolve(import.meta.dirname, "..");
|
|
const readInventory = async (name) => decodeInventory(await readFile(resolve(packageRoot, `spec/capability/${name}.yaml`), "utf8"));
|
|
const inventories = {
|
|
capabilities: await readInventory("capabilities"),
|
|
evaluations: await readInventory("eval-traceability"),
|
|
legacyMcpAliases: await readInventory("mcp-tool-map"),
|
|
};
|
|
const inventorySchema = JSON.parse(await readFile(resolve(packageRoot, "spec/capability/inventory.schema.json"), "utf8"));
|
|
const errors = [
|
|
...validateInventorySchema(inventories, inventorySchema),
|
|
...validateInventories(inventories),
|
|
];
|
|
const repoRoot = resolve(packageRoot, "../..");
|
|
const [liveCapabilities, liveMcpTools] = await Promise.all([
|
|
buildSkillInventory(repoRoot),
|
|
buildMcpInventory(repoRoot),
|
|
]);
|
|
for (const [name, liveInventory] of [["capabilities", liveCapabilities], ["legacyMcpAliases", liveMcpTools]]) {
|
|
const generatedIds = new Set(inventories[name].rows.map((row) => row.id));
|
|
const liveIds = new Set(liveInventory.rows.map((row) => row.id));
|
|
for (const id of liveIds) if (!generatedIds.has(id)) errors.push(`${name} is missing live source row ${id}.`);
|
|
for (const id of generatedIds) if (!liveIds.has(id)) errors.push(`${name} has stale source row ${id}.`);
|
|
const liveRows = new Map(liveInventory.rows.map((row) => [row.id, row]));
|
|
for (const row of inventories[name].rows) {
|
|
const liveRow = liveRows.get(row.id);
|
|
if (!liveRow) continue;
|
|
for (const field of ["sourceAnchor", "expectedSemantics", "foldedInto"]) {
|
|
if (Object.hasOwn(liveRow, field) && JSON.stringify(row[field]) !== JSON.stringify(liveRow[field])) {
|
|
errors.push(`${name}:${row.id} has stale ${field}.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const expected = {
|
|
[resolve(packageRoot, "src/generated/capability-contract.ts")]: renderContractModule(inventories),
|
|
[resolve(packageRoot, "docs/capability-contract.md")]: renderDocumentation(inventories),
|
|
};
|
|
for (const [path, source] of Object.entries(expected)) {
|
|
if (await readFile(path, "utf8").catch(() => "") !== source) errors.push(`Generated output is stale: ${path}.`);
|
|
}
|
|
if (errors.length > 0) {
|
|
process.stderr.write(`Capability inventory check failed:\n${errors.map((error) => `- ${error}`).join("\n")}\n`);
|
|
process.exitCode = 1;
|
|
} else {
|
|
process.stdout.write("Capability inventory completeness and generated-output checks passed.\n");
|
|
}
|