Reorganize Blender Web execution around capabilities
This commit is contained in:
90
tools/web/capture-corrective-baseline.mjs
Normal file
90
tools/web/capture-corrective-baseline.mjs
Normal file
@@ -0,0 +1,90 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const read = (relative) => fs.readFileSync(path.join(root, relative), "utf8");
|
||||
const sha256 = (relative) => crypto.createHash("sha256").update(read(relative)).digest("hex");
|
||||
const parseJson = (relative) => JSON.parse(read(relative));
|
||||
const relative = (absolute) => path.relative(root, absolute).replaceAll(path.sep, "/");
|
||||
|
||||
function argument(name) {
|
||||
const index = process.argv.indexOf(name);
|
||||
return index >= 0 ? process.argv[index + 1] : undefined;
|
||||
}
|
||||
|
||||
function queueCurrentTask() {
|
||||
const source = read("docs/EXECUTION_QUEUE.md");
|
||||
return source.match(/\|\s*当前任务\s*\|\s*`([^`]+)`/u)?.[1]
|
||||
?? source.match(/当前任务\s*[::]\s*`([^`]+)`/u)?.[1];
|
||||
}
|
||||
|
||||
function countLegacyEvidence() {
|
||||
const rootPath = path.join(root, "tests/golden");
|
||||
return fs.readdirSync(rootPath, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && /^M16-GAP-\d{5}$/u.test(entry.name))
|
||||
.length;
|
||||
}
|
||||
|
||||
const task = argument("--task") ?? queueCurrentTask();
|
||||
const output = argument("--output");
|
||||
if (!task || !output) {
|
||||
throw new Error("usage: node tools/web/capture-corrective-baseline.mjs --task M16-GAP-xxxxx --output path");
|
||||
}
|
||||
|
||||
const queue = read("docs/EXECUTION_QUEUE.md");
|
||||
const parentManifestPath = queue.match(/\|\s*parent manifest\s*\|\s*`([^`]+)`/iu)?.[1];
|
||||
if (!parentManifestPath) throw new Error("EXECUTION_QUEUE.md does not declare parent manifest");
|
||||
const parentManifest = parseJson(parentManifestPath);
|
||||
if (queueCurrentTask() !== task) throw new Error(`queue current task mismatch: ${queueCurrentTask()} != ${task}`);
|
||||
if (parentManifest.nextTask !== task) throw new Error(`parent nextTask mismatch: ${parentManifest.nextTask} != ${task}`);
|
||||
const parentStatusPath = `docs/status/${parentManifest.task}.md`;
|
||||
if (!fs.existsSync(path.join(root, parentStatusPath))) throw new Error(`missing parent status: ${parentStatusPath}`);
|
||||
|
||||
const plan = parseJson("tests/golden/M15-03A/next-task-plan.json");
|
||||
const catalog = read("tests/golden/M15-03A/task-catalog.jsonl");
|
||||
const activeIds = catalog.split("\n").filter(Boolean).map((line) => JSON.parse(line))
|
||||
.filter((entry) => entry.state === "active").map((entry) => entry.id);
|
||||
const sourcePaths = [
|
||||
"docs/EXECUTION_QUEUE.md",
|
||||
parentManifestPath,
|
||||
parentStatusPath,
|
||||
"tests/golden/M15-03A/completed-gap-tasks.json",
|
||||
"tests/golden/M15-03A/task-catalog.jsonl",
|
||||
"tests/golden/M15-03A/task-index.json",
|
||||
"tests/golden/M15-03A/next-task-plan.json",
|
||||
];
|
||||
const baseline = {
|
||||
schemaVersion: 1,
|
||||
task,
|
||||
queue: {
|
||||
currentTask: queueCurrentTask(),
|
||||
parentManifest: parentManifestPath,
|
||||
parentNextTask: parentManifest.nextTask,
|
||||
},
|
||||
generatedState: {
|
||||
catalogActiveTask: activeIds.length === 1 ? activeIds[0] : null,
|
||||
catalogActiveIds: activeIds,
|
||||
planFirstTask: plan.firstTask,
|
||||
planNextTask: plan.nextTask,
|
||||
completionCount: parseJson("tests/golden/M15-03A/completed-gap-tasks.json").length,
|
||||
legacyEvidenceDirectoryCount: countLegacyEvidence(),
|
||||
},
|
||||
sourceHashes: Object.fromEntries(sourcePaths.map((sourcePath) => [sourcePath, sha256(sourcePath)])),
|
||||
invariants: {
|
||||
queueMutationAllowed: false,
|
||||
parentPointerMatchesCurrent: parentManifest.nextTask === task,
|
||||
exactlyOneCatalogActive: activeIds.length === 1,
|
||||
catalogActiveMatchesPlan: activeIds[0] === plan.firstTask,
|
||||
},
|
||||
};
|
||||
if (!baseline.invariants.parentPointerMatchesCurrent || !baseline.invariants.exactlyOneCatalogActive || !baseline.invariants.catalogActiveMatchesPlan) {
|
||||
throw new Error(`generated state is inconsistent: ${JSON.stringify(baseline.invariants)}`);
|
||||
}
|
||||
|
||||
const outputPath = path.resolve(root, output);
|
||||
if (!outputPath.startsWith(`${root}${path.sep}`)) throw new Error("baseline output must stay inside the repository");
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, `${JSON.stringify(baseline, null, 2)}\n`);
|
||||
process.stdout.write(`corrective-baseline-ok task=${task} active=${activeIds[0]} parentNext=${parentManifest.nextTask} output=${relative(outputPath)}\n`);
|
||||
Reference in New Issue
Block a user