#!/usr/bin/env python3 """Executable role planner, family metadata, policy binding and projection tests.""" import glob import importlib.util import os import subprocess import sys import yaml ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) HOOKS = os.path.join(ROOT, ".claude", "hooks") sys.path.insert(0, HOOKS) from orgos.planning.intake_classifier import classify_request # noqa: E402 from orgos.planning.role_selector import resolve_family, select_minimum_sufficient_roles # noqa: E402 passed = failed = 0 def check(name, ok, detail=""): global passed, failed if ok: passed += 1 print(f" PASS {name}") else: failed += 1 print(f" FAIL {name}{': ' + detail if detail else ''}") print("== family metadata is not an execution identity ==") cards = glob.glob(os.path.join(ROOT, ".claude", "agents", "*.md")) check("75 role cards", len(cards) == 75, str(len(cards))) check("zero fam-* cards", glob.glob(os.path.join(ROOT, ".claude", "agents", "fam-*.md")) == []) compiled = subprocess.run([sys.executable, os.path.join(HOOKS, "compile_orgos_registry.py"), "--check"], cwd=ROOT, capture_output=True, text=True) check("Pack registries have no drift", compiled.returncode == 0, compiled.stderr) print("== minimum sufficient selection ==") architecture = resolve_family("FAM-ARCHITECTURE-TECH", ["api", "service-boundary"], "standard") check("fan-out family does not return all seven members", len(architecture["resolved-workers"]) == 1, str(architecture["resolved-workers"])) check("resolved identity is concrete", not architecture["primary-worker"].startswith("FAM-")) plan = select_minimum_sufficient_roles({ "workflow-id": "wf-role-plan", "tier": "standard", "workflow-stage": "design", "candidate-families": ["FAM-ARCHITECTURE-TECH"], "required-artifacts": ["api-contract"], "risks": ["security"], "independent-review-required": True, "signals": ["api", "application", "appsec"], })["selection-plan"] selected = [plan["selected"]["owner"]] + plan["selected"]["contributors"] + plan["selected"]["reviewers"] check("required coverage is complete", plan["status"] == "ready" and plan["coverage"]["missing"] == [], str(plan["coverage"])) check("api producer selected", any("artifact:api-contract" in item["coverage"] for item in selected)) check("security risk selected", any("risk:security" in item["coverage"] for item in selected)) producer_ids = {item["role-id"] for item in selected if "artifact:api-contract" in item["coverage"]} reviewer_ids = {item["role-id"] for item in plan["selected"]["reviewers"]} check("producer and reviewer identities are independent", bool(reviewer_ids) and producer_ids.isdisjoint(reviewer_ids)) check("skipped roles explain why", bool(plan["skipped"]) and all(item.get("reason") for item in plan["skipped"])) blocked = select_minimum_sufficient_roles({ "tier": "standard", "candidate-families": ["FAM-ARCHITECTURE-TECH"], "required-artifacts": ["api-contract"], "token-budget": 1, })["selection-plan"] check("insufficient token budget blocks selection", blocked["status"] == "blocked") print("== deterministic intake avoids blanket CEO ==") check("typo routes light without executive", not classify_request("오탈자 하나 수정")["executive-required"]) check("portfolio pricing routes strategic executive", classify_request("포트폴리오 가격 전략 결정")["executive-required"]) print("== task allowlist binds active subagent ==") import guard_tools as GT # noqa: E402 orig_record, orig_pkg = GT._registry_record, GT._load_bound_package try: GT._registry_record = lambda _agent: {"report_producing": True, "agent_type": "eng-be"} GT._load_bound_package = lambda _record: ({ "target-role-agent": "eng-be", "allowed-tools": ["Read"], "allowed-paths": [ROOT], }, None) check("allowed task tool passes", GT.check("Read", {"file_path": "README.md"}, {"agent_id": "a"}) == (None, None)) check("tool absent from task allowlist blocks", GT.check("Write", {"file_path": "README.md"}, {"agent_id": "a"})[0] == "task-tool-allowlist") finally: GT._registry_record, GT._load_bound_package = orig_record, orig_pkg print("== new reports opt into structured projection ==") new_report_text = open(os.path.join(HOOKS, "new_report.py"), encoding="utf-8").read() check("new report scaffold has projection v1", "projection-version: 1" in new_report_text) check("new report scaffold has all projection fields", all(name in new_report_text for name in ("decision-summary", "evidence-index", "dissent", "open-risks", "artifact-refs"))) print(f"\n{passed} passed, {failed} failed") sys.exit(1 if failed else 0)