import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { execFileSync } from "node:child_process"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const planPath = path.join(root, "tests/golden/M15-03A/next-task-plan.json"); const gapPath = path.join(root, "tests/golden/M15-02B/blender-gap-audit.json"); const reportPath = path.join(root, "tests/golden/M15-03D/task-stats-check-report.json"); const manifestPath = path.join(root, "tests/golden/M15-03D/manifest.json"); const sha256 = (b) => crypto.createHash("sha256").update(b).digest("hex"); const fileSha256 = (f) => sha256(fs.readFileSync(f)); const relative = (f) => path.relative(root, f).replaceAll(path.sep, "/"); const plan = JSON.parse(fs.readFileSync(planPath, "utf8")); const gaps = JSON.parse(fs.readFileSync(gapPath, "utf8")); const completed = plan.tasks.filter((task) => task.state === "completed").length; const blocked = plan.tasks.filter((task) => task.state === "blocked").length; const inventoried = gaps.summary.inventoried; const uninventoried = Math.max(0, inventoried - plan.tasks.length); assert.equal(inventoried, 6900); assert.equal(plan.tasks.length, 6900); assert.equal(uninventoried, 0); assert.equal(blocked, 0); const report = { schemaVersion: 1, task: "M15-03D", operation: "BLENDER_TASK_STATS_CHECK", sources: { plan: { path: "tests/golden/M15-03A/next-task-plan.json", sha256: fileSha256(planPath) }, gaps: { path: "tests/golden/M15-02B/blender-gap-audit.json", sha256: fileSha256(gapPath) } }, statistics: { inventoried, completed, blocked, uninventoried }, nextTask: "M15-03E" }; if (process.env.UPDATE_M15_03D_MANIFEST === "1") { fs.mkdirSync(path.dirname(reportPath), { recursive: true }); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); const paths = { parentManifest: path.join(root, "tests/golden/M15-03C/manifest.json"), checker: path.join(root, "tools/web/check-blender-task-stats.mjs"), plan: planPath, gapAudit: gapPath, report: reportPath, package: path.join(root, "web/package.json") }; const artifacts = Object.fromEntries(Object.entries(paths).map(([name, file]) => [name, { path: relative(file), sha256: fileSha256(file) }])); fs.writeFileSync(manifestPath, `${JSON.stringify({ schemaVersion: 1, task: "M15-03D", parentTask: "M15-03C", enablingTask: false, parityStateChange: false, operation: "BLENDER_TASK_STATS_CHECK", artifacts, nextTask: "M15-03E" }, null, 2)}\n`); } const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); for (const artifact of Object.values(manifest.artifacts)) assert.equal(fileSha256(path.join(root, artifact.path)), artifact.sha256, artifact.path); process.stdout.write(`blender-task-stats-ok inventoried=${inventoried} completed=${completed} blocked=${blocked} uninventoried=${uninventoried} next=${manifest.nextTask}\n`);