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>
122 lines
3.9 KiB
Python
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")
|