Files
stack/agents/darkwing/work/queue-a2/map-check.mjs
T
jason.woltjeandClaude Opus 5.5 6ca116b7ba feat(queue): queue as data A2, migration, render and dispatch (#1508)
Filbert approved round 1 (f167b85e). Manifest 782bcb62, 21 files, plus
the QUEUE.md markers and the TOOLS.md section. Lead decision 35.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 20:14:09 -05:00

81 lines
3.7 KiB
JavaScript

#!/usr/bin/env node
// Usage: node agents/darkwing/work/queue-a2/map-check.mjs [QUEUE.md] [MAP]
//
// Run before genesis. The map names the QUEUE.md blob it was built from.
// This lists every table line that differs from that blob, then every row
// whose piece, owner or issues no longer match the map. Reads only; the
// one git call is `cat-file`. Exit 0 no drift, 1 drift, 4 usage.
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const top = join(dirname(fileURLToPath(import.meta.url)), "../../../..");
const { parseMigrationMap } = await import(join(top, "packages/queue/src/queue.mjs"));
// Table lines by id: `| N | ...` rows, then the parked table's items
// numbered on from the highest row, as the map numbers them.
export function tableLines(text) {
const out = new Map();
const items = [];
let parked = false;
for (const line of text.split("\n")) {
if (line.startsWith("## ")) parked = line.startsWith("## Parked");
const m = /^\| (\d+) \|/.exec(line);
if (m && !parked) out.set(Number(m[1]), line);
else if (parked && line.startsWith("| ") && !line.startsWith("| Item ") && !line.startsWith("|---")) items.push(line);
}
let next = Math.max(0, ...out.keys()) + 1;
for (const line of items) out.set(next++, line);
return out;
}
const cells = (line) => line.slice(2, -2).split(" | ");
export function check(queueText, mapText, oldText) {
const drift = [];
const now = tableLines(queueText);
const then = tableLines(oldText);
for (const id of new Set([...now.keys(), ...then.keys()])) {
if (now.get(id) !== then.get(id)) drift.push(`row ${id}: ${!then.has(id) ? "added" : !now.has(id) ? "removed" : "changed"} since the map's QUEUE.md blob`);
}
const map = parseMigrationMap(mapText);
const byId = new Map(map.rows.map((r) => [r.id, r]));
for (const [id, line] of now) {
const r = byId.get(id);
if (!r) { drift.push(`row ${id}: in QUEUE.md, not in the map`); continue; }
const c = cells(line);
if (!/^\d+$/.test(c[0])) {
if (c[0] !== r.piece) drift.push(`row ${id}: parked item ${JSON.stringify(c[0])} is not the map's piece`);
continue;
}
if (c[1] !== r.piece) drift.push(`row ${id}: piece differs from the map`);
const owner = /^[a-z][a-z0-9-]*/.exec(c[2])?.[0];
if (owner !== r.owner) drift.push(`row ${id}: owner ${owner} in QUEUE.md, ${r.owner} in the map`);
const issues = [...c[3].matchAll(/#(\d+)/g)].map((x) => Number(x[1]));
if (issues.join() !== r.issues.join()) drift.push(`row ${id}: issues ${issues.join(",") || "none"} in QUEUE.md, ${r.issues.join(",") || "none"} in the map`);
}
for (const id of byId.keys()) if (!now.has(id)) drift.push(`row ${id}: in the map, not in QUEUE.md`);
return drift;
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const args = process.argv.slice(2);
if (args.length > 2) {
console.error("usage: map-check.mjs [QUEUE.md] [MAP]");
process.exit(4);
}
const queueText = readFileSync(args[0] ?? join(top, "docs/plans/QUEUE.md"), "utf8");
const mapText = readFileSync(args[1] ?? join(top, "agents/darkwing/work/queue-migration-map.md"), "utf8");
const blob = /QUEUE\.md` blob `([0-9a-f]{40})`/.exec(mapText)?.[1];
if (!blob) {
console.error("the map names no QUEUE.md blob");
process.exit(1);
}
const oldText = execFileSync("git", ["-C", top, "cat-file", "blob", blob]).toString("utf8");
const drift = check(queueText, mapText, oldText);
for (const d of drift) console.log(d);
console.log(drift.length ? `${drift.length} differences; update the map before genesis` : `ok: QUEUE.md matches the map (blob ${blob.slice(0, 8)})`);
process.exit(drift.length ? 1 : 0);
}