#!/usr/bin/env python3 """Compile human-owned Pack and contract sources into runtime registries and architecture docs. Generated output lives under ``org-os/generated`` and must not be edited manually. ``--check`` fails when any generated file drifts from its sources. """ from __future__ import annotations import hashlib import os import sys from typing import Any import yaml ROOT = os.environ.get("CLAUDE_PROJECT_DIR") or os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) PACK_INDEX = os.path.join(ROOT, "org-os", "packs", "pack-index.yaml") REG = os.path.join(ROOT, "org-os", "00-role-registry") WORK = os.path.join(ROOT, "org-os", "06-agent-work") OUT = os.path.join(ROOT, "org-os", "generated") def load(path: str) -> dict[str, Any]: return yaml.safe_load(open(path, encoding="utf-8")) or {} def sha(path: str) -> str: digest = hashlib.sha256() with open(path, "rb") as handle: for chunk in iter(lambda: handle.read(65536), b""): digest.update(chunk) return digest.hexdigest() def method_sources() -> list[str]: directory = os.path.join(REG, "role-working-methods") index = os.path.join(directory, "index.yaml") includes = (load(index).get("role-method-contracts") or {}).get("includes", []) or [] return [index] + [os.path.join(directory, value) for value in includes] def architecture_views(packs: dict[str, Any], source_hashes: dict[str, str]) -> dict[str, str]: """Build four generated views without duplicating the exact YAML registries.""" source_fingerprint = hashlib.sha256( "\n".join(f"{name}:{digest}" for name, digest in sorted(source_hashes.items())).encode() ).hexdigest() generated_header = ( "# generated by .claude/hooks/compile_orgos_registry.py — do not edit\n" f"# source-sha256: {source_fingerprint}" ) static_rows = [ generated_header, "direction: right", 'sources: "Human-owned contracts" {', ' roles: "roles + profiles"', ' families: "family candidate pools"', ' packs: "pack index"', ' methods: "working methods"', ' artifacts: "artifact contracts"', "}", 'compiler: "Org OS compiler"', 'generated: "Generated registries + architecture views"', 'runtime: "Common execution kernel" {', ' intake: "intake classifier"', ' planner: "role / budget planner"', ' context: "context package binder"', ' state: "event + state services"', ' observer: "usage observer"', "}", "sources -> compiler -> generated -> runtime", 'domain_packs: "Domain packs" {', ] for pack_name, definition in packs["packs"].items(): node = "pack_" + pack_name.replace("-", "_") label = f"{pack_name} [{definition['plane']}]\\n{len(definition.get('family-ids', []))} families" static_rows.append(f' {node}: "{label}"') static_rows += ["}", "domain_packs -> sources.packs", "runtime.planner -> domain_packs", ""] runtime = generated_header + """ direction: right request: "Request" intake: "Deterministic intake\\nlight | substantial | strategic" planner: "Minimum-sufficient role planner\\ncoverage + budget + independence" package: "Immutable context package\\nrole + tools + paths + SHA" agent: "Concrete role agent" projection: "Projection-first report" review: "Independent reviewer" ledger: "Append-only event / usage ledgers" request -> intake -> planner -> package -> agent -> projection projection -> review: "when required" intake -> ledger planner -> ledger package -> ledger agent -> ledger review -> ledger """ authority = generated_header + """ direction: down control_plane: "Control plane" { intake: "classify" planner: "select / budget" guard: "bind tools + paths" state: "record truth" } decision_plane: "Decision plane" { decider: "concrete decision role" authority: "approve one-way-door decisions" } delivery_plane: "Design + delivery planes" { producer: "concrete producer role" artifact: "versioned artifact" } assurance_plane: "Assurance plane" { reviewer: "different concrete reviewer role" verdict: "evidence-backed verdict" } control_plane.planner -> delivery_plane.producer: "assign" control_plane.guard -> delivery_plane.producer: "constrain" delivery_plane.artifact -> assurance_plane.reviewer: "review" assurance_plane.verdict -> decision_plane.decider: "escalate if authority needed" decision_plane.authority -> control_plane.state: "immutable decision event" """ events = generated_header + """ direction: right commands: "Commands" { selection: "SelectionPlanCreated" spawn: "SpawnBindingPending / Claimed" artifact: "ArtifactSubmitted / Reviewed" decision: "DecisionRecorded" } event_store: "Append-only event store" materializer: "Deterministic materializer" views: "Materialized views" { workflow: "workflow state" registry: "subagent registry" usage: "token + context metrics" } rehydration: "Tiered rehydration\\nprojection -> evidence index -> source" commands -> event_store -> materializer -> views -> rehydration event_store -> materializer: "replay" """ return { "static-components.d2": "\n".join(static_rows), "runtime-sequence.d2": runtime, "authority-swimlane.d2": authority, "event-model.d2": events, } def compile_outputs() -> dict[str, str]: packs = load(PACK_INDEX)["org-os-packs"] roles_path = os.path.join(REG, "roles.yaml") profiles_path = os.path.join(REG, "role-profiles.yaml") families_path = os.path.join(REG, "capability-families.yaml") artifacts_path = os.path.join(WORK, "generated", "artifact-registry.yaml") contracts_path = os.path.join(WORK, "workflow-contracts.yaml") roles = load(roles_path)["role-registry"] profiles = load(profiles_path)["role-profiles"] family_doc = load(families_path)["capability-families"] artifacts = load(artifacts_path)["artifact-registry"] contracts = load(contracts_path)["workflow-contracts"] families = {item["family-id"]: item for item in family_doc["families"]} role_map = {item["role-id"]: item for item in roles["roles"]} profile_map = {item["role-id"]: item for item in profiles["profiles"]} ownership: dict[str, dict[str, str]] = {} for pack_name, definition in packs["packs"].items(): for family_id in definition.get("family-ids", []) or []: if family_id in ownership: raise ValueError(f"family belongs to multiple packs: {family_id}") if family_id not in families: raise ValueError(f"pack references unknown family: {family_id}") ownership[family_id] = {"pack": pack_name, "plane": definition["plane"]} missing = sorted(set(families) - set(ownership)) if missing: raise ValueError(f"families missing from pack index: {missing}") source_paths = [PACK_INDEX, roles_path, profiles_path, families_path, artifacts_path, contracts_path] + method_sources() source_hashes = {os.path.relpath(path, ROOT): sha(path) for path in source_paths} compiled_families = [] bound_roles = set() for family_id, family in families.items(): entry = dict(family) entry.update(ownership[family_id]) entry["execution-identity"] = "concrete-role-only" entry["agent-card"] = None compiled_families.append(entry) for role_id in entry.get("member-role-ids", []) or []: if role_id in bound_roles: raise ValueError(f"role belongs to multiple families: {role_id}") if role_id not in role_map or role_id not in profile_map: raise ValueError(f"family role lacks role/profile: {role_id}") bound_roles.add(role_id) if bound_roles != set(role_map): raise ValueError(f"family coverage mismatch: {sorted(set(role_map) ^ bound_roles)}") role_entries = [] family_by_role = {role_id: family_id for family_id, family in families.items() for role_id in family.get("member-role-ids", []) or []} for role_id, role in role_map.items(): family_id = family_by_role[role_id] role_entries.append({ **role, "family-id": family_id, "pack": ownership[family_id]["pack"], "plane": ownership[family_id]["plane"], "agent-card": f".claude/agents/{role_id.lower()}.md", }) methods = {} for path in method_sources()[1:]: methods.update(load(path).get("role-working-methods", {}) or {}) if set(methods) != set(role_map): raise ValueError("method registry must cover every concrete role exactly once") commands = {} stages = {} for workflow_name, workflow in (contracts.get("workflows") or {}).items(): stages[workflow_name] = list((workflow.get("stages") or {}).keys()) for stage, definition in (workflow.get("stages") or {}).items(): command = definition.get("command") if isinstance(definition, dict) else None if command: commands.setdefault(command, []).append({"workflow": workflow_name, "stage": stage}) generated = { "role-registry.yaml": { "generated-role-registry": {"version": 1, "generated-by": ".claude/hooks/compile_orgos_registry.py", "source-sha256": source_hashes, "role-count": len(role_entries), "roles": role_entries}}, "family-registry.yaml": { "generated-family-registry": {"version": 1, "generated-by": ".claude/hooks/compile_orgos_registry.py", "source-sha256": source_hashes, "family-count": len(compiled_families), "families": compiled_families}}, "method-registry.yaml": { "generated-method-registry": {"version": 1, "generated-by": ".claude/hooks/compile_orgos_registry.py", "source-sha256": source_hashes, "role-count": len(methods), "roles": methods}}, "architecture-index.yaml": { "architecture-index": {"version": 1, "generated-by": ".claude/hooks/compile_orgos_registry.py", "source-sha256": source_hashes, "counts": {"roles": len(role_entries), "families": len(compiled_families), "agent-cards": len(role_entries), "artifact-kinds": int(artifacts.get("artifact-kind-count") or len(artifacts.get("artifact-kinds", {}))), "packs": len(packs["packs"]), "planes": len(packs["planes"])}, "packs": packs["packs"], "workflow-stages": stages, "command-map": commands}}, } outputs = {name: yaml.safe_dump(doc, sort_keys=False, allow_unicode=True) for name, doc in generated.items()} index = generated["architecture-index.yaml"]["architecture-index"] rows = ["# Generated architecture index", "", "이 파일은 `compile_orgos_registry.py`가 생성합니다. 수기 수정 금지.", "", "| Registry | Count |", "|---|---:|", f"| Concrete roles / agent cards | {index['counts']['roles']} |", f"| Family metadata pools | {index['counts']['families']} |", f"| Domain packs | {index['counts']['packs']} |", f"| Responsibility planes | {index['counts']['planes']} |", f"| Artifact kinds | {index['counts']['artifact-kinds']} |", "", "## Packs", "", "| Pack | Plane | Families |", "|---|---|---|"] for pack_name, definition in packs["packs"].items(): rows.append(f"| {pack_name} | {definition['plane']} | {', '.join(definition['family-ids'])} |") rows += [ "", "## Architecture views", "", "- `static-components.d2` — source, compiler, kernel, Pack 정적 구성", "- `runtime-sequence.d2` — intake부터 projection/review까지의 실행 흐름", "- `authority-swimlane.d2` — control/decision/delivery/assurance 권한 경계", "- `event-model.d2` — append-only event와 materialized view 관계", "", "Family는 actor가 아니며 `.claude/agents`에는 concrete role card만 생성됩니다.", "", ] outputs["README.generated.md"] = "\n".join(rows) outputs.update(architecture_views(packs, source_hashes)) return outputs def main() -> int: check = "--check" in sys.argv[1:] try: outputs = compile_outputs() except Exception as exc: sys.stderr.write(f"[orgos-registry] compile failed: {exc}\n") return 2 drift = [] for name, expected in outputs.items(): path = os.path.join(OUT, name) actual = open(path, encoding="utf-8").read() if os.path.exists(path) else None if actual != expected: drift.append(name) if not check: os.makedirs(OUT, exist_ok=True) with open(path, "w", encoding="utf-8") as handle: handle.write(expected) if check and drift: sys.stderr.write(f"[orgos-registry] generated drift: {', '.join(drift)}\n") return 2 print( "OK orgos registry: 4 registries + generated README + 4 architecture views " f"({'checked' if check else 'written'})" ) return 0 if __name__ == "__main__": sys.exit(main())