"""Materialize built-in persona seed versions on demand. Default mode is a dry run: report the repository persona seed manifest without touching the database. Use --apply when an operator intentionally wants to run the existing idempotent DB materializer. """ from __future__ import annotations import argparse import asyncio import json import sys from pathlib import Path from typing import Any REPO_ROOT = Path(__file__).resolve().parents[1] API_ROOT = REPO_ROOT / "apps" / "api" if str(API_ROOT) not in sys.path: sys.path.insert(0, str(API_ROOT)) from app.persona_repository import ( # noqa: E402 SEED_VERSION, built_in_personas, materialize_seed_personas, seed_persona_id, ) from app.db import close_pool, init_pool # noqa: E402 def _manifest_rows() -> list[dict[str, Any]]: rows: list[dict[str, Any]] = [] for card in built_in_personas(): rows.append( { "code": card.code, "version": SEED_VERSION, "persona_id": str(seed_persona_id(card.code)), "display_name": card.display_name, "difficulty": card.difficulty, "theory_target": list(card.theory_target), "source_provenance": card.source_provenance, "synthetic": bool(card.is_synthetic), } ) return rows def _print_text(*, applied: bool, count: int | None, rows: list[dict[str, Any]]) -> None: mode = "APPLY" if applied else "DRY-RUN" print(f"persona seed materializer: {mode}") print(f"seed_version: {SEED_VERSION}") print(f"manifest_count: {len(rows)}") if count is not None: print(f"materialize_calls: {count}") for row in rows: theories = ",".join(row["theory_target"]) print( f"- {row['code']} v{row['version']} {row['persona_id']} " f"{row['display_name']} [{theories}]" ) async def _main_async(args: argparse.Namespace) -> int: rows = _manifest_rows() count: int | None = None if args.apply: await init_pool() try: count = await materialize_seed_personas() finally: await close_pool() payload = { "applied": bool(args.apply), "seed_version": SEED_VERSION, "manifest_count": len(rows), "materialize_calls": count, "personas": rows, } if args.json: print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True)) else: _print_text(applied=bool(args.apply), count=count, rows=rows) return 0 def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "Report or apply the repository persona seed/version manifest. " "Dry-run is the default; --apply performs DB writes through the " "existing idempotent materializer." ) ) parser.add_argument("--apply", action="store_true", help="write missing seed versions to DB") parser.add_argument("--json", action="store_true", help="emit machine-readable JSON") return parser.parse_args(argv) def main(argv: list[str] | None = None) -> int: args = parse_args(list(argv if argv is not None else sys.argv[1:])) return asyncio.run(_main_async(args)) if __name__ == "__main__": raise SystemExit(main())