Files
settled-reach/tooling/domains/dev/router.py
T
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

122 lines
3.9 KiB
Python

"""Transport for the `dev` domain — args in, delegate, format out."""
from __future__ import annotations
import typer
from tooling.core import cli, console
from tooling.core.command import command
from tooling.core.errors import ReachError
from tooling.domains.dev import environment, service
app = cli.domain("dev", "Developer environment and self-diagnosis.")
@app.callback()
def _domain() -> None:
"""Keeps `dev` a group (Typer collapses a single-command app)."""
@app.command("install-godot")
@command
def install_godot(
version: str = typer.Option(None, "--version", help="Godot version to install."),
plan_only: bool = typer.Option(
False, "--plan", help="Report what would be downloaded without doing it."
),
) -> None:
"""Install the pinned Godot build to ~/bin/godot4, if it is not already there."""
if plan_only:
plan = environment.godot_plan(version)
console.out(f"wanted {plan.wanted}")
console.out(f"installed {plan.installed or '(none)'}")
console.out(f"platform {plan.platform_tag}")
console.out(f"url {plan.url}")
console.verdict(
"already current — nothing to do"
if plan.already_current
else "would download and install"
)
return
environment.install_godot(version)
@app.command("install-rust")
@command
def install_rust() -> None:
"""Ensure clippy and rustfmt are present on the Rust toolchain."""
environment.install_rust()
@app.command("worktree")
@command
def worktree(
branch: str = typer.Argument(..., help="Branch name for the new worktree."),
start: str = typer.Argument("HEAD", help="Start point."),
plan_only: bool = typer.Option(
False, "--plan", help="Report where it would go without creating it."
),
) -> None:
"""Create a worktree under .worktrees/ with the venv linked in."""
if plan_only:
console.verdict(f"would create {environment.worktree_plan(branch)}")
return
environment.setup_worktree(branch, start)
@app.command("perf")
@command
def perf(
compare: bool = typer.Option(
False, "--compare", help="Compare against the saved baseline instead of writing one."
),
) -> None:
"""Benchmark the server and write or check tests/perf/baseline.json."""
from tooling.domains.dev import perf as perf_module
perf_module.run(compare)
@app.command("clerk")
@command
def clerk(
plan_only: bool = typer.Option(
False, "--plan", help="Show the per-commit plan without spawning any clerk."
),
) -> None:
"""Review the push range for decision contradictions, one agent per commit."""
from tooling.domains.dev import clerk as clerk_module
clerk_module.run(plan_only)
@app.command("selftest")
@command
def selftest(
seconds: float = typer.Option(2.0, "--seconds", "-s", help="Roughly how long to run."),
fail: bool = typer.Option(False, "--fail", help="Exit non-zero at the end."),
exit_code: int = typer.Option(1, "--exit-code", help="Which code to fail with."),
) -> None:
"""Emit progress for a while, then succeed or fail on purpose.
A real diagnostic — "does streaming work end to end on this machine, can I
tail it, does a failure survive a detached run?" — and the only command in
reach slow enough to answer those by observation rather than argument.
Every other verb finishes in milliseconds.
"""
for step, total in service.slow_work(seconds):
console.event(
f"step {step} of {total}",
phase="selftest",
progress=step / total,
)
if fail:
raise ReachError(
f"selftest failed on purpose after {seconds:g}s",
fix="this command failed because --fail was passed; drop it to succeed",
exit_code=exit_code,
)
console.verdict(f"selftest: OK — {seconds:g}s of progress, no failures")