Files
settled-reach/tooling/domains/check/service.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

63 lines
2.5 KiB
Python

"""Logic for the `check` domain. Transport-agnostic (D-263).
Nothing here prints, calls `sys.exit`, or imports typer. A service must not know
it was called from a CLI — that is what lets a test call it directly, lets one
domain's service call another's, and leaves a second front end possible without
a rewrite.
"""
from __future__ import annotations
import re
from pathlib import Path
from tooling.core import config
from tooling.domains.check.schemas import VersionCheck
# Anchored to line start so the commentary above `version:` (which mentions
# earlier versions by number) can never be mistaken for the field itself.
_YAML_VERSION = re.compile(r"^version:\s*(\S+)\s*$", re.MULTILINE)
_GODOT_VERSION = re.compile(r'^config/version\s*=\s*"([^"]*)"\s*$', re.MULTILINE)
def client_version() -> VersionCheck:
"""Compare the version in project.yaml with the one baked into the client.
project.yaml is the version source of truth (CLAUDE.md). The client cannot
read it at runtime — an exported build has no repo root — so the value is
mirrored into `application/config/version` in client/project.godot, which
Godot bakes into the PCK (T-1241).
A mirror nobody checks is worse than the bug it replaced: the old code
failed LOUDLY in an export ("?.?.?" everywhere), whereas a stale mirror
fails SILENTLY — the Atlas disk cache keeps serving canvases under a version
that stopped matching the build. That is the T-1239 failure, which cost
eight days of a map drawn from a canvas whose generating code no longer
existed.
"""
root = config.repo_root()
yaml_version, problem = _read(root / "project.yaml", _YAML_VERSION, "`version:` line")
if problem:
return VersionCheck(ok=False, problem=problem)
godot_path = root / "client" / "project.godot"
godot_version, problem = _read(godot_path, _GODOT_VERSION, "`config/version=` line")
if problem:
return VersionCheck(ok=False, yaml_version=yaml_version, problem=problem)
return VersionCheck(
ok=yaml_version == godot_version,
yaml_version=yaml_version,
godot_version=godot_version,
)
def _read(path: Path, pattern: re.Pattern[str], label: str) -> tuple[str | None, str | None]:
"""Return (value, problem). Exactly one of the two is ever set."""
if not path.exists():
return None, f"{path} not found"
match = pattern.search(path.read_text(encoding="utf-8"))
if not match:
return None, f"no {label} in {path}"
return match.group(1), None