#!/usr/bin/env python3 """Prepare selected fixed sources and run explicitly local component checks.""" import argparse from contextlib import contextmanager import os from pathlib import Path import subprocess import sys import tempfile from workspace.cli import Workspace, process_environment from workspace.common import WorkspaceError from workspace.state import State SINGLE = {"test-backend": "Agent-System-Backend", "test-backend-pg": "Agent-System-Backend", "test-core": "Agent-System-Core", "test-agentcmd": "Agent-Command", "test-frontend": "Agent-System-Frontend", "test-ui": "Agent-System-UI", "test-multi-instance": "Agent-System-Backend", "eval-deliverables": "Agent-System-Backend", "dev-backend": "Agent-System-Backend", "dev-frontend": "Agent-System-Frontend"} GO_CHECKS = {"test-backend", "test-backend-pg", "test-core", "test-agentcmd", "test-libs", "test-multi-instance", "eval-deliverables"} def run(command, cwd, env): print("[component-check] " + " ".join(map(str, command)), flush=True) result = subprocess.run(command, cwd=cwd, env=env) if result.returncode: raise WorkspaceError(f"local component command failed ({result.returncode})") def prepare_local_source(workspace, component): """Repeated local npm checks may reuse only recognized generated directories.""" record = workspace.state.data["components"].get(component["id"]) path = workspace.root / component["path"] if record and record.get("mode") == "versioned" and (path / "package.json").is_file(): actual = workspace.git.safe(path, component["repo"], clean=False) commit, source = workspace.baseline(component) generated = ("node_modules/", "dist/") if actual["head"] != commit or actual["branch"] or actual["changes"] or any(not name.startswith(generated) for name in actual["ignored"]): raise WorkspaceError(f"local source or ignored unique files require owner coordination: {component['id']}") record.update(baseline=commit, source=source) workspace.state.save() return workspace.prepare_one(component) @contextmanager def check_sources(workspace, selected, target, overlay): paths = {component["id"]: workspace.root / component["path"] for component in selected} fixed = [component for component in selected if workspace.state.data["components"][component["id"]].get("mode") == "versioned"] if overlay or target not in GO_CHECKS or not fixed: yield paths return # Go tests can create ignored outputs (including Python bytecode). Keep # strict source guards and execute fixed commits in owned disposable clones. # Editable sources and explicit overlays still exercise the owner's code. with tempfile.TemporaryDirectory(prefix="component-check-") as temporary: area = Path(temporary) for component in fixed: name = component["id"] source = paths[name] actual = workspace.git.safe(source, component["repo"]) commit = workspace.state.data["components"][name]["baseline"] if actual["head"] != commit or actual["branch"]: raise WorkspaceError(f"fixed source changed before local check: {name}") destination = area / name workspace.git.call(area, "clone", "--no-checkout", "--no-hardlinks", "--", str(source), str(destination)) workspace.git.call(destination, "checkout", "--detach", commit) paths[name] = destination print(f"[component-check] fixed check snapshot {name} commit={commit}", flush=True) yield paths def check(root, target, overlay): root = root.resolve() if target in ("test-backend-pg", "test-multi-instance") and not os.environ.get("AGENT_PG_TEST_DSN"): raise WorkspaceError("AGENT_PG_TEST_DSN required; real PostgreSQL gate is not skipped") if target == "test-backend-pg" and not os.environ.get("AGENT_REDIS_TEST_ADDR"): raise WorkspaceError("AGENT_REDIS_TEST_ADDR required; real Redis gate is not skipped") workspace = Workspace(root) names = [SINGLE[target]] if target in SINGLE else (["Agent-Native", "Agent-Intent", "Agent-Squad"] if target == "test-libs" else ["Agent-System-Backend", "Agent-System-Frontend"]) selected = [workspace.manifest.select(name)[0] for name in names] with workspace.state.operation(): workspace.state = State(root) if any(component["role"] in ("go-library", "npm-library") for component in selected): for component in workspace.manifest.select(fixed=True): if component["role"] == "application": prepare_local_source(workspace, component) for component in selected: prepare_local_source(workspace, component) workspace.snapshot() env = process_environment(root) if overlay: workspace.go.check_owned() if not workspace.state.data.get("go_work") or not (root / "go.work").is_file(): raise WorkspaceError("LOCAL_OVERLAY=1 requires a registered go.work; use component-edit first") env["GOWORK"] = str(root / "go.work") else: env["GOWORK"] = "off" print(f"[component-check] local developer sources; GOWORK={env['GOWORK']}; registered edit branches and unsaved code retained", flush=True) with check_sources(workspace, selected, target, overlay) as paths: run_checks(root, target, selected, names, paths, env) def run_checks(root, target, selected, names, paths, env): if target == "dev": print("Backend: make dev-backend [LOCAL_OVERLAY=1]\nFrontend: make dev-frontend\nPrivate module transport is scoped to those processes.") return path = paths[names[0]] if target == "dev-backend": run(["go", "run", "./cmd/agent-system", "serve", "--addr", "127.0.0.1:8787", "--data", str(root / "data"), "--plugins", str(root / "plugins")], path, env) elif target == "dev-frontend": run(["npm", "ci", "--ignore-scripts", "--no-fund", "--no-audit"], path, env) run(["npm", "run", "dev"], path, env) elif target == "test-multi-instance": suite = root / "tests/integration/multi-instance" binary = suite / ".build/agent-system" binary.parent.mkdir(parents=True, exist_ok=True) run(["go", "build", "-mod=readonly", "-o", str(binary), "./cmd/agent-system"], path, env | {"CGO_ENABLED": "0"}) run(["go", "test", "-mod=readonly", "-count=1", "-timeout=15m", "."], suite, env | {"AGENT_BIN": str(binary)}) elif target == "eval-deliverables": run(["go", "test", "-mod=readonly", "./eval/", "./internal/quality/", "-count=1"], path, env) elif target in ("test-frontend", "test-ui"): run(["npm", "ci", "--ignore-scripts", "--no-fund", "--no-audit"], path, env) if target == "test-ui": run(["npm", "exec", "--offline", "--", "tsc", "--noEmit"], path, env) run(["npm", "exec", "--offline", "--", "vitest", "run"], path, env) run(["node", "scripts/check-knip.mjs"], path, env) else: for component in selected: source = paths[component["id"]] modules = component.get("modules", []) if target == "test-libs" else [{"directory": "."}] for module in modules: directory = source / module.get("directory", ".") if target in ("test-libs", "test-agentcmd"): run(["go", "vet", "-mod=readonly", "./..."], directory, env) run(["go", "test", "-mod=readonly", "-count=1", "./..."], directory, env) if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1]) parser.add_argument("--target", choices=(*SINGLE, "test-libs", "dev"), required=True) parser.add_argument("--overlay", action="store_true") args = parser.parse_args() try: check(args.root, args.target, args.overlay) except (WorkspaceError, OSError, ValueError) as error: print(f"component-check: {error}", file=sys.stderr) raise SystemExit(1)