"""Task-orchestration helpers: CLI shape, dependency markers, completion markers, lab config loading (docs/rules/build-pipeline-tasks.md, docs/rules/code-python-style.md section 3). """ from __future__ import annotations import argparse import logging import sys from pathlib import Path import yaml logger = logging.getLogger(__name__) REPO_ROOT = Path(__file__).resolve().parent.parent def parse_task_args(description: str) -> argparse.Namespace: parser = argparse.ArgumentParser(description=description) parser.add_argument("--log-level", default="INFO", choices=["DEBUG", "INFO", "WARNING", "ERROR"]) parser.add_argument("--out-root", type=Path, default=REPO_ROOT / "out", help="artifact tree root (default: ./out)") args = parser.parse_args() logging.basicConfig( level=args.log_level, stream=sys.stderr, format="%(asctime)s %(levelname)-7s %(name)s: %(message)s", ) return args def check_dependencies(out_root: Path, deps: list[str]) -> None: for dep in deps: marker = out_root / ".done" / f"{dep}.ok" if not marker.exists(): raise FileNotFoundError(f"dependency marker missing: {marker} - run task {dep} first") def remove_stale_marker(out_root: Path, task_id: str) -> Path: marker = out_root / ".done" / f"{task_id}.ok" if marker.exists(): logger.info("re-run: removing stale marker %s", marker) marker.unlink() return marker def write_marker(out_root: Path, task_id: str, entries: dict) -> Path: marker = out_root / ".done" / f"{task_id}.ok" lines = [f"task: '{task_id}'", "status: ok"] + [f"{k}: {v}" for k, v in entries.items()] marker.parent.mkdir(parents=True, exist_ok=True) with open(marker, "w", encoding="utf-8", newline="\n") as fh: fh.write("\n".join(lines) + "\n") return marker def load_lab_config(out_root: Path) -> dict: with open(out_root / "config" / "lab_config.yaml", encoding="utf-8") as fh: return yaml.safe_load(fh)