#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import re
from pathlib import Path

from .common import ROOT, ensure_dirs, safe_slug

def parse_brief_yaml(path: Path) -> dict:
    # Minimal YAML reader for constrained brief format (keys, lists, scalars)
    text = path.read_text(encoding="utf-8")
    # Very naive; acceptable for initial bootstrap
    data: dict = {}
    stack = [data]
    indent_stack = [0]
    key_stack: list[str] = []
    for line in text.splitlines():
        if not line.strip() or line.strip().startswith("#"):
            continue
        indent = len(line) - len(line.lstrip(" "))
        while indent < indent_stack[-1]:
            stack.pop()
            indent_stack.pop()
            if key_stack:
                key_stack.pop()
        if ":" in line:
            k, v = line.strip().split(":", 1)
            k = k.strip()
            v = v.strip()
            if not v:
                # nested dict
                cur = {}
                stack[-1][k] = cur
                stack.append(cur)
                indent_stack.append(indent + 2)
                key_stack.append(k)
            else:
                # parse JSON-ish scalars
                v = v.strip()
                if v.lower() in ("true", "false"):
                    val = v.lower() == "true"
                else:
                    try:
                        val = json.loads(v)
                    except Exception:
                        val = v.strip('"')
                stack[-1][k] = val
        elif line.strip().startswith("-"):
            item = line.strip()[1:].strip()
            try:
                val = json.loads(item) if item else None
            except Exception:
                val = item.strip('"')
            # attach to last mapping key
            if not key_stack:
                continue
            parent = stack[-1]
            last_key = key_stack[-1]
            if not isinstance(parent.get(last_key), list):
                parent[last_key] = []
            parent[last_key].append(val)
    return data

