feat(web): activate implementation-first product queue
This commit is contained in:
215
my-tools/activate_web_product_migration.py
Normal file
215
my-tools/activate_web_product_migration.py
Normal file
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create the authoritative queue bridge for implementation-first Web work."""
|
||||
|
||||
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-P1419-001"
|
||||
ROOT_TASK = "WBV2-P1420-000"
|
||||
TASK = "WBV2-P1420-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()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
index = read_json(INDEX)
|
||||
source_plan_path = ROOT / index["source"]["path"]
|
||||
plan = read_json(source_plan_path)
|
||||
if any(record.get("id") == TASK for record in plan.get("tasks", [])):
|
||||
print(f"web-product-queue-already-active task={TASK}")
|
||||
return 0
|
||||
|
||||
current_manifest_path = ROOT / "tests/golden" / CURRENT / "manifest.json"
|
||||
current_status_path = ROOT / "docs/status" / f"{CURRENT}.md"
|
||||
current_manifest = read_json(current_manifest_path)
|
||||
current_manifest["nextTask"] = ROOT_TASK
|
||||
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"
|
||||
root_status = ROOT / "docs/status" / f"{ROOT_TASK}.md"
|
||||
root_card = ROOT / "docs/tasks" / f"{ROOT_TASK}.md"
|
||||
task_card = ROOT / "docs/tasks" / f"{TASK}.md"
|
||||
root_report = root_dir / "product-migration-authorization.json"
|
||||
root_manifest = root_dir / "manifest.json"
|
||||
|
||||
root_record = {
|
||||
"id": ROOT_TASK,
|
||||
"phase": "1420",
|
||||
"title": "authorize implementation-first Web product migration",
|
||||
"kind": "enabling",
|
||||
"owner": "PROGRAM",
|
||||
"dependsOn": [CURRENT],
|
||||
"implementationClass": "QUEUE_ONLY",
|
||||
"card": rel(root_card),
|
||||
"commands": [],
|
||||
"status": "done",
|
||||
"ready": False,
|
||||
"queueMutationAllowed": True,
|
||||
}
|
||||
task_record = {
|
||||
"id": TASK,
|
||||
"phase": "1420",
|
||||
"title": "complete user-facing Web workflow implementation",
|
||||
"kind": "product-module",
|
||||
"owner": "WEB_PRODUCT",
|
||||
"dependsOn": [ROOT_TASK],
|
||||
"implementationClass": "PRODUCT_WORKFLOW",
|
||||
"card": rel(task_card),
|
||||
"commands": ["python3 my-tools/run_web_product_migration.py"],
|
||||
"status": "in_progress",
|
||||
"ready": True,
|
||||
"queueMutationAllowed": True,
|
||||
"productBacklog": "tests/golden/WBV2/web-product-backlog.json",
|
||||
}
|
||||
product_plan = {
|
||||
**plan,
|
||||
"planId": "BLENDER_WEB_PRODUCT_IMPLEMENTATION_P1420",
|
||||
"status": "ACTIVE_PRODUCT_IMPLEMENTATION",
|
||||
"verificationPolicy": {
|
||||
"mode": "DEFERRED_TO_USER_OPERATION",
|
||||
"browser": "not_run",
|
||||
"required": [],
|
||||
},
|
||||
"tasks": [*plan.get("tasks", []), root_record, task_record],
|
||||
}
|
||||
write_json(plan_path, product_plan)
|
||||
write_text(root_card, f"""# {ROOT_TASK}: authorize implementation-first Web migration
|
||||
|
||||
- task: {ROOT_TASK}
|
||||
- parent: {CURRENT}
|
||||
- status: done
|
||||
- targetImplementationClass: QUEUE_ONLY
|
||||
- Implementation entrypoint: `python3 my-tools/activate_web_product_migration.py`
|
||||
|
||||
## 目标
|
||||
|
||||
Replace source-symbol verification batches with the user-workflow implementation backlog.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `docs/WEB_PRODUCT_MIGRATION_STRATEGY.md`
|
||||
- `tests/golden/WBV2/web-product-backlog.json`
|
||||
|
||||
## Handoff
|
||||
|
||||
- nextTask: `{TASK}`
|
||||
""")
|
||||
write_text(task_card, f"""# {TASK}: complete Web product workflow implementation
|
||||
|
||||
- task: {TASK}
|
||||
- parent: {ROOT_TASK}
|
||||
- status: in_progress
|
||||
- gap: product-workflow:user-reachable-main-commands
|
||||
- ownerFamily: WEB_PRODUCT
|
||||
- targetImplementationClass: PRODUCT_WORKFLOW
|
||||
- commandBudget: one implementation subtask per Codex turn
|
||||
- Implementation entrypoint: `python3 my-tools/run_web_product_migration.py`
|
||||
|
||||
## Goal
|
||||
|
||||
Complete the implementation tasks in `tests/golden/WBV2/web-product-backlog.json`.
|
||||
The `3745` source-unit inventory is traceability-only and is not the completion denominator.
|
||||
|
||||
## 输入与范围
|
||||
|
||||
- `web/app/src/app/App.tsx`
|
||||
- direct typed protocol helpers named by the active backlog task
|
||||
- `docs/WEB_PRODUCT_MIGRATION_STRATEGY.md`
|
||||
|
||||
## 验收
|
||||
|
||||
- implement exactly one backlog task per Codex turn
|
||||
- preserve existing dirty changes
|
||||
- do not run tests, builds, typecheck, browsers, validators, or coverage binding
|
||||
- do not claim similarly named desktop operators
|
||||
- outer scheduler owns subtask rotation
|
||||
- `node tools/web/print-task-context.mjs`
|
||||
|
||||
## 失败边界
|
||||
|
||||
- a failed Codex turn remains pending and is retried without advancing the backlog
|
||||
- unrelated dirty files are never reverted or included in an automatic commit
|
||||
- no implementation task changes the source traceability ledger
|
||||
|
||||
## 交付与回滚
|
||||
|
||||
All 16 workflow modules are implementation-complete. Runtime defects are opened from user operation.
|
||||
- Backlog: `tests/golden/WBV2/web-product-backlog.json`
|
||||
- Report: `tests/golden/{TASK}/product-implementation-report.json`
|
||||
- Rollback: stop `my-tools/run_web_product_migration.py` and retain the last completed subtask.
|
||||
""")
|
||||
write_text(root_status, f"# {ROOT_TASK} status\n\n- task: `{ROOT_TASK}`; status: `done`\n- strategy: `implementation_first_user_feedback`\n- nextTask: `{TASK}`\n")
|
||||
write_json(root_report, {
|
||||
"schemaVersion": 1,
|
||||
"task": ROOT_TASK,
|
||||
"operation": "AUTHORIZE_WEB_PRODUCT_IMPLEMENTATION",
|
||||
"status": "PASS",
|
||||
"sourceInventoryRole": "traceability_only",
|
||||
"workflowModuleCount": 16,
|
||||
"implementationTaskCount": 4,
|
||||
"nextTask": TASK,
|
||||
})
|
||||
write_json(root_manifest, {
|
||||
"schemaVersion": 1,
|
||||
"task": ROOT_TASK,
|
||||
"parentTask": CURRENT,
|
||||
"operation": "AUTHORIZE_WEB_PRODUCT_IMPLEMENTATION",
|
||||
"status": "done",
|
||||
"artifacts": {
|
||||
"parentManifest": {"path": rel(current_manifest_path), "sha256": sha(current_manifest_path)},
|
||||
"plan": {"path": rel(plan_path), "sha256": sha(plan_path)},
|
||||
"report": {"path": rel(root_report), "sha256": sha(root_report)},
|
||||
"status": {"path": rel(root_status), "sha256": sha(root_status)},
|
||||
},
|
||||
"nextTask": TASK,
|
||||
})
|
||||
|
||||
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)
|
||||
task_dir.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(["node", "tools/web/generate-v2-task-index.mjs", "--plan-path", rel(plan_path)], cwd=ROOT, check=True)
|
||||
subprocess.run(["node", "tools/web/check-task-context.mjs", "--task", TASK, "--write"], cwd=ROOT, check=True)
|
||||
print(f"web-product-queue-activated task={TASK} modules=16 implementationTasks=4")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -92,6 +92,74 @@ def refresh_modules(backlog: dict) -> None:
|
||||
module["status"] = "partial"
|
||||
|
||||
|
||||
def finalize_product_task(backlog: dict) -> None:
|
||||
task_id = "WBV2-P1420-001"
|
||||
parent_id = "WBV2-P1420-000"
|
||||
index = read_json(CONTROL / "task-index.json")
|
||||
plan_path = ROOT / index["source"]["path"]
|
||||
plan = read_json(plan_path)
|
||||
for record in plan.get("tasks", []):
|
||||
if record.get("id") == task_id:
|
||||
record["status"] = "done"
|
||||
record["ready"] = False
|
||||
plan["status"] = "PRODUCT_IMPLEMENTATION_COMPLETE"
|
||||
write_json(plan_path, plan)
|
||||
output = ROOT / "tests/golden" / task_id
|
||||
report_path = output / "product-implementation-report.json"
|
||||
status_path = ROOT / "docs/status" / f"{task_id}.md"
|
||||
task_context_path = output / "task-context.json"
|
||||
changed_files = []
|
||||
for task in backlog.get("tasks", []):
|
||||
result_path = ROOT / task["resultPath"]
|
||||
result = read_json(result_path) if result_path.is_file() else {}
|
||||
changed_files.extend(result.get("changedFiles", []))
|
||||
changed_files = list(dict.fromkeys(path for path in changed_files if isinstance(path, str)))
|
||||
write_json(report_path, {
|
||||
"schemaVersion": 1,
|
||||
"task": task_id,
|
||||
"operation": "WEB_PRODUCT_IMPLEMENTATION_COMPLETE",
|
||||
"status": "implemented",
|
||||
"strategy": backlog.get("strategy"),
|
||||
"verificationPolicy": backlog.get("verificationPolicy"),
|
||||
"workflowModules": backlog.get("workflowModules", []),
|
||||
"implementationTasks": backlog.get("tasks", []),
|
||||
"changedFiles": changed_files,
|
||||
"sourceInventory": backlog.get("sourceInventory"),
|
||||
})
|
||||
write_json(status_path.with_suffix(".json"), {
|
||||
"schemaVersion": 1,
|
||||
"task": task_id,
|
||||
"status": "implemented",
|
||||
"verification": "deferred_until_user_reports",
|
||||
})
|
||||
status_path.write_text(
|
||||
f"# {task_id} status\n\n- status: `implemented`\n- workflow modules: `16/16`\n- implementation tasks: `{len(backlog.get('tasks', []))}/{len(backlog.get('tasks', []))}`\n- verification: `deferred_until_user_reports`\n- nextTask: `NONE`\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
artifacts = {
|
||||
"parentManifest": {"path": f"tests/golden/{parent_id}/manifest.json", "sha256": file_hash(f"tests/golden/{parent_id}/manifest.json")},
|
||||
"backlog": {"path": str(BACKLOG.relative_to(ROOT)), "sha256": file_hash(str(BACKLOG.relative_to(ROOT)))},
|
||||
"strategy": {"path": "docs/WEB_PRODUCT_MIGRATION_STRATEGY.md", "sha256": file_hash("docs/WEB_PRODUCT_MIGRATION_STRATEGY.md")},
|
||||
"report": {"path": str(report_path.relative_to(ROOT)), "sha256": file_hash(str(report_path.relative_to(ROOT)))},
|
||||
"status": {"path": str(status_path.relative_to(ROOT)), "sha256": file_hash(str(status_path.relative_to(ROOT)))},
|
||||
"plan": {"path": str(plan_path.relative_to(ROOT)), "sha256": file_hash(str(plan_path.relative_to(ROOT)))},
|
||||
}
|
||||
if task_context_path.is_file():
|
||||
artifacts["taskContext"] = {"path": str(task_context_path.relative_to(ROOT)), "sha256": file_hash(str(task_context_path.relative_to(ROOT)))}
|
||||
write_json(output / "manifest.json", {
|
||||
"schemaVersion": 1,
|
||||
"task": task_id,
|
||||
"parentTask": parent_id,
|
||||
"operation": "WEB_PRODUCT_IMPLEMENTATION_COMPLETE",
|
||||
"status": "done",
|
||||
"implementationComplete": True,
|
||||
"verificationDeferred": True,
|
||||
"artifacts": artifacts,
|
||||
"nextTask": None,
|
||||
})
|
||||
subprocess.run(["node", "tools/web/generate-v2-task-index.mjs", "--plan-path", str(plan_path.relative_to(ROOT))], cwd=ROOT, check=True)
|
||||
|
||||
|
||||
def write_status(backlog: dict, status: str, **extra: object) -> None:
|
||||
tasks = backlog.get("tasks", [])
|
||||
modules = backlog.get("workflowModules", [])
|
||||
@@ -127,8 +195,10 @@ Implementation requirement: {task['implementation']}
|
||||
|
||||
Rules:
|
||||
- Follow the repository AGENTS.md minimal-context instructions first.
|
||||
- The authoritative queue task is WBV2-P1420-001; this product task is one bounded subtask inside it. Run the default task-context check once, and never pass the product subtask id to check-task-context.
|
||||
- Preserve all existing dirty changes. Do not revert or rewrite unrelated work.
|
||||
- Read only the owned paths and their direct imported types/helpers.
|
||||
- For scoped searches, pass each owned path explicitly through the batch search runtime option; never pass a bare --runtime flag.
|
||||
- Implement production behavior through the existing Blender Main/WASM command routes; do not simulate Blender behavior in JavaScript.
|
||||
- Do not run tests, builds, typecheck, lint, Chromium, full validators, or coverage binding. The user explicitly deferred verification to real usage.
|
||||
- Do not modify docs/EXECUTION_QUEUE.md, nextTask.md, source coverage ledgers, manifests, or this backlog.
|
||||
@@ -215,6 +285,8 @@ def main() -> int:
|
||||
refresh_modules(backlog)
|
||||
write_json(BACKLOG, backlog)
|
||||
blocked = [task for task in backlog.get("tasks", []) if task.get("status") == "blocked"]
|
||||
if not blocked:
|
||||
finalize_product_task(backlog)
|
||||
write_status(backlog, "blocked" if blocked else "implementation_complete")
|
||||
return 2 if blocked else 0
|
||||
pending["status"] = "active"
|
||||
|
||||
Reference in New Issue
Block a user