Files
OwnCord/.superpowers/render-ledger.mjs
T

153 lines
5.8 KiB
JavaScript
Raw Normal View History

// Renders .superpowers/findings-ledger.json to FINDINGS.md, and validates it.
// Run: node .superpowers/render-ledger.mjs # write FINDINGS.md
// node .superpowers/render-ledger.mjs --check # validate only
// node .superpowers/render-ledger.mjs --selftest # run built-in tests
import assert from "node:assert/strict";
const VALID_STATUS = ["open", "fixed", "declined", "refuted", "duplicate", "blocked"];
// Must stay in lockstep with SEV_RANK below. render() sorts the open section by
// SEV_RANK, and an unranked severity makes the comparator return NaN — which
// leaves the sort order implementation-defined, so the rendering would stop
// being a pure function of the ledger. The drift gate compares the rendering
// against the ledger, so its whole premise rests on this being enforced.
const VALID_SEVERITY = ["critical", "high", "medium", "low"];
export function validate(ledger) {
const problems = [];
const ids = new Set();
for (const r of ledger.findings) {
if (ids.has(r.id)) problems.push(`duplicate id ${r.id}`);
ids.add(r.id);
if (!/^OC-\d{4}$/.test(r.id)) problems.push(`${r.id}: malformed id`);
if (!VALID_STATUS.includes(r.status)) problems.push(`${r.id}: bad status ${r.status}`);
if (!VALID_SEVERITY.includes(r.severity)) problems.push(`${r.id}: bad severity ${r.severity}`);
if (r.status === "fixed" && (!r.fix || !r.fix.commit))
problems.push(`${r.id}: fixed without a commit`);
if (r.status === "declined" && !r.rationale)
problems.push(`${r.id}: declined without a rationale`);
if (r.status === "duplicate" && !r.duplicateOf)
problems.push(`${r.id}: duplicate without duplicateOf`);
}
return problems;
}
function selftest() {
// Every fixture carries a severity: validate() now requires one, so omitting
// it would make each case report two problems and assert against the wrong one.
assert.deepEqual(validate({ findings: [] }), []);
assert.deepEqual(
validate({ findings: [{ id: "OC-0001", severity: "low", status: "fixed", fix: null }] }),
["OC-0001: fixed without a commit"],
);
assert.deepEqual(validate({ findings: [{ id: "bad", severity: "low", status: "open" }] }), [
"bad: malformed id",
]);
assert.deepEqual(
validate({
findings: [
{ id: "OC-0001", severity: "low", status: "open" },
{ id: "OC-0001", severity: "low", status: "open" },
],
}),
["duplicate id OC-0001"],
);
assert.deepEqual(
validate({ findings: [{ id: "OC-0002", severity: "low", status: "declined" }] }),
["OC-0002: declined without a rationale"],
);
// An unranked severity is what makes render()'s sort implementation-defined.
assert.deepEqual(
validate({ findings: [{ id: "OC-0003", severity: "moderate", status: "open" }] }),
["OC-0003: bad severity moderate"],
);
assert.deepEqual(validate({ findings: [{ id: "OC-0004", status: "open" }] }), [
"OC-0004: bad severity undefined",
]);
for (const sev of VALID_SEVERITY) {
assert.deepEqual(
validate({ findings: [{ id: "OC-0005", severity: sev, status: "open" }] }),
[],
);
}
console.log("selftest: all assertions pass");
}
const SEV_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
export function render(ledger) {
const by = (s) => ledger.findings.filter((f) => f.status === s);
const open = by("open").sort((a, b) => SEV_RANK[a.severity] - SEV_RANK[b.severity]);
const blocked = by("blocked");
const fixed = by("fixed");
const declined = by("declined");
const refuted = by("refuted");
const dup = by("duplicate");
const lines = [];
lines.push("# OwnCord Findings Ledger", "");
lines.push(
"Generated by `render-ledger.mjs`. Do not hand-edit — edit `findings-ledger.json`.",
"",
);
lines.push(
`**${open.length} open** · ${blocked.length} blocked · ${fixed.length} fixed · ` +
`${declined.length} declined · ${refuted.length} refuted · ${dup.length} duplicate`,
"",
);
const section = (title, rows, extra) => {
if (!rows.length) return;
lines.push(`## ${title}`, "");
for (const r of rows) {
lines.push(`### ${r.id}${r.severity}${r.title}`, "");
lines.push(
`\`${r.file}:${r.line}\` · found ${r.found} · hunt \`${r.hunt}\` · lens \`${r.lens}\``,
"",
);
if (r.why) lines.push(r.why, "");
if (r.repro) lines.push(`**Repro:** ${r.repro}`, "");
if (r.evidence) lines.push(`**Evidence:** ${r.evidence}`, "");
if (r.suggestedFix) lines.push(`**Suggested fix:** ${r.suggestedFix}`, "");
const e = extra && extra(r);
if (e) lines.push(e, "");
}
};
section("Open", open);
section("Blocked — fix attempted, revert-proof failed", blocked);
section(
"Fixed",
fixed,
(r) =>
`**Fixed:** \`${r.fix.commit}\` · test \`${r.fix.test}\` · revert-proof ${r.fix.revertProof}`,
);
section("Declined", declined, (r) => `**Declined:** ${r.rationale}`);
section("Refuted", refuted);
section("Duplicate", dup, (r) => `**Duplicate of** ${r.duplicateOf}`);
return lines.join("\n");
}
async function main() {
const { readFileSync, writeFileSync } = await import("node:fs");
const { dirname, join } = await import("node:path");
const { fileURLToPath } = await import("node:url");
const here = dirname(fileURLToPath(import.meta.url));
const ledger = JSON.parse(readFileSync(join(here, "findings-ledger.json"), "utf8"));
const problems = validate(ledger);
if (problems.length) {
for (const p of problems) console.error(`INVALID ${p}`);
process.exit(1);
}
if (process.argv.includes("--check")) {
console.log(`ledger valid: ${ledger.findings.length} finding(s)`);
return;
}
writeFileSync(join(here, "FINDINGS.md"), render(ledger) + "\n");
console.log(`wrote FINDINGS.md (${ledger.findings.length} finding(s))`);
}
if (process.argv.includes("--selftest")) selftest();
else await main();