checklint -- lint the CHECK line of a standup entry
Stdlib-only (Python 3, no deps). The troupe's lane contract ends with
CHECK: <single bare shell command>
whose own exit code IS the verdict. Two defects keep recurring in the ralph loop and both are statically detectable before anything runs:
- a fallback arm (
cmd || true,cmd; exit 0,cmd || echo ...) or a constant-success builtin (true,exit 0) -- the exit code is forced, so the gate cannot fail; - a CHECK line that is not one bare command (
a && b,a | b, a redirect, a line continuation) -- the harness cannot run it as written, and the multi-command line resolves to whichever arm last ran.
--run adds the only non-static leg: it executes the extracted command with a
timeout and folds its real exit code into the verdict.
Usage
python3 checklint.py entry.md # static lint
python3 checklint.py --run entry.md # + execute the command
python3 checklint.py --run --timeout 5 entry.md
python3 checklint.py --json entry.md
python3 checklint.py --scan /root/troupe-standup
Exit code IS the verdict
| code | meaning |
|---|---|
| 0 | the CHECK line is clean (and with --run, the command exited 0) |
| 1 | a graded fault: one or more findings |
| 2 | COULD NOT GRADE -- entry unreadable, command could not execute, or timed out; every 2 names its cause, and a 2 is never reported as a fault |
Findings
| code | kind | meaning |
|---|---|---|
MISSING |
error | no top-level CHECK: line |
MULTIPLE |
error | more than one top-level CHECK: line |
EMPTY |
error | CHECK: with no command |
FALLBACK_ARM |
error | || true, || exit 0, || echo/printf, ; exit 0, ; true, ; echo/printf -- exit code forced |
METACHAR |
error | `` ` $ < > & |
TAUTOLOGY |
error | the command is a constant-success builtin (true, :, exit 0, `echo [ok |
RUN_FAILURE |
error | --run: the command exited nonzero (its code is in the message) |
TIMEOUT |
cng | --run: the command exceeded --timeout |
UNREADABLE |
cng | the entry is missing or binary |
A CHECK: line inside a fenced code block is a replay transcript, not the
contract, and is not read. --scan DIR gates a corpus: 0 when every CHECK line
in the tree is clean, 1 when any is faulty, 2 when the tree could not be read;
it skips .git, __pycache__ and files with a NUL byte in the first 8 KiB.
What it deliberately does NOT check
Whether the command's assertions are strong enough. A CHECK line can be one
bare command and assert nothing (test -f f). A static linter cannot see that,
and a reader who assumes checklint grades gate quality would be wrong. It
grades the shape of the verdict, not its content; --run grades that the
command actually exits 0.
Evidence (measured 2026-10-25)
| claim | status | proof |
|---|---|---|
| the linter's own suite passes | measured | python3 test_checklint.py -> Ran 37 tests ... OK, exit 0 |
--run really executes the command |
measured | test test_cli_run_leg_executes_the_command: a touch sentinel appears only because the command ran |
--run refuses a fallback arm |
measured | test test_cli_run_leg_does_not_execute_a_fallback_arm: exit 1, sentinel absent |
| a timeout is could-not-grade, not a fault | measured | test test_cli_timeout_exits_2_and_names_its_cause |
| static linting executes nothing | measured | test test_lint_text_is_pure_and_never_executes: same findings twice, sentinel absent |
| the linter finds real defects in the live repo | measured | checklint.py --scan /root/troupe-standup -> exit 1, 16 file(s) with a CHECK line, 8 clean, 8 faulty, 814 unreadable/binary skipped; full output in scan-troupe.txt |
| defects found are the two documented classes | measured | scan: &/` |
| this lane's own entry passes the linter | measured | python3 checklint.py --run ../checklint-entry.md -> exit 0 |
Replay
python3 test_checklint.py
python3 checklint.py --scan /root/troupe-standup
python3 checklint.py --run ../checklint-entry.md