def write_python_cli_repo(repo_dir: Path, brief: dict) -> None:
    pkg = repo_dir / "src" / "app"
    tests = repo_dir / "tests"
    scripts = repo_dir / "scripts" / "ci"
    pkg.mkdir(parents=True, exist_ok=True)
    tests.mkdir(parents=True, exist_ok=True)
    scripts.mkdir(parents=True, exist_ok=True)
    (repo_dir / "pyproject.toml").write_text(
        """
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "app"
version = "0.0.1"
requires-python = ">=3.8"
dependencies = ["pytest"]


                    except Exception:
                        val = v.strip('"')
                stack[-1][k] = val
        elif line.strip().startswith("-"):
            item = line.strip()[1:].strip()
            try:
                val = json.loads(item) if item else None
            except Exception:
                val = item.strip('"')
            # attach to last mapping key
            if not key_stack:
                continue
            parent = stack[-1]
            last_key = key_stack[-1]
            if not isinstance(parent.get(last_key), list):
                parent[last_key] = []
            parent[last_key].append(val)
    return data

def write_python_cli_repo(repo_dir: Path, brief: dict) -> None:
    pkg = repo_dir / "src" / "app"
    tests = repo_dir / "tests"
    scripts = repo_dir / "scripts" / "ci"
    pkg.mkdir(parents=True, exist_ok=True)
    tests.mkdir(parents=True, exist_ok=True)
    scripts.mkdir(parents=True, exist_ok=True)
    (repo_dir / "pyproject.toml").write_text(
        """
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "app"
version = "0.0.1"
requires-python = ">=3.8"
dependencies = ["pytest"]
""".strip()
        + "\n",
        encoding="utf-8",
    )
    (pkg / "__init__.py").write_text("\n", encoding="utf-8")
    (pkg / "cli.py").write_text(
        """
import argparse
import sys
import uuid

def text_to_uuid(name: str) -> str:
    return str(uuid.uuid5(uuid.NAMESPACE_DNS, name))

def main(argv=None) -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("text")
    args = ap.parse_args(argv)
    print(text_to_uuid(args.text))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
""".strip()
        + "\n",
        encoding="utf-8",
    )
    (tests / "test_cli.py").write_text(
        """
from src.app.cli import text_to_uuid

def test_uuid_deterministic():
    a = text_to_uuid("hello")
    b = text_to_uuid("hello")
    assert a == b and len(a) == 36
""".strip()
        + "\n",
        encoding="utf-8",
    )
    (scripts / "gates.py").write_text(
        """
#!/usr/bin/env python3
import shutil, subprocess, json

def run(cmd):
    res = subprocess.run(cmd, shell=True)
    return res.returncode

def main():
    tests = run("pytest -q") if shutil.which("pytest") else 0
    types = run("mypy .") if shutil.which("mypy") else 0
    lint = run("flake8 .") if shutil.which("flake8") else 0
    ok = (tests==0 and types==0 and lint==0)
    print(json.dumps({"ok": ok, "rc": {"pytest": tests, "mypy": types, "flake8": lint}}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
""".strip()
        + "\n",
        encoding="utf-8",
    )

def scaffold_from_brief(brief_path: Path, run_id: str) -> Path:
    brief = parse_brief_yaml(brief_path)
    slug = safe_slug(brief.get("id") or "repo")
    repo_dir = ROOT / "data" / "foundry" / "repos" / run_id / slug
    repo_dir.mkdir(parents=True, exist_ok=True)
    arche = (brief.get("archetype") or "python-cli").lower()
    if arche in ("python-cli", "python_cli", "python-cli|lib|web-min|codemod|bench-adapter"):
        write_python_cli_repo(repo_dir, brief)
    else:
        write_python_cli_repo(repo_dir, brief)
    # Write policies stub
    (repo_dir / "policies.yaml").write_text("risk_caps:\n  max_files: 3\n  max_lines: 200\n  max_risk: 0.2\n", encoding="utf-8")
    return repo_dir

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--brief", required=True)


def run(cmd):
    res = subprocess.run(cmd, shell=True)
    return res.returncode

def main():
    tests = run("pytest -q") if shutil.which("pytest") else 0
    types = run("mypy .") if shutil.which("mypy") else 0
    lint = run("flake8 .") if shutil.which("flake8") else 0
    ok = (tests==0 and types==0 and lint==0)
    print(json.dumps({"ok": ok, "rc": {"pytest": tests, "mypy": types, "flake8": lint}}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
""".strip()
        + "\n",
        encoding="utf-8",
    )

def scaffold_from_brief(brief_path: Path, run_id: str) -> Path:
    brief = parse_brief_yaml(brief_path)
    slug = safe_slug(brief.get("id") or "repo")
    repo_dir = ROOT / "data" / "foundry" / "repos" / run_id / slug
    repo_dir.mkdir(parents=True, exist_ok=True)
    arche = (brief.get("archetype") or "python-cli").lower()
    if arche in ("python-cli", "python_cli", "python-cli|lib|web-min|codemod|bench-adapter"):
        write_python_cli_repo(repo_dir, brief)
    else:
        write_python_cli_repo(repo_dir, brief)
    # Write policies stub
    (repo_dir / "policies.yaml").write_text("risk_caps:\n  max_files: 3\n  max_lines: 200\n  max_risk: 0.2\n", encoding="utf-8")
    return repo_dir

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--brief", required=True)
    ap.add_argument("--run-id", required=True)
    args = ap.parse_args()
    ensure_dirs()
    repo = scaffold_from_brief(Path(args.brief), args.run_id)
    print(str(repo))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


main

        + "\n",
        encoding="utf-8",
    )

def scaffold_from_brief(brief_path: Path, run_id: str) -> Path:
    brief = parse_brief_yaml(brief_path)
    slug = safe_slug(brief.get("id") or "repo")
    repo_dir = ROOT / "data" / "foundry" / "repos" / run_id / slug
    repo_dir.mkdir(parents=True, exist_ok=True)
    arche = (brief.get("archetype") or "python-cli").lower()
    if arche in ("python-cli", "python_cli", "python-cli|lib|web-min|codemod|bench-adapter"):
        write_python_cli_repo(repo_dir, brief)
    else:
        write_python_cli_repo(repo_dir, brief)
    # Write policies stub
    (repo_dir / "policies.yaml").write_text("risk_caps:\n  max_files: 3\n  max_lines: 200\n  max_risk: 0.2\n", encoding="utf-8")
    return repo_dir

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--brief", required=True)
    ap.add_argument("--run-id", required=True)
    args = ap.parse_args()
    ensure_dirs()
    repo = scaffold_from_brief(Path(args.brief), args.run_id)
    print(str(repo))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import shutil
import subprocess
from pathlib import Path

from .common import write_run_append

def run(cmd: str, cwd: Path) -> int:
    res = subprocess.run(cmd, shell=True, cwd=str(cwd))
    return int(res.returncode)

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--repo", required=True)
    ap.add_argument("--run-id")
    args = ap.parse_args()
    repo = Path(args.repo)

    tests_rc = run("pytest -q", repo) if shutil.which("pytest") else 0
    types_rc = run("mypy .", repo) if shutil.which("mypy") else 0
    lint_rc = run("flake8 .", repo) if shutil.which("flake8") else 0
    ok = (tests_rc == 0 and types_rc == 0 and lint_rc == 0)
    summary = {"ok": ok, "rc": {"pytest": tests_rc, "mypy": types_rc, "flake8": lint_rc}}
    run_id = args.run_id or "local"
    write_run_append(run_id, {"repo": str(repo), "phase": "verify", "result": summary})
    print(json.dumps(summary))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


run

#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import shutil
import subprocess
from pathlib import Path

from .common import write_run_append

def run(cmd: str, cwd: Path) -> int:
    res = subprocess.run(cmd, shell=True, cwd=str(cwd))
    return int(res.returncode)

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--repo", required=True)
    ap.add_argument("--run-id")
    args = ap.parse_args()
    repo = Path(args.repo)

    tests_rc = run("pytest -q", repo) if shutil.which("pytest") else 0
    types_rc = run("mypy .", repo) if shutil.which("mypy") else 0
    lint_rc = run("flake8 .", repo) if shutil.which("flake8") else 0
    ok = (tests_rc == 0 and types_rc == 0 and lint_rc == 0)
    summary = {"ok": ok, "rc": {"pytest": tests_rc, "mypy": types_rc, "flake8": lint_rc}}
    run_id = args.run_id or "local"
    write_run_append(run_id, {"repo": str(repo), "phase": "verify", "result": summary})
    print(json.dumps(summary))
    return 0


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
from pathlib import Path

from .common import ROOT, write_json

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--run-id", required=True)
    args = ap.parse_args()
    run_dir = ROOT / "data" / "traces" / "foundry_runs" / args.run_id
    run_dir.mkdir(parents=True, exist_ok=True)

    # Create placeholder unified traces and curated data updates
    unified = run_dir / "run.jsonl"
    pr_evidence = ROOT / "artifacts" / "foundry" / "default" / args.run_id / "pr_evidence.json"
    summary_md = ROOT / "artifacts" / "foundry" / "default" / args.run_id / "summary.md"
    pr_evidence.parent.mkdir(parents=True, exist_ok=True)
    write_json(pr_evidence, {"ok": True, "run": args.run_id})
    summary_md.write_text(f"# Foundry run {args.run_id}\n\nFiles: {unified if unified.exists() else 'none'}\n", encoding="utf-8")

    # Append manifest stub for SFT
    sft_manifest = ROOT / "data" / "sandbox" / "sft" / "manifest.json"
    sft_manifest.parent.mkdir(parents=True, exist_ok=True)
    if not sft_manifest.exists():
        write_json(sft_manifest, {"runs": []})
    try:
        obj = json.loads(sft_manifest.read_text(encoding="utf-8"))
    except Exception:
        obj = {"runs": []}
    obj.setdefault("runs", []).append({"run_id": args.run_id, "path": str(unified)})
    write_json(sft_manifest, obj)
    print(json.dumps({"ok": True, "run_id": args.run_id}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import subprocess
from datetime import datetime
from pathlib import Path

from .common import ROOT, ensure_dirs, write_run_append

def run(cmd: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
    return subprocess.run(cmd, shell=True, cwd=str(cwd) if cwd else None, capture_output=True, text=True)

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--n", type=int, default=5)
    ap.add_argument("--k", type=int, default=2)
    ap.add_argument("--tags", default="foundations/*")
    args = ap.parse_args()
    ensure_dirs()
    run_id = datetime.now().strftime("%Y%m%d-%H%M%S")

    # 1) Ideate
    ideate = run(
        f"python3 -m agi_dw.scripts.foundry.ideate --n {args.n} --tags '{args.tags}' --outdir data/foundry/backlog",
        ROOT,
    )
    briefs = [line.strip() for line in ideate.stdout.splitlines() if line.strip().endswith(".yaml")]
    write_run_append(run_id, {"phase": "ideate", "ok": ideate.returncode == 0, "briefs": briefs})

    scaffolded: list[str] = []
    # 2) Scaffold each brief (cap to N)
    for brief in briefs[: args.n]:
        r = run(
            f"python3 -m agi_dw.scripts.foundry.scaffold --brief {brief} --run-id {run_id}",
            ROOT,
        )
        repo_path = r.stdout.strip().splitlines()[-1] if r.stdout.strip() else ""
        if repo_path:
            scaffolded.append(repo_path)
        write_run_append(run_id, {"phase": "scaffold", "brief": brief, "repo": repo_path, "rc": r.returncode})

    verified: list[dict] = []
    # 3) Solve and verify (trivial baseline)
    for repo in scaffolded:
        run(
            f"python3 -m agi_dw.scripts.foundry.solve --repo {repo} --k {args.k} --run-id {run_id}",
            ROOT,
        )
        v = run(
            f"python3 -m agi_dw.scripts.foundry.verify --repo {repo} --run-id {run_id}",
            ROOT,
        )
        ok_json = {"ok": True}
        try:
            ok_json = json.loads(v.stdout.strip() or "{}")
        except Exception:
            pass
        verified.append({"repo": repo, "result": ok_json})

    # 4) Harvest
    run(f"python3 -m agi_dw.scripts.foundry.harvest --run-id {run_id}", ROOT)

    # 5) Curriculum
    run(
        f"python3 -m agi_dw.scripts.foundry.curriculum --tags '{args.tags}' --run-id {run_id}",
        ROOT,
    )

    # summary
    write_run_append(run_id, {"phase": "tick", "run_id": run_id, "repos": scaffolded, "verified": verified})
    print(json.dumps({"ok": True, "run_id": run_id, "n": len(scaffolded)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import subprocess
from datetime import datetime
from pathlib import Path

from .common import ROOT, ensure_dirs, write_run_append

def run(cmd: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
    return subprocess.run(cmd, shell=True, cwd=str(cwd) if cwd else None, capture_output=True, text=True)

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--n", type=int, default=5)
    ap.add_argument("--k", type=int, default=2)
    ap.add_argument("--tags", default="foundations/*")
    args = ap.parse_args()
    ensure_dirs()
    run_id = datetime.now().strftime("%Y%m%d-%H%M%S")

    # 1) Ideate
    ideate = run(
        f"python3 -m agi_dw.scripts.foundry.ideate --n {args.n} --tags '{args.tags}' --outdir data/foundry/backlog",
        ROOT,
    )
    briefs = [line.strip() for line in ideate.stdout.splitlines() if line.strip().endswith(".yaml")]
    write_run_append(run_id, {"phase": "ideate", "ok": ideate.returncode == 0, "briefs": briefs})

    scaffolded: list[str] = []


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import subprocess
from pathlib import Path

from .common import write_run_append, ROOT

def run_cmd(cmd: str, cwd: Path) -> int:
    res = subprocess.run(cmd, shell=True, cwd=str(cwd))
    return int(res.returncode)

def minimal_attempt(repo: Path) -> dict:
    # No-op baseline solve: run tests
    rc = run_cmd("pytest -q", repo)
    return {"attempt_idx": 0, "rc": rc, "pass": rc == 0}

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--repo", required=True)
    ap.add_argument("--caps")
    ap.add_argument("--k", type=int, default=2)
    ap.add_argument("--run-id")
    args = ap.parse_args()

    repo = Path(args.repo)
    repo.mkdir(parents=True, exist_ok=True)
    attempt = minimal_attempt(repo)
    run_id = args.run_id or "local"
    entry = {"repo": str(repo), "phase": "solve", "result": attempt}
    write_run_append(run_id, entry)
    print(json.dumps(entry))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import subprocess
from pathlib import Path

from .common import write_run_append, ROOT

def run_cmd(cmd: str, cwd: Path) -> int:
    res = subprocess.run(cmd, shell=True, cwd=str(cwd))
    return int(res.returncode)

def minimal_attempt(repo: Path) -> dict:
    # No-op baseline solve: run tests
    rc = run_cmd("pytest -q", repo)
    return {"attempt_idx": 0, "rc": rc, "pass": rc == 0}

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--repo", required=True)
    ap.add_argument("--caps")
    ap.add_argument("--k", type=int, default=2)
    ap.add_argument("--run-id")
    args = ap.parse_args()

    repo = Path(args.repo)
    repo.mkdir(parents=True, exist_ok=True)
    attempt = minimal_attempt(repo)
    run_id = args.run_id or "local"


#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import subprocess
from pathlib import Path

from .common import write_run_append, ROOT

def run_cmd(cmd: str, cwd: Path) -> int:
    res = subprocess.run(cmd, shell=True, cwd=str(cwd))
    return int(res.returncode)

def minimal_attempt(repo: Path) -> dict:
    # No-op baseline solve: run tests
    rc = run_cmd("pytest -q", repo)
    return {"attempt_idx": 0, "rc": rc, "pass": rc == 0}

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--repo", required=True)
    ap.add_argument("--caps")
    ap.add_argument("--k", type=int, default=2)
    ap.add_argument("--run-id")
    args = ap.parse_args()

    repo = Path(args.repo)
    repo.mkdir(parents=True, exist_ok=True)
    attempt = minimal_attempt(repo)
    run_id = args.run_id or "local"
    entry = {"repo": str(repo), "phase": "solve", "result": attempt}
    write_run_append(run_id, entry)
    print(json.dumps(entry))
    return 0


main

import argparse
import json
import subprocess
from pathlib import Path

from .common import write_run_append, ROOT

def run_cmd(cmd: str, cwd: Path) -> int:
    res = subprocess.run(cmd, shell=True, cwd=str(cwd))
    return int(res.returncode)

def minimal_attempt(repo: Path) -> dict:
    # No-op baseline solve: run tests
    rc = run_cmd("pytest -q", repo)
    return {"attempt_idx": 0, "rc": rc, "pass": rc == 0}

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--repo", required=True)
    ap.add_argument("--caps")
    ap.add_argument("--k", type=int, default=2)
    ap.add_argument("--run-id")
    args = ap.parse_args()

    repo = Path(args.repo)
    repo.mkdir(parents=True, exist_ok=True)
    attempt = minimal_attempt(repo)
    run_id = args.run_id or "local"
    entry = {"repo": str(repo), "phase": "solve", "result": attempt}
    write_run_append(run_id, entry)
    print(json.dumps(entry))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations

import argparse
from datetime import datetime
from pathlib import Path

from .common import ROOT, RepoBrief, default_briefs, ensure_dirs

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--tags", default="foundations/*")
    ap.add_argument("--n", type=int, default=5)
    ap.add_argument("--outdir", default=str(ROOT / "data" / "foundry" / "backlog"))
    args = ap.parse_args()

    ensure_dirs()
    outdir = Path(args.outdir)
    outdir.mkdir(parents=True, exist_ok=True)
    date_str = datetime.now().strftime("%Y-%m-%d")
    briefs = default_briefs(args.n, date_str)
    for brief in briefs:
        name = f"{date_str}-{brief.id}.yaml"
        (outdir / name).write_text(brief.to_yaml(), encoding="utf-8")
        print(str(outdir / name))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations
import logging

import json
from pathlib import Path
from typing import Dict, Any

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    pack_path = root / "data" / "sandbox" / "tmp" / "prompt_pack.json"
    out = root / "data" / "sandbox" / "tmp" / "name_api_synth.json"
    pack = json.loads(pack_path.read_text(encoding="utf-8")) if pack_path.exists() else {}
    # Minimal: propose a module/name tuple per file
    items = []
    for fp in (pack.get("files", []) if isinstance(pack, dict) else []):
        base = Path(fp).stem
        items.append({"file": fp, "proposed_name": f"{base}_adapter", "api": {"functions": []}})
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps({"ok": True, "items": items}, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out), "n": len(items)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


#!/usr/bin/env python3
from __future__ import annotations
import logging

import json
from pathlib import Path

def main() -> int:
    root = Path(__file__).resolve().parents[2]
    ev = root / "artifacts" / "pr_evidence.json"
    out = root / "artifacts" / "pr_narrative.md"
    try:
        obj = json.loads(ev.read_text(encoding="utf-8")) if ev.exists() else {}
    except Exception:
        obj = {}
    lines = [
        "# PR Summary",
        f"Commit: {obj.get('commit','')}",
        f"Files changed: {obj.get('files_changed',0)}",
        f"Risk score: {obj.get('risk',0.0)}",
        f"Tests exit: {(obj.get('tests') or {}).get('code',127)}; Types exit: {(obj.get('types') or {}).get('code',127)}",
    ]
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text("\n".join(lines) + "\n", encoding="utf-8")
    print(json.dumps({"ok": True, "out": str(out)}))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


import logging
import argparse
import json
from pathlib import Path
from typing import Any, Dict, List

import sys

from agi_dw.core.verifier.llm_verifier import verify_trace_snippet

def process(
	in_jsonl: Path,
	out_jsonl: Path,
	model: str,
	timeout_sec: int,
	use_llm: bool,
	require_llm: bool,
	backend: str,
	log_prompts: bool,
	adapter_dir: str | None,
	structured_mode: str,
	add_wm_prior: bool,
	wm_model_path: str,
	max_items: int | None,
	progress_every: int,
	warmup: bool,
	workers: int,
	batch_size: int,
) -> int:
	out_jsonl.parent.mkdir(parents=True, exist_ok=True)

	# Optional warmup: verify the first non-empty line once to load the model
	if warmup:
		for line in in_jsonl.read_text(encoding="utf-8").splitlines():
			if line.strip():
				obj = json.loads(line)
				_ = verify_trace_snippet(obj, model=model, timeout_sec=timeout_sec, use_llm=use_llm, require_llm=require_llm, backend=backend, log_prompts=log_prompts, adapter_dir=adapter_dir, structured_mode=structured_mode)
				break

	# Read all lines (bounded by max_items) to allow chunking
	lines: List[str] = []
	with in_jsonl.open("r", encoding="utf-8") as fin:
		for line in fin:
			if not line.strip():
				continue
			lines.append(line)
			if max_items is not None and len(lines) >= max_items:
				break
	# Optional HF batch path to reduce per-call overhead
	use_hf_batch = (backend == "hf") and use_llm and (workers == 1) and (batch_size and batch_size > 1)

	if use_hf_batch:
		try:
			from agi_dw.core.llm.hf_client import HFClient  # type: ignore
			from agi_dw.core.verifier.llm_verifier import _robust_struct_parse  # type: ignore
			client = HFClient.get_cached(model)
			batched_results: List[Dict[str, Any]] = []
			# Build prompts in chunks
			def _mk_prompt(obj: Dict[str, Any]) -> str:
				return (
					"You are a strict verifier. Given a trace snippet, output ONLY a YAML mapping with keys"
					" success_prob (float 0..1), risk (float 0..1), critique (short string). No prose.\n"
					"Example:\n"
					"success_prob: 0.72\n"
					"risk: 0.18\n"
					"critique: brief reason\n\n"
					"Trace (YAML or JSON content below; do NOT echo it):\n" + str(obj)
				)
			objs = [json.loads(s) for s in lines]
			# Optional WM prior loader (lazy)
			wm = None
			if add_wm_prior:
				try:
					from agi_dw.core.world_model.api import WorldModelPrior  # type: ignore
					p = Path(wm_model_path)
					if p.exists():
						wm = WorldModelPrior.load(p)
				except Exception:
					wm = None
			for i in range(0, len(objs), batch_size):
				chunk = objs[i : i + batch_size]
				prompts = [_mk_prompt(o) for o in chunk]
				texts = client.generate_batch(prompts, max_new_tokens=200, temperature=0.0)
				for o, t in zip(chunk, texts):
					parsed = _robust_struct_parse(t)
					if not ("success_prob" in parsed and "risk" in parsed):
						# Fallback to single verify for this item
						v = verify_trace_snippet(o, model=model, timeout_sec=timeout_sec, use_llm=use_llm, require_llm=require_llm, backend=backend, log_prompts=log_prompts, adapter_dir=adapter_dir, structured_mode=structured_mode)
						o["critique"] = {
							"issues": o.get("critique", {}).get("issues", []),
							"risk": float(v.get("risk", 0.5)),
							"proposal": str(v.get("critique", "")),
						}
					else:
						o["critique"] = {
							"issues": o.get("critique", {}).get("issues", []),
							"risk": float(parsed.get("risk", 0.5)),
							"proposal": str(parsed.get("critique", "")),
						}
					# Optional WM prior per item
					if wm is not None:
						try:
							prior = wm.predict_prior(o.get("obs", {}), o.get("plan", {}), o.get("action", {}))
							if isinstance(prior, dict):
								o["wm_prior"] = prior
						except Exception:
							pass
					batched_results.append(o)
					if progress_every > 0 and len(batched_results) % progress_every == 0:
						print(f"progress: {len(batched_results)} items verified...")
			with out_jsonl.open("w", encoding="utf-8") as fout:
				for obj in batched_results:
					fout.write(json.dumps(obj, ensure_ascii=False) + "\n")
			return len(batched_results)
		except Exception:
			# Fall back to non-batch path below
			pass

	# Process sequentially or via workers
	def _verify_one(s: str) -> Dict[str, Any]:
		obj: Dict[str, Any] = json.loads(s)
		v = verify_trace_snippet(obj, model=model, timeout_sec=timeout_sec, use_llm=use_llm, require_llm=require_llm, backend=backend, log_prompts=log_prompts, adapter_dir=adapter_dir, structured_mode=structured_mode)
		obj["critique"] = {
			"issues": obj.get("critique", {}).get("issues", []),
			"risk": float(v.get("risk", 0.5)),
			"proposal": str(v.get("critique", "")),
		}
		if add_wm_prior:
			try:
				from agi_dw.core.world_model.api import WorldModelPrior  # type: ignore
				p = Path(wm_model_path)
				if p.exists():
					wm = WorldModelPrior.load(p)
					prior = wm.predict_prior(obj.get("obs", {}), obj.get("plan", {}), obj.get("action", {}))
					if isinstance(prior, dict):
						obj["wm_prior"] = prior
			except Exception:
				pass
		return obj
	results: List[Dict[str, Any]] = []
	if workers and workers > 1:
		try:
			from concurrent.futures import ThreadPoolExecutor
			with ThreadPoolExecutor(max_workers=workers) as ex:
				for i, obj in enumerate(ex.map(_verify_one, lines)):
					results.append(obj)
					if progress_every > 0 and (i + 1) % progress_every == 0:
						print(f"progress: {i+1} items verified...")
		except Exception:
			# Fallback to sequential
			for i, s in enumerate(lines):
				results.append(_verify_one(s))
				if progress_every > 0 and (i + 1) % progress_every == 0:
					print(f"progress: {i+1} items verified...")
	else:
		for i, s in enumerate(lines):
			results.append(_verify_one(s))
			if progress_every > 0 and (i + 1) % progress_every == 0:
				print(f"progress: {i+1} items verified...")
	with out_jsonl.open("w", encoding="utf-8") as fout:
		for obj in results:
			fout.write(json.dumps(obj, ensure_ascii=False) + "\n")
	count = len(results)
	return count

def main() -> int:
	parser = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	parser.add_argument("src", nargs="?", default=str(root / "data" / "traces" / "seed_os_cli.jsonl"))
	parser.add_argument("dst", nargs="?", default=str(root / "data" / "traces" / "seed_os_cli.verified.jsonl"))
	parser.add_argument("--model", default="gemma3:12b")
	parser.add_argument("--timeout", type=int, default=8)
	parser.add_argument("--backend", choices=["hf"], default="hf")
	parser.add_argument("--no-llm", action="store_true")
	parser.add_argument("--require-llm", action="store_true", default=True)
	parser.add_argument("--log-prompts", action="store_true")
	parser.add_argument("--adapter", default=None, help="HF backend only: PEFT adapter directory for verifier (auto-detected if not provided)")
	parser.add_argument("--max", type=int, default=None, help="max items to process")
	parser.add_argument("--structured", choices=["none", "json"], default="json", help="Enable structured decoding for verifier (HF only)")
	parser.add_argument("--wm-prior", action="store_true", help="Augment verified traces with world model priors if available")
	parser.add_argument("--wm-model", default=str(root / "models" / "wm_mlp" / "wm_mlp.joblib"))
	parser.add_argument("--progress-every", type=int, default=5, help="log every N items")
	parser.add_argument("--warmup", action="store_true", help="do one warmup verify first")
	parser.add_argument("--workers", type=int, default=1, help="number of worker threads for verification")
	parser.add_argument("--batch-size", type=int, default=0, help="HF backend only: batch size for batched generation (workers must be 1)")
	args = parser.parse_args()
	# Auto-detect adapter if not provided and backend is HF
	if args.backend == "hf" and (args.adapter is None or args.adapter == ""):
		try:
			default_adapter = Path(__file__).resolve().parents[1] / "models" / "verifier_qlora"
			if default_adapter.exists():
				args.adapter = str(default_adapter)
		except Exception:
			pass

	n = process(
		Path(args.src),
		Path(args.dst),
		model=args.model,
		timeout_sec=args.timeout,
		use_llm=(not args.no_llm),
		require_llm=args.require_llm,
		backend=args.backend,
		log_prompts=args.log_prompts,
		adapter_dir=args.adapter,
		structured_mode=args.structured,
		add_wm_prior=args.wm_prior,
		wm_model_path=args.wm_model,
		max_items=args.max,
		progress_every=args.progress_every,
		warmup=args.warmup,
		workers=args.workers,
		batch_size=args.batch_size,
	)
	print(f"Verified {n} traces -> {args.dst}")
	return 0

if __name__ == "__main__":
	raise SystemExit(main())


process

import logging
import argparse
import json
from pathlib import Path
from typing import Any, Dict, List

import sys

from agi_dw.core.verifier.llm_verifier import verify_trace_snippet

def process(
	in_jsonl: Path,
	out_jsonl: Path,
	model: str,
	timeout_sec: int,
	use_llm: bool,
	require_llm: bool,
	backend: str,
	log_prompts: bool,
	adapter_dir: str | None,
	structured_mode: str,
	add_wm_prior: bool,
	wm_model_path: str,
	max_items: int | None,
	progress_every: int,
	warmup: bool,
	workers: int,
	batch_size: int,
) -> int:
	out_jsonl.parent.mkdir(parents=True, exist_ok=True)

	# Optional warmup: verify the first non-empty line once to load the model
	if warmup:
		for line in in_jsonl.read_text(encoding="utf-8").splitlines():
			if line.strip():
				obj = json.loads(line)
				_ = verify_trace_snippet(obj, model=model, timeout_sec=timeout_sec, use_llm=use_llm, require_llm=require_llm, backend=backend, log_prompts=log_prompts, adapter_dir=adapter_dir, structured_mode=structured_mode)
				break

	# Read all lines (bounded by max_items) to allow chunking
	lines: List[str] = []
	with in_jsonl.open("r", encoding="utf-8") as fin:
		for line in fin:
			if not line.strip():
				continue
			lines.append(line)
			if max_items is not None and len(lines) >= max_items:
				break
	# Optional HF batch path to reduce per-call overhead
	use_hf_batch = (backend == "hf") and use_llm and (workers == 1) and (batch_size and batch_size > 1)

	if use_hf_batch:
		try:
			from agi_dw.core.llm.hf_client import HFClient  # type: ignore
			from agi_dw.core.verifier.llm_verifier import _robust_struct_parse  # type: ignore
			client = HFClient.get_cached(model)
			batched_results: List[Dict[str, Any]] = []
			# Build prompts in chunks
			def _mk_prompt(obj: Dict[str, Any]) -> str:
				return (
					"You are a strict verifier. Given a trace snippet, output ONLY a YAML mapping with keys"
					" success_prob (float 0..1), risk (float 0..1), critique (short string). No prose.\n"
					"Example:\n"
					"success_prob: 0.72\n"
					"risk: 0.18\n"
					"critique: brief reason\n\n"
					"Trace (YAML or JSON content below; do NOT echo it):\n" + str(obj)
				)
			objs = [json.loads(s) for s in lines]
			# Optional WM prior loader (lazy)
			wm = None
			if add_wm_prior:
				try:
					from agi_dw.core.world_model.api import WorldModelPrior  # type: ignore
					p = Path(wm_model_path)
					if p.exists():
						wm = WorldModelPrior.load(p)
				except Exception:
					wm = None
			for i in range(0, len(objs), batch_size):
				chunk = objs[i : i + batch_size]
				prompts = [_mk_prompt(o) for o in chunk]
				texts = client.generate_batch(prompts, max_new_tokens=200, temperature=0.0)
				for o, t in zip(chunk, texts):
					parsed = _robust_struct_parse(t)
					if not ("success_prob" in parsed and "risk" in parsed):
						# Fallback to single verify for this item
						v = verify_trace_snippet(o, model=model, timeout_sec=timeout_sec, use_llm=use_llm, require_llm=require_llm, backend=backend, log_prompts=log_prompts, adapter_dir=adapter_dir, structured_mode=structured_mode)
						o["critique"] = {
							"issues": o.get("critique", {}).get("issues", []),
							"risk": float(v.get("risk", 0.5)),
							"proposal": str(v.get("critique", "")),
						}
					else:
						o["critique"] = {
							"issues": o.get("critique", {}).get("issues", []),
							"risk": float(parsed.get("risk", 0.5)),
							"proposal": str(parsed.get("critique", "")),
						}
					# Optional WM prior per item
					if wm is not None:
						try:
							prior = wm.predict_prior(o.get("obs", {}), o.get("plan", {}), o.get("action", {}))
							if isinstance(prior, dict):
								o["wm_prior"] = prior
						except Exception:
							pass
					batched_results.append(o)
					if progress_every > 0 and len(batched_results) % progress_every == 0:
						print(f"progress: {len(batched_results)} items verified...")
			with out_jsonl.open("w", encoding="utf-8") as fout:
				for obj in batched_results:
					fout.write(json.dumps(obj, ensure_ascii=False) + "\n")
			return len(batched_results)
		except Exception:
			# Fall back to non-batch path below
			pass

	# Process sequentially or via workers
	def _verify_one(s: str) -> Dict[str, Any]:
		obj: Dict[str, Any] = json.loads(s)
		v = verify_trace_snippet(obj, model=model, timeout_sec=timeout_sec, use_llm=use_llm, require_llm=require_llm, backend=backend, log_prompts=log_prompts, adapter_dir=adapter_dir, structured_mode=structured_mode)
		obj["critique"] = {
			"issues": obj.get("critique", {}).get("issues", []),
			"risk": float(v.get("risk", 0.5)),
			"proposal": str(v.get("critique", "")),
		}
		if add_wm_prior:
			try:
				from agi_dw.core.world_model.api import WorldModelPrior  # type: ignore
				p = Path(wm_model_path)
				if p.exists():
					wm = WorldModelPrior.load(p)
					prior = wm.predict_prior(obj.get("obs", {}), obj.get("plan", {}), obj.get("action", {}))
					if isinstance(prior, dict):
						obj["wm_prior"] = prior
			except Exception:
				pass
		return obj
	results: List[Dict[str, Any]] = []
	if workers and workers > 1:
		try:
			from concurrent.futures import ThreadPoolExecutor
			with ThreadPoolExecutor(max_workers=workers) as ex:
				for i, obj in enumerate(ex.map(_verify_one, lines)):
					results.append(obj)
					if progress_every > 0 and (i + 1) % progress_every == 0:
						print(f"progress: {i+1} items verified...")
		except Exception:
			# Fallback to sequential
			for i, s in enumerate(lines):
				results.append(_verify_one(s))
				if progress_every > 0 and (i + 1) % progress_every == 0:
					print(f"progress: {i+1} items verified...")
	else:
		for i, s in enumerate(lines):
			results.append(_verify_one(s))
			if progress_every > 0 and (i + 1) % progress_every == 0:
				print(f"progress: {i+1} items verified...")
	with out_jsonl.open("w", encoding="utf-8") as fout:
		for obj in results:
			fout.write(json.dumps(obj, ensure_ascii=False) + "\n")
	count = len(results)
	return count

def main() -> int:
	parser = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	parser.add_argument("src", nargs="?", default=str(root / "data" / "traces" / "seed_os_cli.jsonl"))
	parser.add_argument("dst", nargs="?", default=str(root / "data" / "traces" / "seed_os_cli.verified.jsonl"))
	parser.add_argument("--model", default="gemma3:12b")
	parser.add_argument("--timeout", type=int, default=8)
	parser.add_argument("--backend", choices=["hf"], default="hf")
	parser.add_argument("--no-llm", action="store_true")
	parser.add_argument("--require-llm", action="store_true", default=True)
	parser.add_argument("--log-prompts", action="store_true")
	parser.add_argument("--adapter", default=None, help="HF backend only: PEFT adapter directory for verifier (auto-detected if not provided)")
	parser.add_argument("--max", type=int, default=None, help="max items to process")
	parser.add_argument("--structured", choices=["none", "json"], default="json", help="Enable structured decoding for verifier (HF only)")
	parser.add_argument("--wm-prior", action="store_true", help="Augment verified traces with world model priors if available")
	parser.add_argument("--wm-model", default=str(root / "models" / "wm_mlp" / "wm_mlp.joblib"))
	parser.add_argument("--progress-every", type=int, default=5, help="log every N items")
	parser.add_argument("--warmup", action="store_true", help="do one warmup verify first")


main

					if progress_every > 0 and (i + 1) % progress_every == 0:
						print(f"progress: {i+1} items verified...")
		except Exception:
			# Fallback to sequential
			for i, s in enumerate(lines):
				results.append(_verify_one(s))
				if progress_every > 0 and (i + 1) % progress_every == 0:
					print(f"progress: {i+1} items verified...")
	else:
		for i, s in enumerate(lines):
			results.append(_verify_one(s))
			if progress_every > 0 and (i + 1) % progress_every == 0:
				print(f"progress: {i+1} items verified...")
	with out_jsonl.open("w", encoding="utf-8") as fout:
		for obj in results:
			fout.write(json.dumps(obj, ensure_ascii=False) + "\n")
	count = len(results)
	return count

def main() -> int:
	parser = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	parser.add_argument("src", nargs="?", default=str(root / "data" / "traces" / "seed_os_cli.jsonl"))
	parser.add_argument("dst", nargs="?", default=str(root / "data" / "traces" / "seed_os_cli.verified.jsonl"))
	parser.add_argument("--model", default="gemma3:12b")
	parser.add_argument("--timeout", type=int, default=8)
	parser.add_argument("--backend", choices=["hf"], default="hf")
	parser.add_argument("--no-llm", action="store_true")
	parser.add_argument("--require-llm", action="store_true", default=True)
	parser.add_argument("--log-prompts", action="store_true")
	parser.add_argument("--adapter", default=None, help="HF backend only: PEFT adapter directory for verifier (auto-detected if not provided)")
	parser.add_argument("--max", type=int, default=None, help="max items to process")
	parser.add_argument("--structured", choices=["none", "json"], default="json", help="Enable structured decoding for verifier (HF only)")
	parser.add_argument("--wm-prior", action="store_true", help="Augment verified traces with world model priors if available")
	parser.add_argument("--wm-model", default=str(root / "models" / "wm_mlp" / "wm_mlp.joblib"))
	parser.add_argument("--progress-every", type=int, default=5, help="log every N items")
	parser.add_argument("--warmup", action="store_true", help="do one warmup verify first")
	parser.add_argument("--workers", type=int, default=1, help="number of worker threads for verification")
	parser.add_argument("--batch-size", type=int, default=0, help="HF backend only: batch size for batched generation (workers must be 1)")
	args = parser.parse_args()
	# Auto-detect adapter if not provided and backend is HF
	if args.backend == "hf" and (args.adapter is None or args.adapter == ""):
		try:
			default_adapter = Path(__file__).resolve().parents[1] / "models" / "verifier_qlora"
			if default_adapter.exists():
				args.adapter = str(default_adapter)
		except Exception:
			pass

	n = process(
		Path(args.src),
		Path(args.dst),
		model=args.model,
		timeout_sec=args.timeout,
		use_llm=(not args.no_llm),
		require_llm=args.require_llm,
		backend=args.backend,
		log_prompts=args.log_prompts,
		adapter_dir=args.adapter,
		structured_mode=args.structured,
		add_wm_prior=args.wm_prior,
		wm_model_path=args.wm_model,
		max_items=args.max,
		progress_every=args.progress_every,
		warmup=args.warmup,
		workers=args.workers,
		batch_size=args.batch_size,
	)
	print(f"Verified {n} traces -> {args.dst}")
	return 0

if __name__ == "__main__":
	raise SystemExit(main())


					# Optional WM prior per item
					if wm is not None:
						try:
							prior = wm.predict_prior(o.get("obs", {}), o.get("plan", {}), o.get("action", {}))
							if isinstance(prior, dict):
								o["wm_prior"] = prior
						except Exception:
							pass
					batched_results.append(o)
					if progress_every > 0 and len(batched_results) % progress_every == 0:
						print(f"progress: {len(batched_results)} items verified...")
			with out_jsonl.open("w", encoding="utf-8") as fout:
				for obj in batched_results:
					fout.write(json.dumps(obj, ensure_ascii=False) + "\n")
			return len(batched_results)
		except Exception:
			# Fall back to non-batch path below
			pass

	# Process sequentially or via workers
	def _verify_one(s: str) -> Dict[str, Any]:
		obj: Dict[str, Any] = json.loads(s)
		v = verify_trace_snippet(obj, model=model, timeout_sec=timeout_sec, use_llm=use_llm, require_llm=require_llm, backend=backend, log_prompts=log_prompts, adapter_dir=adapter_dir, structured_mode=structured_mode)
		obj["critique"] = {
			"issues": obj.get("critique", {}).get("issues", []),
			"risk": float(v.get("risk", 0.5)),
			"proposal": str(v.get("critique", "")),
		}
		if add_wm_prior:
			try:
				from agi_dw.core.world_model.api import WorldModelPrior  # type: ignore
				p = Path(wm_model_path)
				if p.exists():
					wm = WorldModelPrior.load(p)
					prior = wm.predict_prior(obj.get("obs", {}), obj.get("plan", {}), obj.get("action", {}))
					if isinstance(prior, dict):
						obj["wm_prior"] = prior
			except Exception:
				pass
		return obj
	results: List[Dict[str, Any]] = []
	if workers and workers > 1:
		try:
			from concurrent.futures import ThreadPoolExecutor
			with ThreadPoolExecutor(max_workers=workers) as ex:
				for i, obj in enumerate(ex.map(_verify_one, lines)):
					results.append(obj)
					if progress_every > 0 and (i + 1) % progress_every == 0:
						print(f"progress: {i+1} items verified...")
		except Exception:
			# Fallback to sequential
			for i, s in enumerate(lines):
				results.append(_verify_one(s))
				if progress_every > 0 and (i + 1) % progress_every == 0:
					print(f"progress: {i+1} items verified...")
	else:
		for i, s in enumerate(lines):
			results.append(_verify_one(s))
			if progress_every > 0 and (i + 1) % progress_every == 0:
				print(f"progress: {i+1} items verified...")


	# Read all lines (bounded by max_items) to allow chunking
	lines: List[str] = []
	with in_jsonl.open("r", encoding="utf-8") as fin:
		for line in fin:
			if not line.strip():
				continue
			lines.append(line)
			if max_items is not None and len(lines) >= max_items:
				break
	# Optional HF batch path to reduce per-call overhead
	use_hf_batch = (backend == "hf") and use_llm and (workers == 1) and (batch_size and batch_size > 1)

	if use_hf_batch:
		try:
			from agi_dw.core.llm.hf_client import HFClient  # type: ignore
			from agi_dw.core.verifier.llm_verifier import _robust_struct_parse  # type: ignore
			client = HFClient.get_cached(model)
			batched_results: List[Dict[str, Any]] = []
			# Build prompts in chunks
			def _mk_prompt(obj: Dict[str, Any]) -> str:
				return (
					"You are a strict verifier. Given a trace snippet, output ONLY a YAML mapping with keys"
					" success_prob (float 0..1), risk (float 0..1), critique (short string). No prose.\n"
					"Example:\n"
					"success_prob: 0.72\n"
					"risk: 0.18\n"
					"critique: brief reason\n\n"
					"Trace (YAML or JSON content below; do NOT echo it):\n" + str(obj)
				)
			objs = [json.loads(s) for s in lines]
			# Optional WM prior loader (lazy)
			wm = None
			if add_wm_prior:
				try:
					from agi_dw.core.world_model.api import WorldModelPrior  # type: ignore
					p = Path(wm_model_path)
					if p.exists():
						wm = WorldModelPrior.load(p)
				except Exception:
					wm = None
			for i in range(0, len(objs), batch_size):
				chunk = objs[i : i + batch_size]
				prompts = [_mk_prompt(o) for o in chunk]
				texts = client.generate_batch(prompts, max_new_tokens=200, temperature=0.0)
				for o, t in zip(chunk, texts):
					parsed = _robust_struct_parse(t)
					if not ("success_prob" in parsed and "risk" in parsed):
						# Fallback to single verify for this item
						v = verify_trace_snippet(o, model=model, timeout_sec=timeout_sec, use_llm=use_llm, require_llm=require_llm, backend=backend, log_prompts=log_prompts, adapter_dir=adapter_dir, structured_mode=structured_mode)


import logging
import json
from pathlib import Path
import sys

try:
	import jsonschema  # type: ignore
except Exception:
	jsonschema = None

def main() -> int:
	if len(sys.argv) < 2:
		print("Usage: validate_refactor_plan.py <refactor_plan.json>")
		return 2
	root = Path(__file__).resolve().parents[1]
	schema_path = root / "docs" / "schemas" / "refactor_plan.schema.json"
	plan_path = Path(sys.argv[1])
	if not schema_path.exists():
		print(f"Schema not found: {schema_path}")
		return 2
	if not plan_path.exists():
		print(f"Plan file not found: {plan_path}")
		return 2
	if jsonschema is None:
		print("jsonschema not installed. Please pip install jsonschema.")
		return 1
	schema = json.loads(schema_path.read_text(encoding="utf-8"))
	data = json.loads(plan_path.read_text(encoding="utf-8"))
	jsonschema.validate(data, schema)  # type: ignore[arg-type]
	print("OK")
	return 0

if __name__ == "__main__":
	raise SystemExit(main())


import logging
import argparse
import json
from pathlib import Path

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	ap.add_argument("--registry", default=str(root / "models" / "base_models.json"))
	ap.add_argument("--outdir", default=str(root / "models" / "bases"))
	args = ap.parse_args()

	reg_path = Path(args.registry)

	try:
		obj = json.loads(reg_path.read_text(encoding="utf-8"))
	except Exception:
		print("{}")
		return 0

	outdir = Path(args.outdir)
	outdir.mkdir(parents=True, exist_ok=True)

	changed = False
	for role, rec in (obj.items() if isinstance(obj, dict) else []):
		hf_id = str((rec or {}).get("hf_id", ""))
		if not hf_id:
			continue
		# Snapshot using huggingface_hub snapshot_download if available
		local_dir = outdir / role
		try:
			from huggingface_hub import snapshot_download  # type: ignore
			local_dir.mkdir(parents=True, exist_ok=True)
			p = snapshot_download(repo_id=hf_id, local_dir=str(local_dir), local_dir_use_symlinks=False)
			rec["local_path"] = str(local_dir)
			changed = True
		except Exception:
			# best effort only
			continue

	if changed:
		reg_path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
	print(json.dumps({"ok": True, "registry": str(reg_path)}))
	return 0

if __name__ == "__main__":
	import sys
	sys.exit(main())


#!/usr/bin/env python3
import logging
import argparse
import difflib
import json
import subprocess
import tempfile
from pathlib import Path

def parse_args():
	ap = argparse.ArgumentParser(description="Validate minimal patch compiles and tests pass in sandbox")
	ap.add_argument("--orig", required=True, help="Path to original code file")
	ap.add_argument("--edited", required=True, help="Path to edited code file")
	ap.add_argument("--tests", required=True, help="Path to tests file (asserts)")
	ap.add_argument("--timeout", type=int, default=12)
	ap.add_argument("--memmb", type=int, default=256)
	return ap.parse_args()

def main() -> int:
	args = parse_args()
	orig = Path(args.orig).read_text(encoding="utf-8")
	ed = Path(args.edited).read_text(encoding="utf-8")
	# Compute unified diff for display
	diff = "".join(difflib.unified_diff(orig.splitlines(keepends=True), ed.splitlines(keepends=True), fromfile="orig", tofile="edited"))
	# Validate edited passes tests in sandbox
	with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as tf_code:
		tf_code.write(ed)
		code_path = tf_code.name
	with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as tf_tests:
		tf_tests.write(Path(args.tests).read_text(encoding="utf-8"))
		test_path = tf_tests.name
	p = subprocess.run([
		"python3", str(Path(__file__).resolve().parents[0] / "sandbox_exec.py"),
		"--code", code_path, "--tests", test_path, "--timeout", str(int(args.timeout)), "--memmb", str(int(args.memmb)), "--coverage"
	], capture_output=True, text=True, timeout=max(5, int(args.timeout) + 5))
	ok = p.returncode == 0
	obj = {}
	try:
		last = (p.stdout or "").strip().splitlines()[-1]
		obj = json.loads(last) if last.startswith("{") else {}
	except Exception:
		obj = {}
	print(json.dumps({
		"ok": ok and bool(obj.get("success", ok)),
		"coverage": obj.get("coverage"),
		"diff": diff,
		"stdout_tail": (p.stdout or "").splitlines()[-10:],
		"stderr_tail": (p.stderr or "").splitlines()[-10:],
	}))
	return 0 if ok and bool(obj.get("success", ok)) else 1

if __name__ == "__main__":
	raise SystemExit(main())


#!/usr/bin/env python3
import logging
import argparse
import difflib
import json
import subprocess
import tempfile
from pathlib import Path

def parse_args():
	ap = argparse.ArgumentParser(description="Validate minimal patch compiles and tests pass in sandbox")
	ap.add_argument("--orig", required=True, help="Path to original code file")
	ap.add_argument("--edited", required=True, help="Path to edited code file")
	ap.add_argument("--tests", required=True, help="Path to tests file (asserts)")
	ap.add_argument("--timeout", type=int, default=12)
	ap.add_argument("--memmb", type=int, default=256)
	return ap.parse_args()

def main() -> int:
	args = parse_args()
	orig = Path(args.orig).read_text(encoding="utf-8")
	ed = Path(args.edited).read_text(encoding="utf-8")
	# Compute unified diff for display
	diff = "".join(difflib.unified_diff(orig.splitlines(keepends=True), ed.splitlines(keepends=True), fromfile="orig", tofile="edited"))
	# Validate edited passes tests in sandbox
	with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as tf_code:
		tf_code.write(ed)
		code_path = tf_code.name
	with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as tf_tests:
		tf_tests.write(Path(args.tests).read_text(encoding="utf-8"))
		test_path = tf_tests.name
	p = subprocess.run([
		"python3", str(Path(__file__).resolve().parents[0] / "sandbox_exec.py"),
		"--code", code_path, "--tests", test_path, "--timeout", str(int(args.timeout)), "--memmb", str(int(args.memmb)), "--coverage"
	], capture_output=True, text=True, timeout=max(5, int(args.timeout) + 5))
	ok = p.returncode == 0


import logging
import argparse
from pathlib import Path

from bench.os_cli.tasks import generate_seed_traces

def main() -> None:
	root = Path(__file__).resolve().parents[1]
	ap = argparse.ArgumentParser()
	ap.add_argument("--episodes", type=int, default=20)
	ap.add_argument("--sandbox", default=str(root / "data" / "sandbox"))
	ap.add_argument("--out", default=str(root / "data" / "traces" / "seed_os_cli.jsonl"))
	args = ap.parse_args()

	total, path = generate_seed_traces(sandbox_dir=str(args.sandbox), out_jsonl=str(args.out), episodes=int(args.episodes))
	print(f"Generated {total} traces -> {path}")

if __name__ == "__main__":
	main()


import logging
import argparse
from pathlib import Path
from typing import List, Tuple, Dict, Any
from concurrent.futures import ThreadPoolExecutor, as_completed
try:
	import yaml  # type: ignore
except Exception:
	yaml = None

from agi_dw.bench.web_dom.runner import fetch_text, click_then_fetch, form_fill_fetch
from agi_dw.bench.common.trace import build_trace, write_jsonl

def main() -> None:
	parser = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	parser.add_argument("--out", default=str(root / "data" / "traces" / "web_dom.jsonl"))
	parser.add_argument("--seeds", default=str(root / "data" / "dom_seeds.yaml"))
	parser.add_argument("--workers", type=int, default=4)
	parser.add_argument("--reuse-browser", action="store_true")
	parser.add_argument("--only-yaml", action="store_true", help="If set, use only YAML-provided seeds")
	args = parser.parse_args()

	tasks: List[tuple[str, str, str | List[str]]] = []
	# Optional per-task metadata (e.g., stability tags)
	task_meta: Dict[str, Dict[str, Any]] = {}
	if not args.only_yaml:
		# Default seed set
		tasks = [
			("web-dom-h1", "https://example.com", "h1"),
			("web-dom-p", "https://example.com", "p"),
			("web-dom-missing", "https://example.com", "#does-not-exist"),
			("web-dom-wikipedia", "https://en.wikipedia.org/wiki/Alan_Turing", "#firstHeading"),
			("web-dom-wikipedia-infobox", "https://en.wikipedia.org/wiki/Alan_Turing", ".infobox"),
			("web-dom-wikipedia-content", "https://en.wikipedia.org/wiki/Alan_Turing", "#mw-content-text p"),
			("web-dom-iana", "https://www.iana.org/domains/reserved", "h1"),
			("web-dom-python-docs-title", "https://docs.python.org/3/", "h1"),
			("web-dom-python-docs-sidebar", "https://docs.python.org/3/", "#sidebar"),
			("web-dom-mdn-js", "https://developer.mozilla.org/en-US/docs/Web/JavaScript", "h1"),
			("web-dom-mdn-search", "https://developer.mozilla.org/en-US/docs/Web/JavaScript", "#top-nav-search-input"),
			("web-dom-fastapi-title", "https://fastapi.tiangolo.com/", "h1"),
			("web-dom-pypi-title", "https://pypi.org/", "h1"),
		]

	# Add a stable click-and-fetch example: MDN Home -> ensure search input visible
	click_tasks: List[tuple[str, str, str, str]] = []
	if not args.only_yaml:
		click_tasks = [
			("web-dom-mdn-click-search", "https://developer.mozilla.org/en-US/", "#top-nav-search-input", "#top-nav-search-input"),
		]

	# Add a minimal form-fill example (typing into MDN search input)
	form_tasks: List[tuple[str, str, str, str | None, str | None]] = []
	if not args.only_yaml:
		form_tasks = [
			("web-dom-mdn-form-type", "https://developer.mozilla.org/en-US/", "#top-nav-search-input", None, "#top-nav-search-input"),
			# Stable: PyPI search box and submit; target reads the first search headline
			("web-dom-pypi-form-submit", "https://pypi.org/", "#search", "button.search-form__button", "h1.package-header__name"),
		]

	# Optionally extend with additional seeds from YAML file (supports multi-gold selectors and click/form types)
	seeds_path = Path(args.seeds)
	if seeds_path.exists() and yaml is not None:
		try:
			data = yaml.safe_load(seeds_path.read_text())
			if isinstance(data, list):
				for i, row in enumerate(data):
					if not isinstance(row, dict):
						continue
					url = str(row.get("url", "")).strip()
					selector = row.get("selector")
					selectors = row.get("selectors")
					type_ = str(row.get("type", "fetch")).strip().lower() or "fetch"
					if not url:
						continue
					tid = str(row.get("id", f"web-dom-extra-{i}"))
					# Capture optional tags/stability for downstream analysis
					tags = row.get("tags")
					stability = row.get("stability")
					assert_meta = row.get("assert")
					meta_add: Dict[str, Any] = {}
					if isinstance(tags, list):
						meta_add["tags"] = [str(t).strip() for t in tags if str(t).strip()]
					if isinstance(stability, (str, int, float)):
						meta_add["stability"] = str(stability)
					# Carry through optional assertion hints for verification-time checks
					if isinstance(assert_meta, dict):
						# Only keep simple string assertions like {contains: "..."}
						keep: Dict[str, Any] = {}
						val = assert_meta.get("contains")
						if isinstance(val, str) and val.strip():
							keep["contains"] = val.strip()
						if keep:
							meta_add["assert"] = keep
					if meta_add:
						task_meta[tid] = meta_add
					if type_ == "fetch":
						# If multi-gold selectors provided, store list; else single selector
						if isinstance(selectors, list) and selectors:
							sel_list = [str(s).strip() for s in selectors if str(s).strip()]
							if sel_list:
								# Use first as canonical; carry the full list in job for multi-gold
								tasks.append((tid, url, sel_list))
								continue
						# Fallback single selector
						sel_str = str(selector or "").strip()
						if not sel_str:
							continue
						tasks.append((tid, url, sel_str))
					elif type_ == "click":
						click_sel = str(row.get("click_selector", "")).strip()
						target_sel = str(row.get("target_selector", "")).strip() or str(selector or "").strip()
						if click_sel and target_sel:
							click_tasks.append((tid, url, click_sel, target_sel))
					elif type_ == "form":
						input_sel = str(row.get("input_selector", "")).strip()
						submit_sel = row.get("submit_selector")
						target_sel = row.get("target_selector") or selector
						if isinstance(submit_sel, str):
							submit_sel = submit_sel.strip() or None
						if isinstance(target_sel, str):
							target_sel = target_sel.strip() or None
						if input_sel:
							form_tasks.append((tid, url, input_sel, submit_sel, target_sel))
		except Exception:
			pass

	out_path = Path(args.out)
	out_path.parent.mkdir(parents=True, exist_ok=True)

	def run_one(job: Tuple[str, str, Any]) -> Dict[str, Any]:
		task_id, url, sel = job
		picked_selector: str = ""
		res = {"text": ""}
		# If multi-gold provided, try each until success and record the first hit
		if isinstance(sel, list):
			for s in sel:
				res = fetch_text(url, s)
				if bool(res.get("text")):
					picked_selector = s
					break
			if not picked_selector and sel:
				picked_selector = str(sel[0])
		else:
			picked_selector = str(sel)
			res = fetch_text(url, picked_selector)
		# Fallbacks for robustness: try common alternatives if empty
		if not bool(res.get("text")):
			for alt in ("h1", "#firstHeading", "title", "p", "body"):
				if alt == picked_selector:
					continue
				r2 = fetch_text(url, alt)
				if bool(r2.get("text")):
					picked_selector = alt
					res = r2
					break
		# Fallback to stable pairs if still empty
		if not bool(res.get("text")):
			stable_pairs = [
				("https://example.com", "h1"),
				("https://en.wikipedia.org/wiki/Alan_Turing", "#firstHeading"),
			]
			for u, s2 in stable_pairs:
				r3 = fetch_text(u, s2)
				if bool(r3.get("text")):
					url = u
					picked_selector = s2
					res = r3
					break
		obs_meta: Dict[str, Any] = {"url": url, "selector": picked_selector}
		# Stability tags: mark default seeds as stable and include dynamic indicator for docs/blog-like pages
		try:
			stable = ["example.com", "iana.org", "en.wikipedia.org"]
			from urllib.parse import urlparse as _u  # type: ignore
			host = _u(url).hostname or ""
			tags: list[str] = []
			if any(host.endswith(s) for s in stable):
				tags.append("stable")
			else:
				tags.append("dynamic")
			obs_meta["tags"] = tags
		except Exception:
			pass
		# Attach any per-task metadata (e.g., tags, stability)
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "fetch text via selector", "meta": obs_meta}
		plan = {"subgoals": ["open page", "read selector"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.read", "args": {"url": url, "selector": picked_selector}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		# Attach assertion for stable example.com tasks
		try:
			from urllib.parse import urlparse as _u  # type: ignore
			if (_u(url).hostname or "").endswith("example.com"):
				# For example.com, h1 should contain "Example Domain" and p contains known blurb
				if picked_selector == "h1":
					obs_meta.setdefault("assert", {})
					obs_meta["assert"]["contains"] = "Example Domain"
				elif picked_selector == "p":
					obs_meta.setdefault("assert", {})
					obs_meta["assert"]["contains"] = "for illustrative examples"
		except Exception:
			pass
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		tr = build_trace(task_id, obs, plan, action, result, reward, critique)
		# Include gold selectors list if provided
		if isinstance(sel, list):
			tr["gold_selectors"] = sel
		return tr

	def run_click(job: Tuple[str, str, str, str]) -> Dict[str, Any]:
		task_id, start_url, click_sel, target_sel = job
		res = click_then_fetch(start_url, click_sel, target_sel)
		obs_meta: Dict[str, Any] = {"url": start_url, "selector": target_sel}
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "click then fetch selector", "meta": obs_meta}
		plan = {"subgoals": ["open page", "click", "read selector"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.click_read", "args": {"url": start_url, "click_selector": click_sel, "selector": target_sel}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		return build_trace(task_id, obs, plan, action, result, reward, critique)

	def run_form(job: Tuple[str, str, str, str | None, str | None]) -> Dict[str, Any]:
		task_id, start_url, input_sel, submit_sel, target_sel = job
		res = form_fill_fetch(start_url, input_sel, text="javascript", submit_selector=submit_sel, target_selector=target_sel)
		obs_meta: Dict[str, Any] = {"url": start_url, "selector": target_sel or input_sel}
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "form fill then fetch", "meta": obs_meta}
		plan = {"subgoals": ["open page", "type", "maybe submit", "read"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.form_fill", "args": {"url": start_url, "input_selector": input_sel, "text": "javascript", "submit_selector": submit_sel, "selector": target_sel or input_sel}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		return build_trace(task_id, obs, plan, action, result, reward, critique)

	traces: List[Dict[str, Any]] = []
	if not args.reuse_browser:
		# Run per-task fresh browser (default behavior)
		with ThreadPoolExecutor(max_workers=max(1, args.workers)) as ex:
			futures_one = {ex.submit(run_one, job): job for job in tasks}
			for fut in as_completed(futures_one):
				try:
					tr = fut.result()
					traces.append(tr)
				except Exception:
					continue
			# Also run click tasks
			futures_click = {ex.submit(run_click, job): job for job in click_tasks}
			for fut in as_completed(futures_click):
				try:
					traces.append(fut.result())
				except Exception:
					continue
			# And form tasks
			futures_form = {ex.submit(run_form, job): job for job in form_tasks}
			for fut in as_completed(futures_form):
				try:
					traces.append(fut.result())
				except Exception:
					continue
	else:
		# Reuse one browser per worker by chunking tasks
		try:
			import undetected_chromedriver as uc  # type: ignore
			from selenium.webdriver.common.by import By  # type: ignore
			from selenium.webdriver.support.ui import WebDriverWait  # type: ignore
			from selenium.webdriver.support import expected_conditions as EC  # type: ignore
			from selenium.common.exceptions import StaleElementReferenceException  # type: ignore
		except Exception:
			uc = None

		def _fetch_with_driver(driver, url: str, selector: str) -> Dict[str, Any]:
			try:
				driver.get(url)
				WebDriverWait(driver, 8).until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))
				elem = driver.find_element(By.CSS_SELECTOR, selector)
				try:
					text = (elem.text or elem.get_attribute("textContent") or "").strip()
				except StaleElementReferenceException:
					try:
						WebDriverWait(driver, 3).until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))
						elem = driver.find_element(By.CSS_SELECTOR, selector)
						text = (elem.text or elem.get_attribute("textContent") or "").strip()
					except Exception:
						text = ""
			except Exception:
				text = ""
			return {"url": url, "selector": selector, "text": text}

		def run_chunk(chunk: List[Tuple[str, str, str]]) -> List[Dict[str, Any]]:
			local_traces: List[Dict[str, Any]] = []
			if uc is None:
				for job in chunk:
					local_traces.append(run_one(job))
				return local_traces
			# Create one browser for this worker
			opts = uc.ChromeOptions()
			opts.add_argument("--headless=new")
			opts.add_argument("--no-sandbox")
			opts.add_argument("--disable-dev-shm-usage")
			drv = None
			try:
				drv = uc.Chrome(options=opts)
				for task_id, url, selector in chunk:
					res = _fetch_with_driver(drv, url, selector)
					obs = {"kind": "dom", "content": "fetch text via selector", "meta": {"url": url, "selector": selector}}
					plan = {"subgoals": ["open page", "read selector"], "tools": ["browser"], "constraints": {}}
					action = {"tool": "browser.read", "args": {"url": url, "selector": selector}}
					ok = bool(res.get("text"))
					result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
					reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
					critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
					local_traces.append(build_trace(task_id, obs, plan, action, result, reward, critique))
			except Exception:
				# Fallback per-job if browser init fails
				for job in chunk:
					local_traces.append(run_one(job))
			finally:
				try:
					if drv is not None:
						drv.quit()
				except Exception:
					pass
			return local_traces

		# Partition tasks into roughly equal chunks
		workers = max(1, args.workers)
		chunk_size = max(1, (len(tasks) + workers - 1) // workers)
		chunks: List[List[Tuple[str, str, str]]] = [tasks[i : i + chunk_size] for i in range(0, len(tasks), chunk_size)]
		with ThreadPoolExecutor(max_workers=workers) as ex:
			futs = [ex.submit(run_chunk, ch) for ch in chunks]
			for fut in as_completed(futs):
				try:
					traces.extend(fut.result())
				except Exception:
					continue

	for tr in traces:
		write_jsonl(str(out_path), tr)

	print(str(out_path))

if __name__ == "__main__":
	main()


						click_sel = str(row.get("click_selector", "")).strip()
						target_sel = str(row.get("target_selector", "")).strip() or str(selector or "").strip()
						if click_sel and target_sel:
							click_tasks.append((tid, url, click_sel, target_sel))
					elif type_ == "form":
						input_sel = str(row.get("input_selector", "")).strip()
						submit_sel = row.get("submit_selector")
						target_sel = row.get("target_selector") or selector
						if isinstance(submit_sel, str):
							submit_sel = submit_sel.strip() or None
						if isinstance(target_sel, str):
							target_sel = target_sel.strip() or None
						if input_sel:
							form_tasks.append((tid, url, input_sel, submit_sel, target_sel))
		except Exception:
			pass

	out_path = Path(args.out)
	out_path.parent.mkdir(parents=True, exist_ok=True)

	def run_one(job: Tuple[str, str, Any]) -> Dict[str, Any]:
		task_id, url, sel = job
		picked_selector: str = ""
		res = {"text": ""}
		# If multi-gold provided, try each until success and record the first hit
		if isinstance(sel, list):
			for s in sel:
				res = fetch_text(url, s)
				if bool(res.get("text")):
					picked_selector = s
					break
			if not picked_selector and sel:
				picked_selector = str(sel[0])
		else:
			picked_selector = str(sel)
			res = fetch_text(url, picked_selector)
		# Fallbacks for robustness: try common alternatives if empty
		if not bool(res.get("text")):
			for alt in ("h1", "#firstHeading", "title", "p", "body"):
				if alt == picked_selector:
					continue
				r2 = fetch_text(url, alt)
				if bool(r2.get("text")):
					picked_selector = alt
					res = r2
					break
		# Fallback to stable pairs if still empty
		if not bool(res.get("text")):
			stable_pairs = [
				("https://example.com", "h1"),
				("https://en.wikipedia.org/wiki/Alan_Turing", "#firstHeading"),
			]
			for u, s2 in stable_pairs:
				r3 = fetch_text(u, s2)
				if bool(r3.get("text")):
					url = u
					picked_selector = s2
					res = r3
					break
		obs_meta: Dict[str, Any] = {"url": url, "selector": picked_selector}
		# Stability tags: mark default seeds as stable and include dynamic indicator for docs/blog-like pages
		try:
			stable = ["example.com", "iana.org", "en.wikipedia.org"]
			from urllib.parse import urlparse as _u  # type: ignore
			host = _u(url).hostname or ""
			tags: list[str] = []
			if any(host.endswith(s) for s in stable):
				tags.append("stable")
			else:
				tags.append("dynamic")
			obs_meta["tags"] = tags
		except Exception:
			pass
		# Attach any per-task metadata (e.g., tags, stability)
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "fetch text via selector", "meta": obs_meta}
		plan = {"subgoals": ["open page", "read selector"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.read", "args": {"url": url, "selector": picked_selector}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		# Attach assertion for stable example.com tasks
		try:
			from urllib.parse import urlparse as _u  # type: ignore
			if (_u(url).hostname or "").endswith("example.com"):
				# For example.com, h1 should contain "Example Domain" and p contains known blurb
				if picked_selector == "h1":
					obs_meta.setdefault("assert", {})
					obs_meta["assert"]["contains"] = "Example Domain"
				elif picked_selector == "p":
					obs_meta.setdefault("assert", {})
					obs_meta["assert"]["contains"] = "for illustrative examples"
		except Exception:
			pass
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		tr = build_trace(task_id, obs, plan, action, result, reward, critique)
		# Include gold selectors list if provided
		if isinstance(sel, list):
			tr["gold_selectors"] = sel
		return tr

	def run_click(job: Tuple[str, str, str, str]) -> Dict[str, Any]:
		task_id, start_url, click_sel, target_sel = job
		res = click_then_fetch(start_url, click_sel, target_sel)
		obs_meta: Dict[str, Any] = {"url": start_url, "selector": target_sel}
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "click then fetch selector", "meta": obs_meta}
		plan = {"subgoals": ["open page", "click", "read selector"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.click_read", "args": {"url": start_url, "click_selector": click_sel, "selector": target_sel}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		return build_trace(task_id, obs, plan, action, result, reward, critique)

	def run_form(job: Tuple[str, str, str, str | None, str | None]) -> Dict[str, Any]:
		task_id, start_url, input_sel, submit_sel, target_sel = job
		res = form_fill_fetch(start_url, input_sel, text="javascript", submit_selector=submit_sel, target_selector=target_sel)
		obs_meta: Dict[str, Any] = {"url": start_url, "selector": target_sel or input_sel}


		try:
			from urllib.parse import urlparse as _u  # type: ignore
			if (_u(url).hostname or "").endswith("example.com"):
				# For example.com, h1 should contain "Example Domain" and p contains known blurb
				if picked_selector == "h1":
					obs_meta.setdefault("assert", {})
					obs_meta["assert"]["contains"] = "Example Domain"
				elif picked_selector == "p":
					obs_meta.setdefault("assert", {})
					obs_meta["assert"]["contains"] = "for illustrative examples"
		except Exception:
			pass
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		tr = build_trace(task_id, obs, plan, action, result, reward, critique)
		# Include gold selectors list if provided
		if isinstance(sel, list):
			tr["gold_selectors"] = sel
		return tr

	def run_click(job: Tuple[str, str, str, str]) -> Dict[str, Any]:
		task_id, start_url, click_sel, target_sel = job
		res = click_then_fetch(start_url, click_sel, target_sel)
		obs_meta: Dict[str, Any] = {"url": start_url, "selector": target_sel}
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "click then fetch selector", "meta": obs_meta}
		plan = {"subgoals": ["open page", "click", "read selector"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.click_read", "args": {"url": start_url, "click_selector": click_sel, "selector": target_sel}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		return build_trace(task_id, obs, plan, action, result, reward, critique)

	def run_form(job: Tuple[str, str, str, str | None, str | None]) -> Dict[str, Any]:
		task_id, start_url, input_sel, submit_sel, target_sel = job
		res = form_fill_fetch(start_url, input_sel, text="javascript", submit_selector=submit_sel, target_selector=target_sel)
		obs_meta: Dict[str, Any] = {"url": start_url, "selector": target_sel or input_sel}
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "form fill then fetch", "meta": obs_meta}
		plan = {"subgoals": ["open page", "type", "maybe submit", "read"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.form_fill", "args": {"url": start_url, "input_selector": input_sel, "text": "javascript", "submit_selector": submit_sel, "selector": target_sel or input_sel}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		return build_trace(task_id, obs, plan, action, result, reward, critique)

	traces: List[Dict[str, Any]] = []
	if not args.reuse_browser:
		# Run per-task fresh browser (default behavior)
		with ThreadPoolExecutor(max_workers=max(1, args.workers)) as ex:


		# Include gold selectors list if provided
		if isinstance(sel, list):
			tr["gold_selectors"] = sel
		return tr

	def run_click(job: Tuple[str, str, str, str]) -> Dict[str, Any]:
		task_id, start_url, click_sel, target_sel = job
		res = click_then_fetch(start_url, click_sel, target_sel)
		obs_meta: Dict[str, Any] = {"url": start_url, "selector": target_sel}
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "click then fetch selector", "meta": obs_meta}
		plan = {"subgoals": ["open page", "click", "read selector"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.click_read", "args": {"url": start_url, "click_selector": click_sel, "selector": target_sel}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		return build_trace(task_id, obs, plan, action, result, reward, critique)

	def run_form(job: Tuple[str, str, str, str | None, str | None]) -> Dict[str, Any]:
		task_id, start_url, input_sel, submit_sel, target_sel = job
		res = form_fill_fetch(start_url, input_sel, text="javascript", submit_selector=submit_sel, target_selector=target_sel)
		obs_meta: Dict[str, Any] = {"url": start_url, "selector": target_sel or input_sel}
		if task_id in task_meta:
			obs_meta.update(task_meta[task_id])
		obs = {"kind": "dom", "content": "form fill then fetch", "meta": obs_meta}
		plan = {"subgoals": ["open page", "type", "maybe submit", "read"], "tools": ["browser"], "constraints": {}}
		action = {"tool": "browser.form_fill", "args": {"url": start_url, "input_selector": input_sel, "text": "javascript", "submit_selector": submit_sel, "selector": target_sel or input_sel}}
		ok = bool(res.get("text"))
		result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
		reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
		critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
		return build_trace(task_id, obs, plan, action, result, reward, critique)

	traces: List[Dict[str, Any]] = []
	if not args.reuse_browser:
		# Run per-task fresh browser (default behavior)
		with ThreadPoolExecutor(max_workers=max(1, args.workers)) as ex:
			futures_one = {ex.submit(run_one, job): job for job in tasks}
			for fut in as_completed(futures_one):
				try:
					tr = fut.result()
					traces.append(tr)
				except Exception:
					continue
			# Also run click tasks
			futures_click = {ex.submit(run_click, job): job for job in click_tasks}
			for fut in as_completed(futures_click):
				try:
					traces.append(fut.result())
				except Exception:
					continue
			# And form tasks


				except Exception:
					continue
			# And form tasks
			futures_form = {ex.submit(run_form, job): job for job in form_tasks}
			for fut in as_completed(futures_form):
				try:
					traces.append(fut.result())
				except Exception:
					continue
	else:
		# Reuse one browser per worker by chunking tasks
		try:
			import undetected_chromedriver as uc  # type: ignore
			from selenium.webdriver.common.by import By  # type: ignore
			from selenium.webdriver.support.ui import WebDriverWait  # type: ignore
			from selenium.webdriver.support import expected_conditions as EC  # type: ignore
			from selenium.common.exceptions import StaleElementReferenceException  # type: ignore
		except Exception:
			uc = None

		def _fetch_with_driver(driver, url: str, selector: str) -> Dict[str, Any]:
			try:
				driver.get(url)
				WebDriverWait(driver, 8).until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))
				elem = driver.find_element(By.CSS_SELECTOR, selector)
				try:
					text = (elem.text or elem.get_attribute("textContent") or "").strip()
				except StaleElementReferenceException:
					try:
						WebDriverWait(driver, 3).until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))
						elem = driver.find_element(By.CSS_SELECTOR, selector)
						text = (elem.text or elem.get_attribute("textContent") or "").strip()
					except Exception:
						text = ""
			except Exception:
				text = ""
			return {"url": url, "selector": selector, "text": text}

		def run_chunk(chunk: List[Tuple[str, str, str]]) -> List[Dict[str, Any]]:
			local_traces: List[Dict[str, Any]] = []
			if uc is None:
				for job in chunk:
					local_traces.append(run_one(job))
				return local_traces
			# Create one browser for this worker
			opts = uc.ChromeOptions()
			opts.add_argument("--headless=new")
			opts.add_argument("--no-sandbox")
			opts.add_argument("--disable-dev-shm-usage")
			drv = None
			try:
				drv = uc.Chrome(options=opts)
				for task_id, url, selector in chunk:
					res = _fetch_with_driver(drv, url, selector)
					obs = {"kind": "dom", "content": "fetch text via selector", "meta": {"url": url, "selector": selector}}
					plan = {"subgoals": ["open page", "read selector"], "tools": ["browser"], "constraints": {}}
					action = {"tool": "browser.read", "args": {"url": url, "selector": selector}}


			uc = None

		def _fetch_with_driver(driver, url: str, selector: str) -> Dict[str, Any]:
			try:
				driver.get(url)
				WebDriverWait(driver, 8).until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))
				elem = driver.find_element(By.CSS_SELECTOR, selector)
				try:
					text = (elem.text or elem.get_attribute("textContent") or "").strip()
				except StaleElementReferenceException:
					try:
						WebDriverWait(driver, 3).until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))
						elem = driver.find_element(By.CSS_SELECTOR, selector)
						text = (elem.text or elem.get_attribute("textContent") or "").strip()
					except Exception:
						text = ""
			except Exception:
				text = ""
			return {"url": url, "selector": selector, "text": text}

		def run_chunk(chunk: List[Tuple[str, str, str]]) -> List[Dict[str, Any]]:
			local_traces: List[Dict[str, Any]] = []
			if uc is None:
				for job in chunk:
					local_traces.append(run_one(job))
				return local_traces
			# Create one browser for this worker
			opts = uc.ChromeOptions()
			opts.add_argument("--headless=new")
			opts.add_argument("--no-sandbox")
			opts.add_argument("--disable-dev-shm-usage")
			drv = None
			try:
				drv = uc.Chrome(options=opts)
				for task_id, url, selector in chunk:
					res = _fetch_with_driver(drv, url, selector)
					obs = {"kind": "dom", "content": "fetch text via selector", "meta": {"url": url, "selector": selector}}
					plan = {"subgoals": ["open page", "read selector"], "tools": ["browser"], "constraints": {}}
					action = {"tool": "browser.read", "args": {"url": url, "selector": selector}}
					ok = bool(res.get("text"))
					result = {"dom": res.get("text", ""), "status": "ok" if ok else "error"}
					reward = {"scalar": 1.0 if ok else 0.0, "components": {"success": 1 if ok else 0, "latency": 0, "side_effect": 1}}
					critique = {"issues": [], "risk": 0.1 if ok else 0.5, "proposal": ""}
					local_traces.append(build_trace(task_id, obs, plan, action, result, reward, critique))
			except Exception:
				# Fallback per-job if browser init fails
				for job in chunk:
					local_traces.append(run_one(job))
			finally:
				try:
					if drv is not None:
						drv.quit()
				except Exception:
					pass
			return local_traces

		# Partition tasks into roughly equal chunks
		workers = max(1, args.workers)
		chunk_size = max(1, (len(tasks) + workers - 1) // workers)
		chunks: List[List[Tuple[str, str, str]]] = [tasks[i : i + chunk_size] for i in range(0, len(tasks), chunk_size)]
		with ThreadPoolExecutor(max_workers=workers) as ex:
			futs = [ex.submit(run_chunk, ch) for ch in chunks]
			for fut in as_completed(futs):
				try:
					traces.extend(fut.result())
				except Exception:
					continue

	for tr in traces:
		write_jsonl(str(out_path), tr)

	print(str(out_path))

if __name__ == "__main__":


import logging
import json
from pathlib import Path
from typing import Any, Dict

import sys

try:
	import jsonschema  # type: ignore
except Exception:
	jsonschema = None

def load_schema(root: Path) -> Dict[str, Any]:
	schema_path = root / "docs" / "schemas" / "trace.schema.json"
	return json.loads(schema_path.read_text(encoding="utf-8"))

def validate_file(schema: Dict[str, Any], jsonl_path: Path) -> int:
	if jsonschema is None:
		print("jsonschema not installed. Please pip install jsonschema.")
		return 1
	validator = jsonschema.Draft202012Validator(schema)  # type: ignore[attr-defined]
	errors = 0
	with jsonl_path.open("r", encoding="utf-8") as f:
		for i, line in enumerate(f, start=1):
			if not line.strip():
				continue
			obj = json.loads(line)
			for err in validator.iter_errors(obj):
				print(f"Line {i}: {err.message}")
				errors += 1
	if errors == 0:
		print(f"OK: {jsonl_path}")
		return 0
	print(f"Found {errors} validation errors in {jsonl_path}")
	return 2

def main() -> int:
	root = Path(__file__).resolve().parents[1]
	schema = load_schema(root)
	target = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "data" / "traces" / "seed_os_cli.jsonl"
	return validate_file(schema, target)

if __name__ == "__main__":
	raise SystemExit(main())


import logging
import json
from pathlib import Path
from typing import Any, Dict

import sys

try:
	import jsonschema  # type: ignore
except Exception:
	jsonschema = None

def load_schema(root: Path) -> Dict[str, Any]:
	schema_path = root / "docs" / "schemas" / "trace.schema.json"
	return json.loads(schema_path.read_text(encoding="utf-8"))

def validate_file(schema: Dict[str, Any], jsonl_path: Path) -> int:
	if jsonschema is None:
		print("jsonschema not installed. Please pip install jsonschema.")
		return 1
	validator = jsonschema.Draft202012Validator(schema)  # type: ignore[attr-defined]
	errors = 0
	with jsonl_path.open("r", encoding="utf-8") as f:
		for i, line in enumerate(f, start=1):
			if not line.strip():
				continue
			obj = json.loads(line)
			for err in validator.iter_errors(obj):
				print(f"Line {i}: {err.message}")
				errors += 1
	if errors == 0:
		print(f"OK: {jsonl_path}")
		return 0
	print(f"Found {errors} validation errors in {jsonl_path}")


	if jsonschema is None:
		print("jsonschema not installed. Please pip install jsonschema.")
		return 1
	validator = jsonschema.Draft202012Validator(schema)  # type: ignore[attr-defined]
	errors = 0
	with jsonl_path.open("r", encoding="utf-8") as f:
		for i, line in enumerate(f, start=1):
			if not line.strip():
				continue
			obj = json.loads(line)
			for err in validator.iter_errors(obj):
				print(f"Line {i}: {err.message}")
				errors += 1
	if errors == 0:
		print(f"OK: {jsonl_path}")
		return 0
	print(f"Found {errors} validation errors in {jsonl_path}")
	return 2

def main() -> int:
	root = Path(__file__).resolve().parents[1]
	schema = load_schema(root)
	target = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "data" / "traces" / "seed_os_cli.jsonl"
	return validate_file(schema, target)

if __name__ == "__main__":
	raise SystemExit(main())


import logging
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any, Dict, List

def sha256_file(path: Path) -> str:
	try:
		h = hashlib.sha256()
		with path.open("rb") as f:
			for chunk in iter(lambda: f.read(8192), b""):
				h.update(chunk)
		return h.hexdigest()
	except Exception:
		return ""

def list_dir_hash(root: Path) -> Dict[str, Any]:
	info: Dict[str, Any] = {"exists": root.exists(), "files": [], "digest": ""}
	if not root.exists():
		return info
	files: List[str] = []
	for p in sorted(root.rglob("*")):
		if p.is_file():
			files.append(str(p.relative_to(root)))
	info["files"] = files
	# Digest over filenames for stability (avoid hashing large models)
	try:
		h = hashlib.sha256()
		for name in files:
			h.update(name.encode("utf-8"))
		info["digest"] = h.hexdigest()
	except Exception:
		info["digest"] = ""
	return info

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	ap.add_argument("--out", default=str(root / "data" / "registry" / "registry.json"))
	args = ap.parse_args()

	# Ensure output dir
	out_path = Path(args.out)
	out_path.parent.mkdir(parents=True, exist_ok=True)

	# Skills library snapshot
	skills_info: Dict[str, Any] = {}
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root))
		skills_jsonl = lib.skills_path
		skills_info = {
			"current_version": lib.current_version(),
			"versions": lib.list_versions(),
			"skills_path": str(skills_jsonl),
			"skills_digest": sha256_file(skills_jsonl) if skills_jsonl.exists() else "",
		}
	except Exception:
		skills_info = {"error": "unavailable"}

	# Model packs (presence + filenames digest)
	models = {
		"actuator_il_t5": list_dir_hash(root / "models" / "actuator_il_t5"),
		"actuator_dom_t5": list_dir_hash(root / "models" / "actuator_dom_t5"),
		"verifier_qlora": list_dir_hash(root / "models" / "verifier_qlora"),
		"verifier_calib": list_dir_hash(root / "models" / "verifier_calib"),
		"wm_mlp": list_dir_hash(root / "models" / "wm_mlp"),
		"router": list_dir_hash(root / "models" / "router"),
	}

	# Traces files snapshot (filenames + digest of filenames)
	traces_dir = root / "data" / "traces"
	traces = list_dir_hash(traces_dir)

	registry = {
		"skills": skills_info,
		"models": models,
		"traces": traces,
	}

	out_path.write_text(json.dumps(registry, ensure_ascii=False, indent=2), encoding="utf-8")
	print(json.dumps({"ok": True, "out": str(out_path)}))
	return 0

if __name__ == "__main__":
	import sys
	sys.exit(main())


import logging
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any, Dict, List

def sha256_file(path: Path) -> str:
	try:
		h = hashlib.sha256()
		with path.open("rb") as f:
			for chunk in iter(lambda: f.read(8192), b""):
				h.update(chunk)
		return h.hexdigest()
	except Exception:
		return ""

def list_dir_hash(root: Path) -> Dict[str, Any]:
	info: Dict[str, Any] = {"exists": root.exists(), "files": [], "digest": ""}
	if not root.exists():
		return info
	files: List[str] = []
	for p in sorted(root.rglob("*")):
		if p.is_file():
			files.append(str(p.relative_to(root)))
	info["files"] = files
	# Digest over filenames for stability (avoid hashing large models)
	try:
		h = hashlib.sha256()
		for name in files:
			h.update(name.encode("utf-8"))
		info["digest"] = h.hexdigest()
	except Exception:
		info["digest"] = ""
	return info


import logging
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any, Dict, List

def sha256_file(path: Path) -> str:
	try:
		h = hashlib.sha256()
		with path.open("rb") as f:
			for chunk in iter(lambda: f.read(8192), b""):
				h.update(chunk)
		return h.hexdigest()
	except Exception:
		return ""

def list_dir_hash(root: Path) -> Dict[str, Any]:
	info: Dict[str, Any] = {"exists": root.exists(), "files": [], "digest": ""}
	if not root.exists():
		return info
	files: List[str] = []
	for p in sorted(root.rglob("*")):
		if p.is_file():
			files.append(str(p.relative_to(root)))
	info["files"] = files
	# Digest over filenames for stability (avoid hashing large models)
	try:
		h = hashlib.sha256()
		for name in files:
			h.update(name.encode("utf-8"))
		info["digest"] = h.hexdigest()
	except Exception:
		info["digest"] = ""
	return info

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	ap.add_argument("--out", default=str(root / "data" / "registry" / "registry.json"))
	args = ap.parse_args()

	# Ensure output dir
	out_path = Path(args.out)
	out_path.parent.mkdir(parents=True, exist_ok=True)

	# Skills library snapshot
	skills_info: Dict[str, Any] = {}
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root))
		skills_jsonl = lib.skills_path
		skills_info = {
			"current_version": lib.current_version(),


def list_dir_hash(root: Path) -> Dict[str, Any]:
	info: Dict[str, Any] = {"exists": root.exists(), "files": [], "digest": ""}
	if not root.exists():
		return info
	files: List[str] = []
	for p in sorted(root.rglob("*")):
		if p.is_file():
			files.append(str(p.relative_to(root)))
	info["files"] = files
	# Digest over filenames for stability (avoid hashing large models)
	try:
		h = hashlib.sha256()
		for name in files:
			h.update(name.encode("utf-8"))
		info["digest"] = h.hexdigest()
	except Exception:
		info["digest"] = ""
	return info

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	ap.add_argument("--out", default=str(root / "data" / "registry" / "registry.json"))
	args = ap.parse_args()

	# Ensure output dir
	out_path = Path(args.out)
	out_path.parent.mkdir(parents=True, exist_ok=True)

	# Skills library snapshot
	skills_info: Dict[str, Any] = {}
	try:
		from agi_dw.core.memory.skills import SkillLibrary  # type: ignore
		lib = SkillLibrary(str(root))
		skills_jsonl = lib.skills_path
		skills_info = {
			"current_version": lib.current_version(),
			"versions": lib.list_versions(),
			"skills_path": str(skills_jsonl),
			"skills_digest": sha256_file(skills_jsonl) if skills_jsonl.exists() else "",
		}
	except Exception:
		skills_info = {"error": "unavailable"}

	# Model packs (presence + filenames digest)
	models = {
		"actuator_il_t5": list_dir_hash(root / "models" / "actuator_il_t5"),
		"actuator_dom_t5": list_dir_hash(root / "models" / "actuator_dom_t5"),
		"verifier_qlora": list_dir_hash(root / "models" / "verifier_qlora"),
		"verifier_calib": list_dir_hash(root / "models" / "verifier_calib"),
		"wm_mlp": list_dir_hash(root / "models" / "wm_mlp"),
		"router": list_dir_hash(root / "models" / "router"),
	}

	# Traces files snapshot (filenames + digest of filenames)
	traces_dir = root / "data" / "traces"
	traces = list_dir_hash(traces_dir)

	registry = {
		"skills": skills_info,
		"models": models,
		"traces": traces,
	}

	out_path.write_text(json.dumps(registry, ensure_ascii=False, indent=2), encoding="utf-8")
	print(json.dumps({"ok": True, "out": str(out_path)}))
	return 0

if __name__ == "__main__":
	import sys
	sys.exit(main())


import logging
import json
from pathlib import Path
import sys

try:
	import jsonschema  # type: ignore
except Exception:
	jsonschema = None

def main() -> int:
	if len(sys.argv) < 2:
		print("Usage: validate_plan.py <plan.json>")
		return 2
	root = Path(__file__).resolve().parents[1]
	schema_path = root / "docs" / "schemas" / "plan.schema.json"
	plan_path = Path(sys.argv[1])

	if not schema_path.exists():
		print(f"Schema not found: {schema_path}")
		return 2
	if not plan_path.exists():
		print(f"Plan file not found: {plan_path}")
		return 2
	if jsonschema is None:
		print("jsonschema not installed. Please pip install jsonschema.")
		return 1

	schema = json.loads(schema_path.read_text(encoding="utf-8"))
	data = json.loads(plan_path.read_text(encoding="utf-8"))

	jsonschema.validate(data, schema)  # type: ignore[arg-type]
	print("OK")
	return 0

if __name__ == "__main__":
	raise SystemExit(main())


import logging
import argparse
import json
from pathlib import Path
from typing import Any, Dict, Iterable, List

from agi_dw.bench.common.trace import write_jsonl

def iter_jsonl(path: Path) -> Iterable[Dict[str, Any]]:
	if not path.exists():
		return
	with path.open("r", encoding="utf-8") as f:
		for line in f:
			line = line.strip()
			if not line:
				continue
			try:
				yield json.loads(line)
			except Exception:
				continue

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	ap.add_argument("--inputs", nargs="*", default=[
		str(root / "data" / "traces" / "seed_os_cli.jsonl"),
		str(root / "data" / "traces" / "seed_os_cli.verified.llm.jsonl"),
		str(root / "data" / "traces" / "loop_run.jsonl"),
		str(root / "data" / "traces" / "loop_web_dom.jsonl"),
		str(root / "data" / "traces" / "web_dom.verified.jsonl"),
	])
	ap.add_argument("--out", default=str(root / "data" / "traces" / "all.jsonl"))
	args = ap.parse_args()

	out = Path(args.out)
	out.parent.mkdir(parents=True, exist_ok=True)

	seen_keys: set[str] = set()
	count = 0
	for raw in args.inputs:
		p = Path(raw)
		for obj in iter_jsonl(p):
			# Dedup by coarse key: (task_id, result.status, critique.risk rounded)
			tid = str(obj.get("task_id", ""))
			status = str(((obj.get("result") or {}).get("status", "")))
			try:
				risk = float(((obj.get("critique") or {}).get("risk", 0.0)))
			except Exception:
				risk = 0.0
			key = f"{tid}|{status}|{round(risk, 2)}"
			if key in seen_keys:
				continue
			seen_keys.add(key)
			write_jsonl(args.out, obj)
			count += 1
	print(json.dumps({"merged": count, "inputs": len(args.inputs), "out": str(out)}))
	return 0

if __name__ == "__main__":
	import sys
	sys.exit(main())


import logging
import argparse
import json
from pathlib import Path
from typing import Any, Dict, Iterable, List

from agi_dw.bench.common.trace import write_jsonl

def iter_jsonl(path: Path) -> Iterable[Dict[str, Any]]:
	if not path.exists():
		return
	with path.open("r", encoding="utf-8") as f:
		for line in f:
			line = line.strip()
			if not line:
				continue
			try:
				yield json.loads(line)
			except Exception:
				continue

def main() -> int:
	ap = argparse.ArgumentParser()
	root = Path(__file__).resolve().parents[1]
	ap.add_argument("--inputs", nargs="*", default=[
		str(root / "data" / "traces" / "seed_os_cli.jsonl"),
		str(root / "data" / "traces" / "seed_os_cli.verified.llm.jsonl"),
		str(root / "data" / "traces" / "loop_run.jsonl"),
		str(root / "data" / "traces" / "loop_web_dom.jsonl"),
		str(root / "data" / "traces" / "web_dom.verified.jsonl"),
	])
	ap.add_argument("--out", default=str(root / "data" / "traces" / "all.jsonl"))
	args = ap.parse_args()

	out = Path(args.out)
	out.parent.mkdir(parents=True, exist_ok=True)

	seen_keys: set[str] = set()
	count = 0