"""Transport for the `pr` domain — args in, delegate, format out.""" from __future__ import annotations from pathlib import Path import typer from tooling.core import cli, console from tooling.core.command import command from tooling.domains.pr import service app = cli.domain("pr", "The review loop — comments and the stale-DB watchlist.") @app.callback() def _domain() -> None: """Keeps `pr` a group (Typer collapses a single-command app).""" @app.command("comment") @command def comment( number: str = typer.Argument(..., help="PR or issue number."), body: str = typer.Argument(..., help="The comment, or @path to read it from a file."), ) -> None: """Post a comment on a Gitea PR or issue. Use `@path` for anything long. That form exists because an inline body would need a `$(...)` subshell, and a subshell breaks the permission gate's prefix matching (.claude/rules/tea-cli.md). """ service.comment(number, body) console.verdict(f"commented on #{number}") @app.command("watchlist-diff") @command def watchlist_diff( base: str = typer.Argument(..., help="Base ref."), head: str = typer.Argument("HEAD", help="Head ref."), ) -> None: """List changed files in a range that could make the committed systems.db stale. The watched set is read from tooling/generator_sources.py rather than restated here, so it cannot drift from the registry the stamp check uses. """ changed = service.watchlist_diff(base, head) for path in changed: console.out(path) if not changed: console.verdict(f"pr-watchlist-diff: nothing generator-relevant in {base}...{head}") @app.command("board-html") @command def board_html( output: Path = typer.Option(None, "--output", help="Where to write the board HTML."), ) -> None: """Render the pql ticket board as a standalone HTML page.""" from tooling.domains.pr import board board.run(output)