#!/usr/bin/env python3 """Generate every platform adapter from repository-neutral workflow/role sources. The generator intentionally uses only the Python standard library. Neutral source bodies live below ``harness/source``; platform-only execution metadata (tool names, model selection, and sandbox mode) lives beside this adapter. """ from __future__ import annotations import argparse import hashlib import json import sys from dataclasses import dataclass from pathlib import Path from typing import Any, Iterable DEFAULT_ROOT = Path(__file__).resolve().parents[2] MANIFEST_REL = Path("harness/source/generation-manifest.json") PLATFORM_METADATA_REL = Path("harness/adapters/platform-metadata.json") EXECUTION_PROFILES_REL = Path("harness/source/execution-profiles.json") MARKDOWN_MARKER = "" COMMENT_MARKER = "# GENERATED from {source} sha256:{digest}; DO NOT EDIT" JSON_MARKER = "GENERATED from {source} sha256:{digest}; DO NOT EDIT" SUPPORTED_SOURCE_KINDS = {"workflow", "agent"} SUPPORTED_TARGET_KINDS = { "agent-skill", "agent-workflow", "claude-command", "plugin-agent-md", "claude-agent-md", "codex-agent-md", "codex-agent-toml", "antigravity-agent-json", } SUPPORTED_HEADING_LOCALES = {"en", "ko-KR", "mixed"} TARGET_KIND_PREFIXES = { "agent-skill": ".agents/skills/", "agent-workflow": ".agents/workflows/", "claude-command": ".claude/commands/", "plugin-agent-md": ".agents/plugins/wiki-superpowers/agents/", "claude-agent-md": ".claude/agents/", "codex-agent-md": ".codex/agents/", "codex-agent-toml": ".codex/agents/", "antigravity-agent-json": ".agents/agents/", } EXECUTION_KINDS = {"orchestrated", "deterministic"} EXECUTION_PROFILES = {"capture", "design", "audit", "publish"} EXECUTION_RISKS = {"low", "medium", "high", "critical"} sys.path.insert(0, str(DEFAULT_ROOT / "harness/runtime")) from fs_transaction import replace_many # noqa: E402 class GenerationError(ValueError): """Raised when the neutral-source contract is internally inconsistent.""" @dataclass(frozen=True) class Target: kind: str path: str @dataclass(frozen=True) class Source: kind: str identifier: str description: str argument_hint: str | None heading_locale: str metadata_path: str body_path: str body: str digest: str execution_contract: dict[str, Any] | None targets: tuple[Target, ...] @dataclass(frozen=True) class Catalog: root: Path manifest: dict[str, Any] platform_metadata: dict[str, Any] sources: tuple[Source, ...] @property def targets(self) -> tuple[tuple[Source, Target], ...]: return tuple((source, target) for source in self.sources for target in source.targets) @dataclass(frozen=True) class GenerationResult: stale: tuple[str, ...] = () missing: tuple[str, ...] = () extra: tuple[str, ...] = () written: tuple[str, ...] = () @property def drift(self) -> tuple[str, ...]: return self.stale + self.missing @property def ok(self) -> bool: return not (self.stale or self.missing or self.extra) def _read_json(path: Path) -> dict[str, Any]: data = json.loads(path.read_text(encoding="utf-8")) if not isinstance(data, dict): raise GenerationError(f"JSON root must be an object: {path}") return data def _safe_rel(value: Any, *, field: str) -> str: if not isinstance(value, str) or not value.strip(): raise GenerationError(f"{field} must be a non-empty path") path = Path(value) if path.is_absolute() or ".." in path.parts: raise GenerationError(f"{field} escapes repository: {value}") return path.as_posix() def _non_empty_string(data: dict[str, Any], key: str, *, source: str) -> str: value = data.get(key) if not isinstance(value, str) or not value.strip(): raise GenerationError(f"{source}: missing non-empty {key}") return value def _composite_digest(metadata_raw: bytes, body_path: str, body_raw: bytes) -> str: digest = hashlib.sha256() digest.update(metadata_raw) digest.update(b"\0") digest.update(body_path.encode("utf-8")) digest.update(b"\0") digest.update(body_raw) return digest.hexdigest() def _expand_globs(root: Path, patterns: Iterable[str], excludes: Iterable[str] = ()) -> set[str]: excluded = set(excludes) found: set[str] = set() for pattern in patterns: found.update( path.relative_to(root).as_posix() for path in root.glob(pattern) if path.is_file() ) return found - excluded def _validate_source_inventory(root: Path, manifest: dict[str, Any]) -> None: inventory = manifest.get("source_inventory") if not isinstance(inventory, dict): raise GenerationError("manifest source_inventory must be an object") patterns = inventory.get("include") if not isinstance(patterns, list) or not all(isinstance(item, str) for item in patterns): raise GenerationError("manifest source_inventory.include must be a string list") declared_entries = manifest.get("sources") if not isinstance(declared_entries, list) or not declared_entries: raise GenerationError("manifest sources must be a non-empty list") declared_metadata: list[str] = [] declared_bodies: list[str] = [] for entry in declared_entries: if not isinstance(entry, dict): raise GenerationError("manifest source entries must be objects") declared_metadata.append(_safe_rel(entry.get("metadata"), field="source metadata")) declared_bodies.append(_safe_rel(entry.get("body"), field="source body")) duplicates = sorted( path for path in set(declared_metadata) if declared_metadata.count(path) > 1 ) if duplicates: raise GenerationError("duplicate source mapping: " + ", ".join(duplicates)) declared = set(declared_metadata) | set(declared_bodies) actual = _expand_globs(root, patterns) missing = sorted(declared - actual) extra = sorted(actual - declared) if missing: raise GenerationError("missing source files: " + ", ".join(missing)) if extra: raise GenerationError("extra source files: " + ", ".join(extra)) def _load_source(root: Path, entry: dict[str, Any]) -> Source: metadata_path = _safe_rel(entry.get("metadata"), field="source metadata") body_path = _safe_rel(entry.get("body"), field="source body") metadata_abs = root / metadata_path body_abs = root / body_path metadata_raw = metadata_abs.read_bytes() body_raw = body_abs.read_bytes() data = json.loads(metadata_raw.decode("utf-8")) if not isinstance(data, dict): raise GenerationError(f"{metadata_path}: JSON root must be an object") kind = data.get("source_kind") if kind not in SUPPORTED_SOURCE_KINDS: raise GenerationError(f"{metadata_path}: unsupported source_kind: {kind}") expected_schema = 2 if kind == "workflow" else 1 if data.get("schema_version") != expected_schema: raise GenerationError(f"{metadata_path}: expected schema_version {expected_schema}") identifier = _non_empty_string(data, "id", source=metadata_path) description = _non_empty_string(data, "description", source=metadata_path) heading_locale = data.get("heading_locale") if heading_locale not in SUPPORTED_HEADING_LOCALES: raise GenerationError( f"{metadata_path}: heading_locale must be one of {sorted(SUPPORTED_HEADING_LOCALES)}" ) if data.get("body_path") != body_path: raise GenerationError( f"{metadata_path}: body_path {data.get('body_path')!r} does not match manifest {body_path!r}" ) argument_hint: str | None = None execution_contract: dict[str, Any] | None = None if kind == "workflow": argument_hint = _non_empty_string(data, "argument_hint", source=metadata_path) execution_contract = _validate_execution_contract(root, data, metadata_path) elif "execution_contract" in data: raise GenerationError(f"{metadata_path}: agents cannot declare execution_contract") raw_targets = data.get("targets") if not isinstance(raw_targets, list) or not raw_targets: raise GenerationError(f"{metadata_path}: targets must be a non-empty list") targets: list[Target] = [] for raw_target in raw_targets: if not isinstance(raw_target, dict): raise GenerationError(f"{metadata_path}: targets must be objects") target_kind = raw_target.get("kind") if target_kind not in SUPPORTED_TARGET_KINDS: raise GenerationError(f"{metadata_path}: unsupported target kind: {target_kind}") target_path = _safe_rel(raw_target.get("path"), field="target path") if not target_path.startswith(TARGET_KIND_PREFIXES[target_kind]): raise GenerationError( f"{metadata_path}: {target_path} does not match target kind {target_kind}" ) if kind == "workflow" and target_kind not in { "agent-skill", "agent-workflow", "claude-command", }: raise GenerationError(f"{metadata_path}: workflow cannot generate {target_kind}") if kind == "agent" and target_kind in { "agent-skill", "agent-workflow", "claude-command", }: raise GenerationError(f"{metadata_path}: agent cannot generate {target_kind}") targets.append(Target(target_kind, target_path)) body = body_raw.decode("utf-8") if not body.strip(): raise GenerationError(f"{body_path}: source body must not be empty") if not body.endswith("\n"): raise GenerationError(f"{body_path}: source body must end with a newline") digest = _composite_digest(metadata_raw, body_path, body_raw) return Source( kind=kind, identifier=identifier, description=description, argument_hint=argument_hint, heading_locale=heading_locale, metadata_path=metadata_path, body_path=body_path, body=body, digest=digest, execution_contract=execution_contract, targets=tuple(targets), ) def _validate_execution_contract( root: Path, data: dict[str, Any], metadata_path: str, ) -> dict[str, Any]: if "profile" in data or "default_risk" in data: raise GenerationError( f"{metadata_path}: profile/default_risk must be nested under execution_contract" ) contract = data.get("execution_contract") if not isinstance(contract, dict): raise GenerationError(f"{metadata_path}: execution_contract must be an object") allowed = { "kind", "profile", "default_risk", "entrypoint", "dry_run_first", "result_schema", "design_bearing", } extra = sorted(set(contract) - allowed) if extra: raise GenerationError( f"{metadata_path}: unsupported execution_contract fields: {', '.join(extra)}" ) kind = contract.get("kind") profile = contract.get("profile") default_risk = contract.get("default_risk") if kind not in EXECUTION_KINDS: raise GenerationError(f"{metadata_path}: unsupported execution kind: {kind}") if profile not in EXECUTION_PROFILES: raise GenerationError(f"{metadata_path}: unsupported execution profile: {profile}") if default_risk not in EXECUTION_RISKS: raise GenerationError(f"{metadata_path}: unsupported default_risk: {default_risk}") if not isinstance(contract.get("design_bearing"), bool): raise GenerationError(f"{metadata_path}: design_bearing must be boolean") deterministic_fields = {"entrypoint", "dry_run_first", "result_schema"} present_deterministic = deterministic_fields.intersection(contract) if kind == "deterministic": missing = sorted(deterministic_fields - present_deterministic) if missing: raise GenerationError( f"{metadata_path}: deterministic contract missing: {', '.join(missing)}" ) entrypoint = _safe_rel(contract.get("entrypoint"), field="execution entrypoint") if not entrypoint.endswith(".py") or not (root / entrypoint).is_file(): raise GenerationError( f"{metadata_path}: deterministic entrypoint is not executable source: {entrypoint}" ) if contract.get("dry_run_first") is not True: raise GenerationError( f"{metadata_path}: deterministic workflow must set dry_run_first=true" ) result_schema = contract.get("result_schema") if not isinstance(result_schema, str) or not result_schema.strip(): raise GenerationError(f"{metadata_path}: result_schema must be non-empty") elif present_deterministic: raise GenerationError( f"{metadata_path}: orchestrated workflow cannot declare deterministic fields" ) return dict(contract) def load_catalog(root: Path = DEFAULT_ROOT) -> Catalog: root = root.resolve() manifest_path = root / MANIFEST_REL platform_path = root / PLATFORM_METADATA_REL manifest = _read_json(manifest_path) if manifest.get("schema_version") != 1: raise GenerationError("generation manifest has unsupported schema_version") _validate_source_inventory(root, manifest) execution_profiles = _read_json(root / EXECUTION_PROFILES_REL) if execution_profiles.get("schema_version") != "execution-profiles/v1": raise GenerationError("execution profile source has unsupported schema_version") if set(execution_profiles.get("profiles", {})) != EXECUTION_PROFILES: raise GenerationError("generator workflow profiles drift from execution profile SSOT") if set(execution_profiles.get("risk_levels", [])) != EXECUTION_RISKS: raise GenerationError("generator workflow risks drift from execution profile SSOT") platform_metadata = _read_json(platform_path) if platform_metadata.get("schema_version") != 1: raise GenerationError("platform metadata has unsupported schema_version") sources = tuple(_load_source(root, entry) for entry in manifest["sources"]) identifiers: set[tuple[str, str]] = set() mapped_targets: dict[str, str] = {} for source in sources: source_key = (source.kind, source.identifier) if source_key in identifiers: raise GenerationError(f"duplicate logical source: {source.kind}:{source.identifier}") identifiers.add(source_key) for target in source.targets: previous = mapped_targets.get(target.path) if previous is not None: raise GenerationError( f"duplicate target mapping: {target.path} ({previous}, {source.metadata_path})" ) mapped_targets[target.path] = source.metadata_path expected_count = manifest.get("expected_target_count") if not isinstance(expected_count, int) or expected_count <= 0: raise GenerationError("manifest expected_target_count must be a positive integer") if len(mapped_targets) != expected_count: raise GenerationError( f"mapped target count {len(mapped_targets)} != expected_target_count {expected_count}" ) configured_agents = platform_metadata.get("agents") if not isinstance(configured_agents, dict): raise GenerationError("platform metadata agents must be an object") agent_ids = {source.identifier for source in sources if source.kind == "agent"} extra_agent_metadata = sorted(set(configured_agents) - agent_ids) if extra_agent_metadata: raise GenerationError("extra platform agent metadata: " + ", ".join(extra_agent_metadata)) for source in sources: if source.kind != "agent": continue required_platforms = { "claude" if target.kind == "claude-agent-md" else "codex" if target.kind.startswith("codex-agent-") else "antigravity" if target.kind == "antigravity-agent-json" else "plugin" for target in source.targets } metadata = configured_agents.get(source.identifier) if not isinstance(metadata, dict): raise GenerationError(f"missing platform metadata for agent {source.identifier}") missing_platforms = sorted(required_platforms - set(metadata)) if missing_platforms: raise GenerationError( f"missing platform metadata for {source.identifier}: {', '.join(missing_platforms)}" ) return Catalog(root, manifest, platform_metadata, sources) def _yaml_string(value: str) -> str: return json.dumps(value, ensure_ascii=False) def _toml_string(value: str) -> str: return json.dumps(value, ensure_ascii=False) def _markdown_marker(source: Source) -> str: return MARKDOWN_MARKER.format(source=source.metadata_path, digest=source.digest) def _workflow_policy_block(workflow: str) -> str: return f"""## 실행 정책 해석 1. 작업을 시작하기 전에 다음 baseline resolver를 실행하고 `status: PLANNED`, `phase: baseline`을 확인한다. ```bash python3 harness/runtime/workflow_dispatch.py {workflow} --root . --phase baseline ``` 2. `mandatory_gates`에서 `required`인 gate는 risk와 무관하게 생략하지 않는다. review dispatch와 응답 형식은 resolver의 `dispatch`, `review_intensity`, `output_contract`만 따른다. 3. finding과 claim 집계가 끝나면 최종 응답 전에 같은 workflow를 `--phase final --finding-count `으로 다시 resolve한다. claim이 있으면 `--claims-present`, 공개 claim이면 `--public-claims-present`를 argv 항목으로 추가한다. 4. final resolver의 필수 gate·review·output contract를 반영하기 전에는 완료를 선언하지 않는다.""" def _render_workflow(source: Source, target: Target) -> str: assert source.argument_hint is not None assert source.execution_contract is not None contract = json.dumps(source.execution_contract, ensure_ascii=False, separators=(",", ":")) if target.kind == "claude-command": frontmatter = ( "---\n" f"description: {_yaml_string(source.description)}\n" f"argument-hint: {source.argument_hint}\n" f"execution_contract: {contract}\n" "---" ) arguments = "$ARGUMENTS" prefix = "" elif target.kind == "agent-skill": frontmatter = ( "---\n" f"name: {source.identifier}\n" f"description: {_yaml_string(f'{source.description} (입력: {source.argument_hint})')}\n" f"execution_contract: {contract}\n" "---" ) arguments = source.argument_hint prefix = "" elif target.kind == "agent-workflow": frontmatter = ( "---\n" f"description: {_yaml_string(source.description)}\n" f"execution_contract: {contract}\n" "---" ) arguments = source.argument_hint prefix = ( f"사용자가 `/{source.identifier} {source.argument_hint}` 를 입력하면 아래 절차를 수행한다.\n\n" ) else: # pragma: no cover - source validation prevents this path raise GenerationError(f"unsupported workflow target kind: {target.kind}") body = source.body.replace("{{arguments}}", arguments) policy = _workflow_policy_block(source.identifier) return f"{frontmatter}\n{_markdown_marker(source)}\n\n{prefix}{policy}\n\n{body}".rstrip() + "\n" def _agent_platform(catalog: Catalog, source: Source, platform: str) -> dict[str, Any]: metadata = catalog.platform_metadata["agents"][source.identifier].get(platform) if not isinstance(metadata, dict): raise GenerationError(f"missing {platform} metadata for {source.identifier}") return metadata def _render_agent(catalog: Catalog, source: Source, target: Target) -> str: marker = _markdown_marker(source) if target.kind in {"plugin-agent-md", "codex-agent-md"}: frontmatter = ( "---\n" f"name: {source.identifier}\n" f"description: {_yaml_string(source.description)}\n" "---" ) return f"{frontmatter}\n{marker}\n\n{source.body}".rstrip() + "\n" if target.kind == "claude-agent-md": metadata = _agent_platform(catalog, source, "claude") tools = metadata.get("tools") model = metadata.get("model") if not isinstance(tools, str) or not tools or not isinstance(model, str) or not model: raise GenerationError(f"invalid Claude metadata for {source.identifier}") frontmatter = ( "---\n" f"name: {source.identifier}\n" f"description: {_yaml_string(source.description)}\n" f"tools: {tools}\n" f"model: {model}\n" "---" ) return f"{frontmatter}\n{marker}\n\n{source.body}".rstrip() + "\n" if target.kind == "codex-agent-toml": metadata = _agent_platform(catalog, source, "codex") sandbox_mode = metadata.get("sandbox_mode") if not isinstance(sandbox_mode, str) or not sandbox_mode: raise GenerationError(f"invalid Codex metadata for {source.identifier}") if "'''" in source.body: raise GenerationError(f"{source.body_path}: cannot embed triple single quote in TOML") comment = COMMENT_MARKER.format(source=source.metadata_path, digest=source.digest) return ( f"{comment}\n" f"name = {_toml_string(source.identifier)}\n" f"description = {_toml_string(source.description)}\n" f"sandbox_mode = {_toml_string(sandbox_mode)}\n" "developer_instructions = '''\n" f"{source.body.rstrip()}\n" "'''\n" ) if target.kind == "antigravity-agent-json": metadata = _agent_platform(catalog, source, "antigravity") hidden = metadata.get("hidden") tool_names = metadata.get("tool_names") include_sections = metadata.get("include_sections") if not isinstance(hidden, bool): raise GenerationError(f"invalid Antigravity hidden flag for {source.identifier}") if not isinstance(tool_names, list) or not all(isinstance(item, str) for item in tool_names): raise GenerationError(f"invalid Antigravity tool_names for {source.identifier}") if not isinstance(include_sections, list) or not all( isinstance(item, str) for item in include_sections ): raise GenerationError(f"invalid Antigravity include_sections for {source.identifier}") payload = { "_generated": JSON_MARKER.format( source=source.metadata_path, digest=source.digest, ), "name": source.identifier, "description": source.description, "hidden": hidden, "config": { "customAgent": { "systemPromptSections": [ {"title": "Agent System Instructions", "content": source.body.rstrip("\n")} ], "toolNames": tool_names, "systemPromptConfig": {"includeSections": include_sections}, } }, } return json.dumps(payload, ensure_ascii=False, indent=2) + "\n" raise GenerationError(f"unsupported agent target kind: {target.kind}") def render_target(catalog: Catalog, source: Source, target: Target) -> str: if source.kind == "workflow": return _render_workflow(source, target) return _render_agent(catalog, source, target) def _target_inventory(catalog: Catalog) -> set[str]: inventory = catalog.manifest.get("target_inventory") if not isinstance(inventory, dict): raise GenerationError("manifest target_inventory must be an object") include = inventory.get("include") exclude = inventory.get("exclude", []) if not isinstance(include, list) or not all(isinstance(item, str) for item in include): raise GenerationError("target_inventory.include must be a string list") if not isinstance(exclude, list) or not all(isinstance(item, str) for item in exclude): raise GenerationError("target_inventory.exclude must be a string list") return _expand_globs(catalog.root, include, exclude) def _render_inventory(catalog: Catalog) -> dict[Path, bytes]: rendered: dict[Path, bytes] = {} for source, target in catalog.targets: path = (catalog.root / target.path).resolve() if path in rendered: raise GenerationError(f"duplicate rendered target: {target.path}") rendered[path] = render_target(catalog, source, target).encode("utf-8") if len(rendered) != catalog.manifest["expected_target_count"]: raise GenerationError("rendered target inventory count drift") return rendered def generate(root: Path = DEFAULT_ROOT, *, check: bool = True) -> GenerationResult: catalog = load_catalog(root) declared = {target.path for _, target in catalog.targets} rendered = _render_inventory(catalog) rendered_rel = {path.relative_to(catalog.root).as_posix() for path in rendered} if rendered_rel != declared: raise GenerationError("rendered target inventory differs from declared inventory") actual = _target_inventory(catalog) extra = tuple(sorted(actual - declared)) missing = tuple(sorted(declared - actual)) if extra: return GenerationResult(missing=missing, extra=extra) stale: list[str] = [] for path, expected in rendered.items(): relative = path.relative_to(catalog.root).as_posix() actual_bytes = path.read_bytes() if path.exists() else None if actual_bytes != expected and path.exists(): stale.append(relative) if check: return GenerationResult( stale=tuple(sorted(stale)), missing=missing, extra=extra, ) if stale or missing: # One transaction owns the complete generated inventory. Passing all # rendered bytes (not only drifted targets) prevents mixed revisions if # the declaration or render order changes during future maintenance. replace_many(rendered) return GenerationResult(written=tuple(sorted(declared))) return GenerationResult() def _print_result(result: GenerationResult, *, check: bool) -> None: if result.stale: print("stale generated targets: " + ", ".join(result.stale), file=sys.stderr) if result.missing: print("missing generated targets: " + ", ".join(result.missing), file=sys.stderr) if result.extra: print("extra unmapped targets: " + ", ".join(result.extra), file=sys.stderr) if not check: for path in result.written: print(f"generated {path}") def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) mode = parser.add_mutually_exclusive_group(required=True) mode.add_argument("--check", action="store_true", help="fail on mapping or content drift") mode.add_argument("--write", action="store_true", help="write every missing or stale target") parser.add_argument("--root", type=Path, default=DEFAULT_ROOT, help=argparse.SUPPRESS) args = parser.parse_args(argv) try: result = generate(args.root, check=args.check) except (OSError, GenerationError, json.JSONDecodeError, UnicodeDecodeError) as exc: print(f"adapter generation failed: {exc}", file=sys.stderr) return 2 _print_result(result, check=args.check) if result.extra or (args.check and not result.ok): return 1 return 0 if __name__ == "__main__": raise SystemExit(main())