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

@@ -24,19 +24,20 @@ const tokenEstimate = (value) => Math.ceil(Buffer.byteLength(value, "utf8") / 4)
const queuePath = path.join(root, "docs/EXECUTION_QUEUE.md");
const planPath = path.join(root, "tests/golden/M15-03A/next-task-plan.json");
const taskIndexPath = path.join(root, "tests/golden/M15-03A/task-index.json");
const legacyTaskIndexPath = path.join(root, "tests/golden/M15-03A/task-index.json");
const capabilityTaskIndexPath = path.join(root, "tests/golden/WBV2/task-index.json");
const taskIndexPathFor = (task) => task?.startsWith("WBV2-") ? capabilityTaskIndexPath : legacyTaskIndexPath;
const sha256File = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
let cachedTaskIndex;
let taskIndexLoaded = false;
const cachedTaskIndexes = new Map();
const safeName = (value) => value.replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96) || "gap";
const expandTask = (task) => ({
...task,
fixture: { path: `tests/files/web/generated/${task.id}-${safeName(task.gapId)}.blend`, state: "REQUIRED" },
desktopCommand: `build_blender_5.2.0/bin/blender -b --factory-startup --python tools/web/generated/${task.id}.py -- tests/files/web/generated/${task.id}-${safeName(task.gapId)}.blend`,
webCommand: `npm --prefix web run test:generated-gap -- --task ${task.id}`,
comparator: `node tools/web/check-generated-gap.mjs --task ${task.id}`,
exitCriteria: ["desktop fixture evidence exists", "WASM uses the same fixture", "comparator passes", "save/reopen preserves Main", "manifest hashes all artifacts"],
});
const expandTask = (task) => task.id.startsWith("WBV2-") ? task : ({
...task,
fixture: { path: `tests/files/web/generated/${task.id}-${safeName(task.gapId)}.blend`, state: "REQUIRED" },
desktopCommand: `build_blender_5.2.0/bin/blender -b --factory-startup --python tools/web/generated/${task.id}.py -- tests/files/web/generated/${task.id}-${safeName(task.gapId)}.blend`,
webCommand: `npm --prefix web run test:generated-gap -- --task ${task.id}`,
comparator: `node tools/web/check-generated-gap.mjs --task ${task.id}`,
exitCriteria: ["desktop fixture evidence exists", "WASM uses the same fixture", "comparator passes", "save/reopen preserves Main", "manifest hashes all artifacts"],
});
export function parseQueue() {
const source = read(queuePath);
@@ -48,23 +49,25 @@ export function parseQueue() {
return { path: queuePath, source, currentTask: current, parentManifest };
}
function loadTaskIndex() {
if (taskIndexLoaded) return cachedTaskIndex;
taskIndexLoaded = true;
function loadTaskIndex(task = parseQueue().currentTask) {
const taskIndexPath = taskIndexPathFor(task);
if (cachedTaskIndexes.has(taskIndexPath)) return cachedTaskIndexes.get(taskIndexPath);
if (!fs.existsSync(taskIndexPath)) {
if (fs.existsSync(planPath)) throw new Error(`missing task index; run node tools/web/generate-task-index.mjs (${relative(taskIndexPath)})`);
const command = task?.startsWith("WBV2-") ? "node tools/web/generate-v2-task-index.mjs" : "node tools/web/generate-task-index.mjs";
if (fs.existsSync(planPath)) throw new Error(`missing task index; run ${command} (${relative(taskIndexPath)})`);
return null;
}
const index = json(taskIndexPath);
if (index.schemaVersion !== 1 || !index.source?.path || !index.catalog?.path || !index.entries) {
throw new Error(`invalid task index: ${relative(taskIndexPath)}`);
}
cachedTaskIndex = index;
return cachedTaskIndex;
cachedTaskIndexes.set(taskIndexPath, index);
return index;
}
export function verifyTaskIndex() {
const index = loadTaskIndex();
export function verifyTaskIndex(task) {
const taskIndexPath = taskIndexPathFor(task ?? parseQueue().currentTask);
const index = loadTaskIndex(task);
if (!index) return null;
const sourcePath = path.join(root, index.source.path);
const catalogPath = path.join(root, index.catalog.path);
@@ -77,7 +80,7 @@ export function verifyTaskIndex() {
return index;
}
function indexedEntry(task, index = loadTaskIndex()) {
function indexedEntry(task, index = loadTaskIndex(task)) {
const metadata = index?.entries?.[task];
if (!metadata) return null;
const catalogPath = path.join(root, index.catalog.path);
@@ -105,7 +108,7 @@ function planEntry(task) {
}
function planParent(task) {
const index = loadTaskIndex();
const index = loadTaskIndex(task);
if (index?.entries?.[task]) return index.entries[task].previous;
return null;
}
@@ -308,15 +311,16 @@ export function compactTaskContext(context) {
}
function nextFromPlan(task) {
const index = loadTaskIndex();
const index = loadTaskIndex(task);
if (index?.entries?.[task]) return index.entries[task].next;
return null;
}
export function buildTaskContext(requestedTask) {
verifyTaskIndex();
const queue = parseQueue();
const task = requestedTask ?? queue.currentTask;
const taskIndexPath = taskIndexPathFor(task);
verifyTaskIndex(task);
const taskPath = path.join(root, "docs/tasks", `${task}.md`);
const entry = planEntry(task);
const taskExists = fs.existsSync(taskPath);