CB-WP-0004 T01: fix environment friction at the root

CB-RES-0003 measured 84 turns and $15.33 — the largest mechanical
category — spent prefixing commands with `cd` and
`export PATH="$HOME/.cargo/bin:$PATH"`. Both causes are now fixed once
instead of per-leaf.

tools/repo.py resolves the repo root from __file__ and cargo from PATH
then the standard rustup locations. Every tool imports ROOT from it, so
the repo path is stated once rather than redefined in four files —
single source of fact, the rule DFD earned in InnerLoop v1.2.
rule-coverage and dep-weight now call enter_root(), which is why their
relative paths did not need rewriting one by one.

The Makefile derives REPO from MAKEFILE_LIST and resolves CARGO the same
way, so `make -C <repo> <target>` works from any directory with no
prefix.

make env-test is the positive control, and is in `make all`: every tool
runs from / with PATH=/usr/bin:/bin. Without it this fix could regress
silently and invalidate T05's measurement — the whole point of the
control loop.

dep-weight's "cargo not on PATH" error is kept rather than deleted. It
should now be unreachable, and --self-test asserts cargo_bin() resolves
unaided; a control that never fires is cheaper than a regression.

loop-lint failed on repo.py on its first run — a reporting tool with a
positive control but no --self-test entry point. Second time the gate
has caught work from its own pass within the hour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-31 10:13:52 +02:00
parent 578dcbea78
commit 3f1dbac164
9 changed files with 254 additions and 30 deletions

View file

@ -22,10 +22,11 @@ Usage: python3 tools/dep-weight.py [--json] [--self-test]
import glob
import json
import os
import shutil
import subprocess
import sys
from repo import cargo_bin, enter_root
PACKAGE = "games-ground"
CONFIGS = {
"shipped-runtime": ["--no-default-features"],
@ -42,14 +43,19 @@ TARGETS = {
def crates(extra_args):
"""Third-party crates in the normal (non-dev) dependency graph."""
if not shutil.which("cargo"):
# T01: locate cargo rather than demanding the caller export PATH. The
# error below is kept as a positive control — it should now be
# unreachable on a machine with rustup installed, and a control that
# never fires is still cheaper than a regression.
cargo = cargo_bin()
if not cargo:
print(
"ERROR: cargo not on PATH. Try: export PATH=\"$HOME/.cargo/bin:$PATH\"",
"ERROR: cargo not found on PATH or in ~/.cargo/bin — is rustup installed?",
file=sys.stderr,
)
sys.exit(1)
out = subprocess.run(
["cargo", "tree", "-p", PACKAGE, "--edges", "normal", "--prefix", "none"]
[cargo, "tree", "-p", PACKAGE, "--edges", "normal", "--prefix", "none"]
+ extra_args,
capture_output=True,
text=True,
@ -114,6 +120,11 @@ def self_test():
# Targets must be present and numeric — a missing target would make
# the breach check vacuous.
# T01: this tool is useless without cargo, and used to demand the caller
# put it on PATH. Assert it resolves unaided.
check("cargo resolves without caller PATH setup", bool(cargo_bin()),
cargo_bin() or "NOT FOUND")
check("targets defined for every configuration",
set(TARGETS) == set(CONFIGS) and all(
isinstance(v, int) and v > 0 for v in TARGETS.values()),
@ -129,6 +140,8 @@ def self_test():
def main():
# T01: `cargo tree` and the own-source walk are both repo-relative.
enter_root()
if "--self-test" in sys.argv:
return self_test()