#!/usr/bin/env python3 """Activate the next implementation-first Web feature gap phase.""" from __future__ import annotations import hashlib import json import re import subprocess from pathlib import Path from wbv2_runtime import CONTROL_ROOT, INDEX, QUEUE, ROOT CURRENT = "WBV2-P1421-001" ROOT_TASK = "WBV2-P1422-000" TASK = "WBV2-P1422-001" def read_json(path: Path) -> dict: return json.loads(path.read_text(encoding="utf-8")) def write_json(path: Path, value: object) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def write_text(path: Path, value: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(value, encoding="utf-8") def sha(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def rel(path: Path) -> str: return path.relative_to(ROOT).as_posix() TASKS = [ { "id": "OBJECT-EDITOR-COVERAGE", "module": "OBJECT", "title": "Complete object editor authoring workflows", "commands": ["clearObjectTransform", "createObjectData", "setObjectSelection", "setCollectionVisibility"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/scene-ir.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.h"], "implementation": "Map the remaining object editor workflows to native Main transactions: clear location/rotation/scale, create camera/light/empty/armature data, selection/visibility and collection operations. Preserve browser-native viewport context; do not emulate desktop screens.", }, { "id": "MESH-EDITOR-COVERAGE", "module": "MESH", "title": "Complete mesh and UV editor authoring workflows", "commands": ["meshSubdivide", "meshTriangulate", "meshRecalculateNormals", "meshBridgeLoops", "meshDeleteElements", "meshUVProject"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/mesh-authoring-commands.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Extend the existing native mesh writer for subdivide, triangulate, normal recalculation, bridge loops, delete variants and common UV projection/seam workflows, with typed selection and no JavaScript mesh simulation.", }, { "id": "CURVE-SURFACE-COVERAGE", "module": "NONMESH", "title": "Complete curve, surface and point-data authoring", "commands": ["setCurveBevel", "setCurveTaper", "convertCurveType", "setSurfaceResolution", "setMetaballElements"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/nonmesh-geometry-commands.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Complete user-reachable curve/surface/point-cloud workflows using Blender Main data, including bevel/taper/resolution/type conversion and remaining metaball/curve properties.", }, { "id": "ARMATURE-EDITOR-COVERAGE", "module": "ANIMATION", "title": "Complete armature edit and constraint workflows", "commands": ["armatureEditOperations", "setBoneRoll", "setBoneSymmetry", "setBoneParenting", "setPoseConstraint"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/scene-ir.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.h"], "implementation": "Extend the existing armature hierarchy writer with edit operations, roll/symmetry/parenting and pose constraints through native Main.", }, { "id": "ANIMATION-FCURVE-DRIVER-COVERAGE", "module": "ANIMATION", "title": "Complete FCurve, driver and keying workflows", "commands": ["setFCurveKeyframes", "setDriverExpression", "setActionGroups", "setKeyingSet", "bakeAnimation"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/animation-render-commands.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Add typed FCurve point/driver/group/keying-set and bounded bake workflows to the existing Action/Main path.", }, { "id": "SEQUENCER-COMPOSITOR-COVERAGE", "module": "MEDIA", "title": "Complete Sequencer effects and compositor authoring", "commands": ["setSequencerStripModifiers", "setSequencerTransitions", "setCompositorNodeGraph", "setSequencerColorBalance"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/sequencer.ts", "web/protocol/compositor-commands.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Extend full-strip editing with effects/transitions and expose compositor graph editing through native Main graph transactions.", }, { "id": "PHYSICS-SIMULATION-COVERAGE", "module": "SIMULATION", "title": "Complete physics and cache authoring workflows", "commands": ["setRigidBodySettings", "setClothSettings", "setFluidSettings", "setParticleCache", "setSimulationPlayback"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/physics-simulation.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Add typed native Main settings and cache/playback controls for rigid body, cloth, fluid, soft body and particle workflows, with explicit unavailable-resource errors.", }, { "id": "TRACKING-MASK-COVERAGE", "module": "ASSET_IO", "title": "Complete movie tracking and mask workflows", "commands": ["setTrackingMarkers", "setTrackingSolve", "setMaskSpline", "setMaskPoint"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/tracking-mask.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Expose browser media-backed tracking markers, solve settings and mask spline/point editing through native Main or the existing server job boundary.", }, { "id": "GREASE-PENCIL-COVERAGE", "module": "PAINT", "title": "Complete Grease Pencil draw/edit/paint workflows", "commands": ["setGreasePencilDrawing", "setGreasePencilMaterial", "setGreasePencilSelection", "setGreasePencilInterpolate"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/grease-pencil.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Extend existing layer/frame/stroke support to drawing edit, material, selection, interpolation and browser pointer workflows.", }, { "id": "RENDER-CAMERA-LIGHT-COVERAGE", "module": "RENDER", "title": "Complete render, camera, light and pass workflows", "commands": ["renderFrame", "renderAnimation", "setRenderPasses", "setCameraView", "setLightLinking"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/render-routing.ts", "web/protocol/render-capabilities.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Complete user-visible render execution/routing, camera view controls, advanced light linking and render pass settings through native or explicit server routes.", }, { "id": "ASSET-LIBRARY-COVERAGE", "module": "ASSET_IO", "title": "Complete asset browser and library workflows", "commands": ["assetMark", "assetCatalog", "libraryLink", "libraryOverride", "libraryReload"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/asset-library-io.ts", "web/protocol/library-main-append.ts", "web/app/src/app/App.tsx", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp"], "implementation": "Expose browser-native asset catalog, link/append/override/reload workflows and preserve path/security rules.", }, { "id": "RNA-DATABLOCK-REFLECTION", "module": "RUNTIME", "title": "Complete generic RNA data-block reflection and editing", "commands": ["listRNADataBlocks", "readRNAProperties", "setRNAProperty", "listRNACollections"], "ownedPaths": ["web/protocol/web-engine.ts", "web/protocol/scene-ir.ts", "web/app/src/app/App.tsx", "web/app/src/engine-client/WebEngineClient.ts", "web/app/src/workers/web-engine.worker.ts", "blender-5.2.0/source/blender/web_engine/web_engine_api.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp", "blender-5.2.0/source/blender/web_engine/web_engine_main_state.h"], "implementation": "Add source-backed bounded RNA reflection for the remaining user-editable data-block properties. Reject unsupported pointers, context-only properties and unsafe arbitrary execution.", }, ] def main() -> int: backlog_path = CONTROL_ROOT / "web-product-backlog.json" backlog = read_json(backlog_path) existing = {task.get("id") for task in backlog.get("tasks", [])} for task in TASKS: task.setdefault("attempts", 0) task["status"] = "pending" task["resultPath"] = f"tests/golden/WBV2/product-migration/{task['id']}-result.json" if task["id"] not in existing: backlog.setdefault("tasks", []).append(task) else: for current in backlog["tasks"]: if current.get("id") == task["id"]: current.update(task) module_tasks = {} for task in backlog["tasks"]: module_tasks.setdefault(task.get("module"), []).append(task["id"]) for module in backlog.get("workflowModules", []): extra = module_tasks.get(module.get("id"), []) if extra: module["taskIds"] = list(dict.fromkeys([*module.get("taskIds", []), *extra])) module["status"] = "partial" backlog["previousQueueTask"] = CURRENT backlog["rootTask"] = ROOT_TASK backlog["queueTask"] = TASK backlog["strategy"] = "implementation_first_full_feature_gaps" backlog["gapInventory"] = "docs/WEB_FULL_FEATURE_GAP_INVENTORY.md" write_json(backlog_path, backlog) current_manifest_path = ROOT / "tests/golden" / CURRENT / "manifest.json" current_manifest = read_json(current_manifest_path) current_manifest["nextTask"] = ROOT_TASK write_json(current_manifest_path, current_manifest) current_status_path = ROOT / "docs/status" / f"{CURRENT}.md" if current_status_path.is_file(): status = re.sub(r"- nextTask: `[^`]+`", f"- nextTask: `{ROOT_TASK}`", current_status_path.read_text(encoding="utf-8")) write_text(current_status_path, status) if isinstance(current_manifest.get("artifacts", {}).get("status"), dict): current_manifest["artifacts"]["status"]["sha256"] = sha(current_status_path) write_json(current_manifest_path, current_manifest) root_dir = ROOT / "tests/golden" / ROOT_TASK task_dir = ROOT / "tests/golden" / TASK plan_path = root_dir / "product-implementation-plan.json" base_plan = read_json(ROOT / "tests/golden/WBV2-P1421-000/product-implementation-plan.json") root_record = {"id": ROOT_TASK, "phase": "1422", "title": "authorize full Web feature gap implementation", "kind": "enabling", "owner": "PROGRAM", "dependsOn": [CURRENT], "implementationClass": "QUEUE_ONLY", "card": f"docs/tasks/{ROOT_TASK}.md", "commands": [], "status": "done", "ready": False, "queueMutationAllowed": True} task_record = {"id": TASK, "phase": "1422", "title": "complete full Web feature gap implementation", "kind": "product-module", "owner": "WEB_PRODUCT", "dependsOn": [ROOT_TASK], "implementationClass": "PRODUCT_WORKFLOW", "card": f"docs/tasks/{TASK}.md", "commands": ["python3 my-tools/run_web_product_migration.py"], "status": "in_progress", "ready": True, "queueMutationAllowed": True, "productBacklog": "tests/golden/WBV2/web-product-backlog.json", "module": {"sourcePaths": list(dict.fromkeys(p for task in TASKS for p in task["ownedPaths"]))}} plan = {**base_plan, "planId": "BLENDER_WEB_FULL_FEATURE_IMPLEMENTATION_P1422", "status": "ACTIVE_FULL_FEATURE_GAP_IMPLEMENTATION", "tasks": [*base_plan.get("tasks", []), root_record, task_record]} write_json(plan_path, plan) write_text(ROOT / "docs/tasks" / f"{ROOT_TASK}.md", f"# {ROOT_TASK}: authorize full Web feature gap implementation\n\n- task: {ROOT_TASK}\n- parent: {CURRENT}\n- status: done\n- nextTask: {TASK}\n- gap inventory: `docs/WEB_FULL_FEATURE_GAP_INVENTORY.md`\n") write_text(ROOT / "docs/tasks" / f"{TASK}.md", f"""# {TASK}: complete full Web feature gap implementation - task: {TASK} - parent: {ROOT_TASK} - status: in_progress - targetImplementationClass: PRODUCT_WORKFLOW - implementation entrypoint: `python3 my-tools/run_web_product_migration.py` - node tools/web/print-task-context.mjs - scope: `docs/WEB_FULL_FEATURE_GAP_INVENTORY.md` and pending tasks in `tests/golden/WBV2/web-product-backlog.json` - verification: deferred to user operation; do not run full validators in implementation turns ## 目标 Implement every pending user-reachable Web feature gap in the backlog through Blender Main/WASM routes, preserving browser-native equivalents for desktop-only UI. ## 输入与范围 - `docs/WEB_FULL_FEATURE_GAP_INVENTORY.md` - `tests/golden/WBV2/web-product-backlog.json` - owned source/protocol/Worker/UI paths listed by each pending task ## 验收 - one bounded implementation task per Codex turn - existing dirty changes are preserved - no JavaScript simulation replaces Blender Main behavior - verification remains deferred until user operation ## 失败边界 - failed turns remain pending and are retried - unsupported desktop-only or unavailable-resource paths fail closed - no task advances from a missing result artifact or unrelated file change ## 交付与回滚 - result artifacts: `tests/golden/WBV2/product-migration/*-result.json` - aggregate report: `tests/golden/{TASK}/product-implementation-report.json` - rollback: stop the product runner and retain the last completed bounded task """) root_report = root_dir / "full-feature-gap-authorization.json" write_json(root_report, {"schemaVersion": 1, "task": ROOT_TASK, "operation": "AUTHORIZE_FULL_FEATURE_GAP_IMPLEMENTATION", "status": "PASS", "gapInventory": "docs/WEB_FULL_FEATURE_GAP_INVENTORY.md", "pendingTaskCount": len(TASKS), "nextTask": TASK}) root_status = ROOT / "docs/status" / f"{ROOT_TASK}.md" write_text(root_status, f"# {ROOT_TASK} status\n\n- status: `done`\n- gap inventory: `docs/WEB_FULL_FEATURE_GAP_INVENTORY.md`\n- nextTask: `{TASK}`\n") root_manifest = root_dir / "manifest.json" write_json(root_manifest, {"schemaVersion": 1, "task": ROOT_TASK, "parentTask": CURRENT, "operation": "AUTHORIZE_FULL_FEATURE_GAP_IMPLEMENTATION", "status": "done", "nextTask": TASK, "artifacts": {"parentManifest": {"path": f"tests/golden/{CURRENT}/manifest.json", "sha256": sha(current_manifest_path)}, "backlog": {"path": str(backlog_path.relative_to(ROOT)), "sha256": sha(backlog_path)}, "plan": {"path": str(plan_path.relative_to(ROOT)), "sha256": sha(plan_path)}, "inventory": {"path": "docs/WEB_FULL_FEATURE_GAP_INVENTORY.md", "sha256": sha(ROOT / "docs/WEB_FULL_FEATURE_GAP_INVENTORY.md")}}}) queue = QUEUE.read_text(encoding="utf-8") queue = re.sub(r"\| 当前任务 \| `[^`]+` \|", f"| 当前任务 | `{TASK}` |", queue, count=1) queue = re.sub(r"\| parent manifest \| `[^`]+` \|", f"| parent manifest | `tests/golden/{ROOT_TASK}/manifest.json` |", queue, count=1, flags=re.IGNORECASE) queue = re.sub(r"\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|", f"| 任务卡 | [`tasks/{TASK}.md`](tasks/{TASK}.md) |", queue, count=1) write_text(QUEUE, queue) subprocess.run(["node", "tools/web/generate-v2-task-index.mjs", "--plan-path", str(plan_path.relative_to(ROOT))], cwd=ROOT, check=True) subprocess.run(["node", "tools/web/check-task-context.mjs", "--task", TASK, "--write"], cwd=ROOT, check=True) print(f"full-feature-gap-phase-activated task={TASK} pending={len(TASKS)}") return 0 if __name__ == "__main__": raise SystemExit(main())