"""Logic for the `pr` domain. Transport-agnostic (D-263).""" from __future__ import annotations import shutil from pathlib import Path from tooling.core import config, process from tooling.core.errors import ReachError LOGIN = "schweitz" REPO = "jpmschweitzer/settled-reach" # Paths whose change means the committed systems.db may be stale. The generator # set is read from the registry rather than restated, so this list cannot drift # from tooling/generator_sources.py (T-1067). EXTRA_WATCHED = ( "tooling/domains/atlas/planet/import_heightmaps.py", "tooling/domains/atlas/planet/import_province_boundaries.py", "server/data/systems-schema.sql", "wiki/star-systems/", "wiki/economics/", ) def comment(number: str, body_or_path: str) -> None: """Post a comment on a Gitea PR or issue. `@path` reads the body from a file — the form that exists because a long comment inline would need a `$(...)` subshell, and a subshell breaks the permission gate's prefix matching (.claude/rules/tea-cli.md). Every flag is explicit for the same reason the rule gives: omitting `--login` or `--repo` sends `tea` interactive, and an interactive prompt with no TTY does not wait — it crashes. """ body = _resolve_body(body_or_path) tea = shutil.which("tea") if tea is None: raise ReachError( "the tea CLI is not on PATH", fix="tea is symlinked into ~/.local/bin — check that, rather than " "calling the linuxbrew path directly, which breaks the Bash(tea *) rule", ) process.run( [tea, "comment", "--login", LOGIN, "--repo", REPO, number, body], cwd=config.repo_root(), fix=f"check that PR/issue {number} exists and the login is authorised", ) def watchlist_diff(base: str, head: str) -> list[str]: """Files changed in the range that would make the committed systems.db stale.""" from tooling import generator_sources root = config.repo_root() registered = sorted( { str(path.relative_to(root)) for paths in generator_sources.GENERATOR_SOURCES.values() for path in paths } ) watched = [*registered, *EXTRA_WATCHED] if not watched: # The original checked this too: an empty registry would diff nothing # and report a clean watchlist, which is the answer that lets a stale DB # through. raise ReachError( "the generator-source registry is empty", fix="tooling/generator_sources.py should list every generator input", ) result = process.run( ["git", "diff", "--name-only", f"{base}...{head}", "--", *watched], cwd=config.repo_root(), check=False, ) return [line for line in (result.stdout or "").splitlines() if line] def _resolve_body(body_or_path: str) -> str: if not body_or_path.startswith("@"): return body_or_path path = Path(body_or_path[1:]) if not path.is_file(): raise ReachError( f"comment body file not found: {path}", fix="write the comment to a file first, then pass @that-path", ) return path.read_text(encoding="utf-8")