Reorganize Blender Web execution around capabilities
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions

This commit is contained in:
mes123456
2026-08-24 18:37:46 -04:00
parent 0b2c3ba0dd
commit a081c68c87
2530 changed files with 763976 additions and 79 deletions

View File

@@ -0,0 +1,244 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { buildTaskContext } from "./task-context-lib.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const correctiveRoot = path.join(root, "tests/golden/corrective");
const read = (file) => fs.readFileSync(file, "utf8");
const json = (file) => JSON.parse(read(file));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
const digest = (value) => crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
const relative = (file) => path.relative(root, file).replaceAll(path.sep, "/");
const inside = (file, parent) => file === parent || file.startsWith(`${parent}${path.sep}`);
function arg(name) {
const index = process.argv.indexOf(name);
return index >= 0 ? process.argv[index + 1] : undefined;
}
const evidenceRootArg = arg("--evidence-root");
const outputRootArg = arg("--output-root");
const reportArg = arg("--report");
const approved = process.argv.includes("--approve-current-worktree");
if (!evidenceRootArg || !outputRootArg || !reportArg || !approved) {
throw new Error("usage: node tools/web/reconcile-corrective-resign.mjs --evidence-root tests/golden --output-root tests/golden/corrective/C0-002/staged --report path --approve-current-worktree");
}
const evidenceRoot = path.resolve(root, evidenceRootArg);
const outputRoot = path.resolve(root, outputRootArg);
const reportPath = path.resolve(root, reportArg);
if (!inside(evidenceRoot, root) || !inside(outputRoot, correctiveRoot) || !inside(reportPath, correctiveRoot)) {
throw new Error("evidence, output and report paths are outside the allowed repository roots");
}
const queuePath = path.join(root, "docs/EXECUTION_QUEUE.md");
const queue = read(queuePath);
const queueTask = queue.match(/\|\s*当前任务\s*\|\s*`([^`]+)`/u)?.[1];
const parentManifestPath = queue.match(/\|\s*parent manifest\s*\|\s*`([^`]+)`/iu)?.[1];
if (!queueTask || !parentManifestPath) throw new Error("queue must declare current task and parent manifest");
const parentManifest = json(path.join(root, parentManifestPath));
const planPath = path.join(root, "tests/golden/M15-03A/next-task-plan.json");
const plan = json(planPath);
const taskToGap = new Map(plan.tasks.map((task) => [task.id, task.gapId]));
const issueCounts = new Map();
const issueTasks = new Map();
const issuePaths = new Map();
const issues = [];
const addIssue = (code, fields = {}) => {
issues.push({ code, ...fields });
issueCounts.set(code, (issueCounts.get(code) ?? 0) + 1);
if (fields.task) {
const values = issueTasks.get(code) ?? new Set();
values.add(fields.task);
issueTasks.set(code, values);
}
if (fields.path) {
const values = issuePaths.get(code) ?? new Set();
values.add(fields.path);
issuePaths.set(code, values);
}
};
const taskDirs = fs.readdirSync(evidenceRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && /^M16-GAP-\d{5}$/u.test(entry.name))
.map((entry) => entry.name)
.sort();
const records = [];
const resignedManifests = [];
const repairedContexts = [];
const repairedParents = [];
const snapshotPaths = new Set();
function taskCardFields(task) {
const cardPath = path.join(root, "docs/tasks", `${task}.md`);
const source = fs.existsSync(cardPath) ? read(cardPath) : "";
const get = (name) => source.match(new RegExp(`^[-*]?\\s*\\x60?${name}\\x60?\\s*[:]\\s*\\x60?([^\\n\\x60]+)`, "imu"))?.[1]?.trim();
return { source, path: cardPath, task: get("task"), parent: get("parent"), status: get("status"), gap: get("gap"), ownerFamily: get("ownerFamily"), implementationClass: get("targetImplementationClass") };
}
function syntheticContext(task, manifest, parent) {
const fields = taskCardFields(task);
const entry = plan.tasks.find((value) => value.id === task);
const taskPath = fields.path;
const parentPath = path.join(root, "tests/golden", parent.task, "manifest.json");
const parentStatusPath = path.join(root, "docs/status", `${parent.task}.md`);
const fixture = entry?.fixture?.path ?? null;
const context = {
schemaVersion: 1,
task,
parentTask: manifest.parentTask,
status: fields.status ?? entry?.state ?? "pending",
goal: fields.source.match(/^##\s+目标\s*\n([\s\S]*?)(?=^##\s|$)/mu)?.[1].trim() ?? `完成 ${entry?.gapId ?? task} 的最小可观察切片`,
scope: {
gap: fields.gap ?? entry?.gapId ?? "NOT_APPLICABLE",
ownerFamily: fields.ownerFamily ?? entry?.ownerFamily ?? "NOT_APPLICABLE",
implementationClass: fields.implementationClass ?? entry?.targetImplementationClass ?? "NOT_APPLICABLE",
},
commands: [entry?.desktopCommand, entry?.webCommand, entry?.comparator].filter(Boolean),
inputPaths: [],
inputSelection: { schemaVersion: 1, limits: { maxFiles: 12, evidenceBytes: 8192, contextRemainingBytes: 0, contextRemainingTokens: 0 }, source: { bytes: Buffer.byteLength(queue) + Buffer.byteLength(fields.source) + Buffer.byteLength(read(parentPath)), tokens: 0 }, selected: [], excluded: fixture ? [{ path: fixture, bytes: null, reason: "EVIDENCE_BYTE_BUDGET" }] : [], totals: { files: 0, bytes: 0, tokens: 0 } },
exitCriteria: entry?.exitCriteria ?? ["focused command exits 0", "report and manifest are hash-bound"],
nextTask: manifest.nextTask ?? entry?.id ?? null,
sourceDocuments: { queue: relative(queuePath), taskCard: relative(taskPath), taskIndex: "tests/golden/M15-03A/task-index.json", parentManifest: relative(parentPath), parentStatus: fs.existsSync(parentStatusPath) ? relative(parentStatusPath) : "NOT_APPLICABLE" },
readPolicy: { required: [relative(queuePath), relative(taskPath), relative(parentPath), fs.existsSync(parentStatusPath) ? relative(parentStatusPath) : "NOT_APPLICABLE"], machineOnly: ["tests/golden/M15-03A/task-index.json", "tests/golden/M15-03A/task-catalog.jsonl"], optionalByNeed: [], forbiddenByDefault: [] },
parent: { manifest: { schemaVersion: parent.schemaVersion, task: parent.task, parentTask: parent.parentTask, status: parent.status ?? "done", nextTask: task, artifactCount: Object.keys(parent.artifacts ?? {}).length }, statusSummary: fs.existsSync(parentStatusPath) ? read(parentStatusPath).split("\n").filter(Boolean).slice(0, 8) : [] },
budgets: { queueBytes: 4096, taskBytes: 8192, parentManifestBytes: 24576, parentStatusBytes: 6144, evidenceFiles: 12, evidenceBytes: 8192, contextTokens: 3500, contextEnvelopeTokens: 1024, taskLines: fields.source ? fields.source.split("\n").length : 0, taskTokens: Math.ceil(Buffer.byteLength(fields.source) / 4) },
};
return context;
}
for (const task of taskDirs) {
const directory = path.join(evidenceRoot, task);
const manifestPath = path.join(directory, "manifest.json");
const manifest = json(manifestPath);
const statusPath = path.join(root, "docs/status", `${task}.md`);
const contextPath = path.join(directory, "task-context.json");
if (!fs.existsSync(statusPath)) addIssue("MISSING_STATUS", { task });
if (!fs.existsSync(contextPath)) addIssue("MISSING_TASK_CONTEXT", { task, repaired: true });
const candidate = structuredClone(manifest);
const drift = [];
for (const artifact of Object.values(candidate.artifacts ?? {})) {
if (!artifact?.path || !artifact?.sha256) continue;
const artifactPath = path.join(root, artifact.path);
if (!fs.existsSync(artifactPath)) {
addIssue("ARTIFACT_MISSING", { task, path: artifact.path });
continue;
}
const actual = sha256(artifactPath);
snapshotPaths.add(artifact.path);
if (actual !== artifact.sha256) {
drift.push({ path: artifact.path, originalSha256: artifact.sha256, resignedSha256: actual });
artifact.sha256 = actual;
addIssue("ARTIFACT_HASH_RESIGNED", { task, path: artifact.path });
}
}
if (manifest.parentTask) {
const parentPath = path.join(root, "tests/golden", manifest.parentTask, "manifest.json");
if (fs.existsSync(parentPath)) {
const parent = json(parentPath);
if (parent.nextTask !== task) {
repairedParents.push({ task, parentTask: manifest.parentTask, originalNextTask: parent.nextTask, resignedNextTask: task });
addIssue("PARENT_POINTER_RESIGNED", { task, parent: manifest.parentTask, originalNextTask: parent.nextTask });
}
}
}
candidate.reconciliation = { mode: "CURRENT_WORKTREE_RESIGN", authorized: true, artifactDrift: drift };
resignedManifests.push({ task, manifest: candidate });
let context;
if (fs.existsSync(contextPath)) {
context = json(contextPath);
} else {
try {
context = buildTaskContext(task).context;
} catch {
const parent = manifest.parentTask ? json(path.join(root, "tests/golden", manifest.parentTask, "manifest.json")) : manifest;
context = syntheticContext(task, manifest, parent);
}
repairedContexts.push({ task, context });
}
records.push({ task, gapId: taskToGap.get(task) ?? null, parentTask: manifest.parentTask, status: manifest.status, completeEvidence: manifest.status === "done" && fs.existsSync(statusPath), resigned: true });
}
if (parentManifest.nextTask !== queueTask) addIssue("PARENT_NEXT_TASK_MISMATCH", { detail: `${parentManifest.nextTask}!=${queueTask}` });
const active = records.filter((record) => record.status === "in_progress");
if (active.length !== 1) addIssue("ACTIVE_TASK_COUNT", { detail: `count=${active.length}` });
if (active[0]?.task !== queueTask) addIssue("ACTIVE_TASK_MISMATCH", { detail: `${active[0]?.task ?? "NONE"}!=${queueTask}` });
const eligibleCompletedTaskIds = records.filter((record) => record.completeEvidence).map((record) => record.task).sort();
const eligibleCompletedGaps = records.filter((record) => record.completeEvidence && record.gapId).map((record) => record.gapId).sort();
const issueSummary = [...issueCounts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([code, count]) => ({ code, count, taskCount: issueTasks.get(code)?.size ?? 0, sampleTasks: [...(issueTasks.get(code) ?? [])].sort().slice(0, 5), pathCount: issuePaths.get(code)?.size ?? 0, samplePaths: [...(issuePaths.get(code) ?? [])].sort().slice(0, 5) }));
const sourcePaths = [queuePath, path.join(root, parentManifestPath), path.join(root, "tests/golden/M15-03A/completed-gap-tasks.json"), planPath, path.join(root, "tests/golden/M15-03A/task-catalog.jsonl"), path.join(root, "tests/golden/M15-03A/task-index.json")];
const sourceHashes = Object.fromEntries(sourcePaths.map((file) => [relative(file), sha256(file)]));
const inputDigest = digest({ sourceHashes, records, eligibleCompletedTaskIds, eligibleCompletedGaps, resignedManifests: resignedManifests.map(({ task, manifest }) => ({ task, artifacts: manifest.artifacts, reconciliation: manifest.reconciliation })), repairedContexts: repairedContexts.map(({ task }) => task), repairedParents });
const report = { schemaVersion: 1, operation: "CORRECTIVE_STATE_RECONCILIATION_RESIGN", mode: "write", status: "BLOCKED", queueMutation: false, authorization: { mode: "CURRENT_WORKTREE_RESIGN", userApproved: true, legacyFilesOverwritten: false }, queue: { currentTask: queueTask, parentManifest: parentManifestPath, parentNextTask: parentManifest.nextTask }, inputTaskCount: records.length, candidateActiveTask: active.length === 1 ? active[0].task : null, sourceHashes, inputDigest, issueSummary, issueCount: issues.length, issues, registryComparison: { candidateCompletedTaskCount: eligibleCompletedTaskIds.length, candidateCompletedGapCount: eligibleCompletedGaps.length, previousRegistryPath: "tests/golden/M15-03A/completed-gap-tasks.json" }, repairPlan: { approvalRequired: false, queueMutationAllowed: false, actions: ["re-sign current artifact hashes in staged manifests", "repair contexts with valid indexed parent data", "record M16-GAP-00001 parent pointer as a staged repair", "regenerate registry, plan, catalog, index and active card in staging"] }, writeOutcome: "NOT_STARTED", commandExitCodes: { resign: null, validation: null, planCheck: null, indexCheck: null, contextCheck: null, governanceCheck: null, diffCheck: null } };
for (const entry of ["resigned-manifests", "resigned-context", "source-snapshot", "completed-gap-tasks.candidate.json", "next-task-plan.candidate.json", "next-task-plan.candidate.second.json", "task-index", "task-cards"]) {
fs.rmSync(path.join(outputRoot, entry), { recursive: true, force: true });
}
fs.mkdirSync(outputRoot, { recursive: true });
const manifestRoot = path.join(outputRoot, "resigned-manifests");
for (const { task, manifest } of resignedManifests) {
const file = path.join(manifestRoot, task, "manifest.json");
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\n`);
}
for (const parentRepair of repairedParents) {
const source = path.join(root, "tests/golden", parentRepair.parentTask, "manifest.json");
const candidate = json(source);
candidate.nextTask = parentRepair.resignedNextTask;
candidate.reconciliation = { mode: "CURRENT_WORKTREE_RESIGN", authorized: true, parentPointerRepair: parentRepair };
const file = path.join(manifestRoot, parentRepair.parentTask, "manifest.json");
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${JSON.stringify(candidate, null, 2)}\n`);
}
const contextRoot = path.join(outputRoot, "resigned-context");
for (const { task, context } of repairedContexts) {
const file = path.join(contextRoot, task, "task-context.json");
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${JSON.stringify(context, null, 2)}\n`);
}
const completionPath = path.join(outputRoot, "completed-gap-tasks.candidate.json");
fs.writeFileSync(completionPath, `${JSON.stringify(eligibleCompletedGaps, null, 2)}\n`);
const snapshotRoot = path.join(outputRoot, "source-snapshot");
const snapshotFiles = [];
for (const artifactPath of [...snapshotPaths].sort()) {
const source = path.join(root, artifactPath);
const destination = path.join(snapshotRoot, "files", artifactPath);
fs.mkdirSync(path.dirname(destination), { recursive: true });
fs.copyFileSync(source, destination);
snapshotFiles.push({ path: artifactPath, snapshotPath: relative(destination), sha256: sha256(source), bytes: fs.statSync(source).size });
}
fs.writeFileSync(path.join(snapshotRoot, "snapshot-manifest.json"), `${JSON.stringify({ schemaVersion: 1, mode: "CURRENT_WORKTREE_RESIGN", authorized: true, files: snapshotFiles }, null, 2)}\n`);
const candidatePlan = path.join(outputRoot, "next-task-plan.candidate.json");
const candidatePlanSecond = path.join(outputRoot, "next-task-plan.candidate.second.json");
const generator = path.join(root, "tools/web/generate-blender-next-task-plan.mjs");
execFileSync(process.execPath, [generator, candidatePlan, "--completion-path", completionPath], { cwd: root, stdio: "ignore" });
execFileSync(process.execPath, [generator, candidatePlanSecond, "--completion-path", completionPath], { cwd: root, stdio: "ignore" });
if (!fs.readFileSync(candidatePlan).equals(fs.readFileSync(candidatePlanSecond))) {
addIssue("CANDIDATE_PLAN_NONDETERMINISTIC");
} else {
fs.rmSync(candidatePlanSecond, { force: true });
}
const indexDir = path.join(outputRoot, "task-index");
execFileSync(process.execPath, [path.join(root, "tools/web/generate-task-index.mjs"), "--plan-path", candidatePlan, "--output-dir", indexDir], { cwd: root, stdio: "ignore" });
const candidatePlanData = json(candidatePlan);
const activeTask = candidatePlanData.firstTask;
const activeCardSource = activeTask && fs.existsSync(path.join(root, "docs/tasks", `${activeTask}.md`)) ? read(path.join(root, "docs/tasks", `${activeTask}.md`)) : "";
if (activeTask && activeCardSource) {
const cardPath = path.join(outputRoot, "task-cards", `${activeTask}.md`);
fs.mkdirSync(path.dirname(cardPath), { recursive: true });
fs.writeFileSync(cardPath, activeCardSource);
}
report.status = issues.some((issue) => issue.code === "ARTIFACT_MISSING" || issue.code === "CANDIDATE_PLAN_NONDETERMINISTIC") ? "BLOCKED" : "PASS_WITH_REPAIR";
report.writeOutcome = report.status === "PASS_WITH_REPAIR" ? "STAGED_RE_SIGN_CANDIDATE" : "NO_FILES_WRITTEN";
report.issueSummary = [...issueCounts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([code, count]) => ({ code, count, taskCount: issueTasks.get(code)?.size ?? 0, sampleTasks: [...(issueTasks.get(code) ?? [])].sort().slice(0, 5), pathCount: issuePaths.get(code)?.size ?? 0, samplePaths: [...(issuePaths.get(code) ?? [])].sort().slice(0, 5) }));
report.issueCount = issues.length;
report.candidates = { completionRegistry: relative(completionPath), plan: relative(candidatePlan), taskIndex: relative(path.join(indexDir, "task-index.json")), catalog: relative(path.join(indexDir, "task-catalog.jsonl")), activeTaskCard: activeTask ? relative(path.join(outputRoot, "task-cards", `${activeTask}.md`)) : null, sourceSnapshot: relative(path.join(snapshotRoot, "snapshot-manifest.json")), sourceSnapshotFileCount: snapshotFiles.length, resignedManifestCount: resignedManifests.length, repairedContextCount: repairedContexts.length, repairedParentCount: repairedParents.length };
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`corrective-resign-${report.status.toLowerCase()} tasks=${records.length} completed=${eligibleCompletedGaps.length} issues=${issues.length} output=${relative(outputRoot)}\n`);