#!/usr/bin/env python3 """ Red Queen — C5 CLI (C5A + C5B): kontrollierte Inspektion & Dry-Run. KEIN produktiver Poll-Daemon. KEINE daemon/start-forever-Funktion. Nur explizite, begrenzte Befehle: * poll-once — ein kontrollierter Poll-Zyklus (read-only) * show-pending — ausstehende Commits anzeigen * show-commit — Details eines Commits anzeigen * dry-run-diff — Diff eines Commits anzeigen (read-only) * health — Health-Contract anzeigen * no-write-check — No-Downstream-Write-Guarantee pruefen """ from __future__ import annotations import argparse import json import os import sys from typing import Any, Dict, List, Optional from rq_c5a import C5AStore from rq_c5b import ( C5BPoller, GitReader, assert_no_downstream_write, DEFAULT_POLL_INTERVAL_SECONDS, ENV_POLL_INTERVAL, ) def _store(db_path: str) -> C5AStore: return C5AStore(db_path) def _poller(store: C5AStore, repo_path: str, interval: Optional[int]) -> C5BPoller: return C5BPoller(store, repo_path, poll_interval_seconds=interval) def cmd_poll_once(args: argparse.Namespace) -> int: store = _store(args.db) poller = _poller(store, args.repo, args.interval) result = poller.poll_once() print(json.dumps(result, ensure_ascii=False, indent=2)) store.close() return 0 def cmd_show_pending(args: argparse.Namespace) -> int: store = _store(args.db) pending = store.list_commits() print(json.dumps(pending, ensure_ascii=False, indent=2)) store.close() return 0 def cmd_show_commit(args: argparse.Namespace) -> int: store = _store(args.db) commit = store.get_commit(args.sha) if commit is None: print(f"Commit nicht gefunden: {args.sha}") store.close() return 1 print(json.dumps(commit, ensure_ascii=False, indent=2)) objs = store.list_object_changes(args.sha) if objs: print("--- Object Changes ---") print(json.dumps(objs, ensure_ascii=False, indent=2)) store.close() return 0 def cmd_dry_run_diff(args: argparse.Namespace) -> int: reader = GitReader(args.repo) changes = reader.diff_name_status(args.sha) print(json.dumps(changes, ensure_ascii=False, indent=2)) return 0 def cmd_health(args: argparse.Namespace) -> int: store = _store(args.db) print(json.dumps(store.health(), ensure_ascii=False, indent=2)) store.close() return 0 def cmd_no_write_check(args: argparse.Namespace) -> int: result = assert_no_downstream_write() print(json.dumps(result, ensure_ascii=False, indent=2)) return 0 if result["no_downstream_write"] else 1 def main(argv: Optional[List[str]] = None) -> int: parser = argparse.ArgumentParser( prog="rq_c5_cli", description="Red Queen C5 CLI (C5A + C5B) — kontrollierte Inspektion & Dry-Run", ) parser.add_argument( "--db", default=os.environ.get("C5A_DB", "c5a.db"), help="Pfad zur C5A-SQLite-DB (Env C5A_DB, Default c5a.db)", ) parser.add_argument( "--repo", default=os.environ.get("C5_REPO", "."), help="Pfad zum Forgejo-Clone (Env C5_REPO, Default .)", ) parser.add_argument( "--interval", type=int, default=None, help=f"Poll-Intervall in Sekunden (Env {ENV_POLL_INTERVAL}, Default {DEFAULT_POLL_INTERVAL_SECONDS})", ) sub = parser.add_subparsers(dest="command", required=True) p = sub.add_parser("poll-once", help="Ein kontrollierter Poll-Zyklus (read-only)") p.set_defaults(func=cmd_poll_once) p = sub.add_parser("show-pending", help="Ausstehende Commits anzeigen") p.set_defaults(func=cmd_show_pending) p = sub.add_parser("show-commit", help="Details eines Commits anzeigen") p.add_argument("sha", help="Commit-SHA") p.set_defaults(func=cmd_show_commit) p = sub.add_parser("dry-run-diff", help="Diff eines Commits anzeigen (read-only)") p.add_argument("sha", help="Commit-SHA") p.set_defaults(func=cmd_dry_run_diff) p = sub.add_parser("health", help="Health-Contract anzeigen") p.set_defaults(func=cmd_health) p = sub.add_parser("no-write-check", help="No-Downstream-Write-Guarantee pruefen") p.set_defaults(func=cmd_no_write_check) args = parser.parse_args(argv) return args.func(args) if __name__ == "__main__": sys.exit(main())