reach validate content / checklist / ron / name-collisions. The three old scripts are retired, their make targets with them. Print statements go through the logging sink rather than a collector. The validators emit their findings as console events as they run, so a long content validation streams instead of going quiet and dumping at the end — the message strings and their order are unchanged, only the destination. That also satisfies the conformance rule forbidding print() in the package, which is what forced the question. validate-ron was three languages deep: bash dispatching on a flag, a Python heredoc doing collision detection, cargo run for schema validation. Logic embedded in a shell string cannot be imported, tested, or found by anything that indexes Python, so it became Python; the cargo call became a guarded exec. It also split into two verbs, because --check-name-collisions answered a different question from the default path: whether the SET of cultures is coherent, versus whether ONE file is well-formed. The move broke something, quietly, which is the point of doing these one at a time. validate-checklist computed ROOT as Path(__file__).parent.parent — the repo root while it lived at tooling/validate-checklist, and tooling/domains once moved. Both its schema and gauntlet paths silently repointed at nothing, the gauntlet directory "did not exist", and it reported success having checked zero files. Caught by running it beside the original: old exit 1, new exit 0. Now config.repo_root(), and load_schema raises ReachError instead of calling sys.exit, which a service must not do. Parity on the live tree: content reproduces the original byte for byte including its counts, name-collisions likewise. Tests pin what those runs cannot reach — the detection path, since the repo currently has no collisions, and the argument errors. Two things found and left alone: validate-content FAILS on the live tree with 13 missing schemas, pre-existing and unrelated to this port; and the ticket's claim that validate-content sits in the pre-commit hook is wrong — that hook runs only check-fact-ids and pql decisions validate, so there was no shared edit to coordinate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
"""Transport for the `validate` domain — args in, delegate, format out.
|
|
|
|
Zero logic. Note what these commands do NOT do: collect output. The validators
|
|
emit their findings as events through `core/console` as they run, so a long
|
|
content validation streams rather than going quiet and dumping at the end. The
|
|
router's job is the verdict and the exit code.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
|
|
from tooling.core import cli, console
|
|
from tooling.core.command import command
|
|
from tooling.core.errors import ReachError
|
|
from tooling.domains.validate import checklist as checklist_module
|
|
from tooling.domains.validate import content as content_module
|
|
from tooling.domains.validate import ron as ron_module
|
|
|
|
app = cli.domain("validate", "Content, checklists and RON against their schemas.")
|
|
|
|
|
|
@app.callback()
|
|
def _domain() -> None:
|
|
"""Keeps `validate` a group (Typer collapses a single-command app)."""
|
|
|
|
|
|
@app.command("content")
|
|
@command
|
|
def content() -> None:
|
|
"""Validate content YAML against JSON schemas, then cross-references."""
|
|
code = content_module.validate()
|
|
if code != 0:
|
|
raise ReachError(
|
|
"validate-content: validation failed",
|
|
fix="the errors above name each file and what is wrong with it; "
|
|
"schemas live in server/content/_schema/",
|
|
exit_code=code,
|
|
)
|
|
console.verdict("validate-content: OK")
|
|
|
|
|
|
@app.command("checklist")
|
|
@command
|
|
def checklist(
|
|
check: bool = typer.Option(
|
|
False, "--check", help="Schema validation only, for the pre-PR chain."
|
|
),
|
|
) -> None:
|
|
"""Validate checklist YAML against its schema, and ids for uniqueness."""
|
|
code = checklist_module.validate(check_only=check)
|
|
if code != 0:
|
|
raise ReachError(
|
|
"validate-checklist: validation failed",
|
|
fix="the errors above name each checklist and the field at fault",
|
|
exit_code=code,
|
|
)
|
|
console.verdict("validate-checklist: OK")
|
|
|
|
|
|
@app.command("ron")
|
|
@command
|
|
def ron(
|
|
path: Path = typer.Argument(..., help="The .ron file to validate."),
|
|
schema: str = typer.Argument(..., help=f"One of: {', '.join(ron_module.SCHEMAS)}"),
|
|
) -> None:
|
|
"""Validate a RON file against its Rust struct schema."""
|
|
code = ron_module.ron_file(path, schema)
|
|
if code != 0:
|
|
raise ReachError(
|
|
f"validate-ron: {path} does not match the {schema} schema",
|
|
fix="the validator's output above names the field; the struct is in "
|
|
"server/src/ — compare field names and types",
|
|
exit_code=code,
|
|
)
|
|
console.verdict(f"validate-ron: OK — {path} matches {schema}")
|
|
|
|
|
|
@app.command("name-collisions")
|
|
@command
|
|
def name_collisions(
|
|
directory: Path = typer.Argument(..., help="Directory holding culture-*.ron files."),
|
|
) -> None:
|
|
"""Report names shared between two cultures' name pools.
|
|
|
|
A separate verb rather than a flag on `ron`, because it answers a different
|
|
question: `ron` asks whether ONE file is well-formed, this asks whether the
|
|
SET of them is coherent. The old script fused them behind
|
|
--check-name-collisions and had to branch on it before doing anything.
|
|
"""
|
|
code = ron_module.name_collisions(directory)
|
|
if code != 0:
|
|
raise ReachError(
|
|
"validate-ron: name pools collide across cultures",
|
|
fix="rename the colliding entries so each name belongs to one culture — "
|
|
"a shared name makes a generated NPC's origin ambiguous",
|
|
exit_code=code,
|
|
)
|