mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-11 23:36:51 +02:00
Include the issue-document reference in the normative inventory and calibrate coverage of every declared source without changing each inventory’s existing heading parsing scope. Co-Authored-By: Paperclip <noreply@paperclip.ing>
67 lines
3.4 KiB
JavaScript
67 lines
3.4 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
import { resolve } from "node:path";
|
|
import { decodeInventory } from "../scripts/lib/capability-inventory.mjs";
|
|
import { validateRows } from "../scripts/generate-capability-contract.mjs";
|
|
|
|
const phaseDirectory = resolve(import.meta.dirname, "../generated/capability");
|
|
|
|
async function readRows(file) {
|
|
return JSON.parse(await readFile(resolve(phaseDirectory, file), "utf8")).rows;
|
|
}
|
|
|
|
test("generated Capability inventory has full source coverage", async () => {
|
|
const [capabilities, tools, evals] = await Promise.all([
|
|
readRows("capabilities.yaml"),
|
|
readRows("mcp-tool-map.yaml"),
|
|
readRows("eval-traceability.yaml"),
|
|
]);
|
|
|
|
assert.equal(capabilities.length, 161);
|
|
assert.equal(capabilities.filter(row => row.sourceAnchor.startsWith("skills/paperclip/references/issue-documents.md#")).length, 3);
|
|
assert.equal(tools.length, 42);
|
|
assert.equal(evals.length, 106);
|
|
assert.equal(new Set(evals.map((row) => row.group)).size, 16);
|
|
for (const row of [...capabilities, ...tools, ...evals]) {
|
|
assert.match(row.sourceAnchor, /\S/);
|
|
assert.match(row.semanticOperation, /\S/);
|
|
assert.match(row.expectedMockState, /\S/);
|
|
}
|
|
});
|
|
|
|
test("contract validation rejects missing, duplicate, and unclassified entries", () => {
|
|
const row = {
|
|
id: "example:1",
|
|
sourceAnchor: "source.md#L1:example",
|
|
primaryDisposition: "control_plane_owned",
|
|
};
|
|
|
|
assert.throws(() => validateRows([{ ...row, sourceAnchor: "" }], "fixture"), /source anchor/);
|
|
assert.throws(() => validateRows([row, { ...row, id: "example:2" }], "fixture"), /duplicate source anchor/);
|
|
assert.throws(() => validateRows([{ ...row, primaryDisposition: "unclassified" }], "fixture"), /valid primary disposition/);
|
|
});
|
|
|
|
|
|
test("conversational answer guidance has the same agent-operation classification in both inventories", async () => {
|
|
const generated = (await readRows("capabilities.yaml")).filter(row => row.heading === "Conversational confirmation answers");
|
|
const spec = decodeInventory(await readFile(resolve(import.meta.dirname, "../spec/capability/capabilities.yaml"), "utf8")).rows
|
|
.filter(row => row.title === "Conversational confirmation answers");
|
|
assert.equal(generated.length, 2);
|
|
assert.equal(spec.length, 2);
|
|
for (const row of [...generated, ...spec]) assert.equal(row.primaryDisposition, "always_agent_tool");
|
|
for (const row of generated) assert.equal(row.semanticOperation, "call_api");
|
|
});
|
|
|
|
test("both capability inventories cover every declared skill source", async () => {
|
|
const contract = JSON.parse(await readFile(resolve(import.meta.dirname, "../spec/capability/source-contract.json"), "utf8"));
|
|
const generated = await readRows("capabilities.yaml");
|
|
const normative = decodeInventory(await readFile(resolve(import.meta.dirname, "../spec/capability/capabilities.yaml"), "utf8"));
|
|
assert.deepEqual(normative.generatedFrom, contract.skillSources);
|
|
for (const source of contract.skillSources) {
|
|
assert.ok(generated.some(row => row.sourceAnchor.startsWith(`${source}#`)), `Missing generated source ${source}`);
|
|
assert.ok(normative.rows.some(row => row.sourceAnchor.startsWith(`${source}:`)), `Missing normative source ${source}`);
|
|
}
|
|
assert.equal(normative.rows.filter(row => row.sourceAnchor.startsWith("skills/paperclip/references/issue-documents.md:")).length, 3);
|
|
});
|