Files
jpmschweitzerandClaude Opus 5 338644b409 refactor(tooling): T-1286 — generate, pr and dev become reach domains
Twelve scripts retired, three domains registered. `reach` now covers nine.

generate: `generate-brands` and `generate-corporations` were the second and
third copies of the same 24-line build-if-missing-then-exec bash `tooling/atlas`
carried, so they collapsed into `core.process.cargo_binary` rather than being
ported. `import_economics` shelled out to the first of those, so it now calls
that helper — `generated_brands.toml` comes back byte-identical, and the stamp
registry swaps the retired wrapper for `core/process.py`.

pr: `watchlist-diff` derives its watched set from `generator_sources.py` instead
of restating it, so it cannot drift from the stamp check.

dev: the environment scripts split decision from performing, per D-263's
guarded-exec rule. `godot_plan()` and `worktree_plan()` decide what would
happen; `install_godot()`, `install_rust()` and `setup_worktree()` do it.
`tooling/test_environment.py` pins the version pin, both override precedences,
the already-current skip, the platform refusal and both worktree refusals —
none of them performed. `make setup` now installs reach first, since the
targets that install rust and godot are reach verbs.

Two live bugs found while porting:

- The clerk read its decision index from `decisions/README.md`, a path that
  stopped existing when the DQR tree moved to `governance/`. Every clerk agent
  has been grepping blind; its prompt pointed at the same dead directory.
- The conformance exec-check matched any `x.system()` regardless of receiver,
  so `platform.system()` read as `os.system()`. Narrowed and re-proved against
  a real mutant.

`process.run` gains `input=`, `timeout=` and a `ProcessTimeout` subclass so a
killed run stays distinguishable from a verdict. The pre-push hook no longer
merges the clerk's stderr into its stdout — under streaming the last merged
line is a JSONL event, which would read as an unrecognised verdict and block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 17:00:46 +02:00

64 lines
1.9 KiB
Python

"""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)