Files
settled-reach/tooling/domains/check/router.py
T
jpmschweitzerandClaude Opus 5 b9d81ac694 feat(config): T-1260 — reach lists its domains without importing them
`reach --help` renders from a declaration table and imports nothing. The cost
of help is now flat as the registry grows, which is the property that has to
hold going from one domain to a dozen.

The trap is real and was confirmed in typer's vendored source rather than
assumed from upstream Click: TyperGroup.format_commands loops over
list_commands calling get_command on each, purely to read a short help string
off the loaded command. With lazy loading underneath, that imports every
domain in the registry to render --help — while the output looks entirely
correct. Nothing observable changes; only the import graph does.

So the test asserts on sys.modules, and it was proven to fail before being
trusted. Disabling the format_commands override made it fail and name the
cause, listing all five leaked check modules. It also carries a positive
control — invoking a domain must import its service — because without one,
"nothing was imported" would pass equally for a loader that is simply broken,
and it fails on an empty registry, which would otherwise satisfy everything
vacuously.

The check domain is created here because the test needs a subject: a stub
raising NotImplementedError would have been committed dead code. That takes
the port out of T-1262, which is rescoped to what it still owns — pydantic
schemas, byte-for-byte output parity on the drift path, and the failure
tests. The old tooling/check-client-version script stays in place and stays
wired to the pre-push hook; the deprecation window is deliberate.

One Typer behaviour worth knowing before every future domain: a single-command
app collapses into a bare command, so `reach check client-version` failed with
"unexpected extra argument" until the router got a callback. Same mechanism as
the root callback, different symptom.

Help now works at every level, closing item 5 of T-1248.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 07:31:15 +02:00

71 lines
2.5 KiB
Python

"""Transport for the `check` domain — args in, delegate, format out.
**Zero logic lives here.** Every command in this file should read as: parse,
call a service, turn the result into output and an exit code. If a command
grows a branch that is about the *problem* rather than about *presentation*,
that branch belongs in `service.py`.
The service import is deliberately at module level: by the time this module is
imported at all, `reach` has already decided to run a `check` command, so there
is nothing left to defer. Laziness lives one level up, in `main.py`.
"""
from __future__ import annotations
import typer
from tooling.core import console
from tooling.domains.check import service
app = typer.Typer(
name="check",
help="Consistency gates — the checks the push hook runs.",
no_args_is_help=True,
add_completion=False,
rich_markup_mode=None,
)
@app.callback()
def _domain() -> None:
"""Keeps `check` a group.
Typer collapses a single-command app into a bare command, so without this
`reach check client-version` fails with "unexpected extra argument". Every
domain router needs this until it has two or more verbs — and keeping it
afterwards costs nothing and stops the shape changing under you.
"""
@app.command("client-version")
def client_version() -> None:
"""Fail if the client's baked version has drifted from project.yaml."""
result = service.client_version()
if result.problem:
console.verdict(
f"check-client-version: {result.problem}",
ok=False,
fix="check that project.yaml and client/project.godot exist and are readable",
)
raise typer.Exit(1)
if not result.ok:
console.verdict(
"check-client-version: version drift\n"
f" project.yaml {result.yaml_version}\n"
f" client/project.godot {result.godot_version}\n"
"\n"
"This matters beyond cosmetics: the Atlas disk cache keys its\n"
"invalidation on this version, so a stale mirror makes a shipped\n"
"build serve canvases generated by code it no longer runs (T-1239).",
ok=False,
fix=(
"set config/version in client/project.godot's [application] "
f"section to {result.yaml_version} — project.yaml is the source of truth"
),
)
raise typer.Exit(1)
console.verdict(f"check-client-version: OK — {result.yaml_version}")