import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const abs = (file) => path.join(root, file); const cachePath = abs("tests/golden/WBV2/context-cache.json"); const read = (file) => fs.readFileSync(abs(file), "utf8"); const digest = (value) => crypto.createHash("sha256").update(value).digest("hex"); const queue = read("docs/EXECUTION_QUEUE.md"); const task = process.argv.includes("--task") ? process.argv[process.argv.indexOf("--task") + 1] : queue.match(/\| 当前任务 \| `([^`]+)` \|/u)?.[1]; if (!task) throw new Error("unable to resolve current WBV2 task"); const parentManifest = queue.match(/\| parent manifest \| `([^`]+)` \|/u)?.[1]; if (!parentManifest) throw new Error("queue parent manifest is missing"); const baseInputPaths = [ "docs/EXECUTION_QUEUE.md", `docs/tasks/${task}.md`, parentManifest, `tests/golden/${task}/task-context.json`, ]; const statInput = (file) => { try { const stat = fs.statSync(abs(file), { bigint: true }); return { bytes: Number(stat.size), mtime_ns: stat.mtimeNs.toString(), exists: true }; } catch (error) { return { bytes: null, mtime_ns: null, exists: false, error: error?.code ?? "STAT_FAILED" }; } }; const metadataComparable = (value) => value && value.exists === true && Number.isSafeInteger(value.bytes) && typeof value.mtime_ns === "string"; let cached = null; try { cached = JSON.parse(fs.readFileSync(cachePath, "utf8")); } catch { /* First use or an interrupted write. */ } const inputPaths = [...new Set([ ...baseInputPaths, ...Object.keys(cached?.inputs ?? {}), ])]; const inputMetadata = Object.fromEntries(inputPaths.map((file) => [file, statInput(file)])); const metadataUnchanged = (cachedInputs) => inputPaths.every((file) => { const current = inputMetadata[file]; const cached = cachedInputs?.[file]; return metadataComparable(current) && metadataComparable(cached) && current.bytes === cached.bytes && current.mtime_ns === cached.mtime_ns; }); const digestPath = (file) => { try { return digest(read(file)); } catch { return "MISSING"; } }; const inputHashFor = (paths = inputPaths) => digest(paths.map((file) => `${file}\0${digestPath(file)}\n`).join("")); if (!process.argv.includes("--force") && cached?.status === "PASS" && cached.task === task && metadataUnchanged(cached.inputs)) { process.stdout.write(`CONTEXT_PACKET_REUSED task=${task} inputHash=${cached.inputHash ?? "UNKNOWN"}\n`); process.exit(0); } const result = spawnSync(process.execPath, [abs("tools/web/print-task-context.mjs"), "--task", task], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }); const check = spawnSync(process.execPath, [abs("tools/web/check-task-context.mjs"), "--task", task, "--write"], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }); if ((result.status ?? 1) !== 0 || (check.status ?? 1) !== 0) { process.stderr.write(`${result.stdout ?? ""}${result.stderr ?? ""}${check.stdout ?? ""}${check.stderr ?? ""}`); process.exit(Math.max(result.status ?? 1, check.status ?? 1)); } fs.mkdirSync(path.dirname(cachePath), { recursive: true }); let contextMetadata = {}; try { const context = JSON.parse(read(`tests/golden/${task}/task-context.json`)); contextMetadata = context.inputMetadata && typeof context.inputMetadata === "object" ? context.inputMetadata : {}; } catch { /* The task-context checker already reported the precise failure. */ } const refreshedPaths = [...new Set([...inputPaths, ...Object.keys(contextMetadata)])]; const refreshedInputHash = inputHashFor(refreshedPaths); const payload = { schemaVersion: 2, status: "PASS", task, inputHash: refreshedInputHash, inputs: Object.fromEntries(refreshedPaths.map((file) => [file, { ...statInput(file), sha256: contextMetadata[file]?.sha256 ?? digestPath(file) }])), refreshedAt: new Date().toISOString(), }; const temporary = `${cachePath}.tmp-${process.pid}`; fs.writeFileSync(temporary, `${JSON.stringify(payload, null, 2)}\n`); fs.renameSync(temporary, cachePath); process.stdout.write(`wbv2-context-cache-refresh task=${task} inputHash=${refreshedInputHash}\n`);