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>
64 lines
1.9 KiB
Python
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)
|