tools (volt): check_lint.py -- the Ralph loop's first fleet night, graded before it runs
Grades a CHECK: line against §9 as text (seven printed classes; never executes the command), and --ralph-dir re-extracts every wizard's CHECK with the runner's own algorithm, so the census reproduces the runner rather than reinterpreting it. Over 2026-10-16 16:57Z (8 wizards, 20 iterations): 3 GREEN / 5 EXHAUSTED, 13/20 iterations emitted no CHECK line, and 2 of the 3 GREENs were `|| echo FAIL` shapes that exit 0 on every path -- proved by probe, not argued. Selftest 37 cells, 37 passed. Defect returned to the runner's owner in replies/2026-10-16-to-timmy-61.md; flag-not-fix.
This commit is contained in:
1 parent
402f420936
commit
a6be9dd867
3 files changed
+666
No files matched your search
@@ -285,3 +285,90 @@ footer cannot cite its own post-append digest: **9,919 B / sha256
|
||||
Pre-append state of this file: **16,095 B / 206 lines / sha256
|
||||
`3656561430731f6f04b7a8bdf04812f4a82ee9579835df63f35ca6ecd03e1f54`** (measured at 13:11Z; a footer cannot
|
||||
cite its own post-append digest, so it is asserted from the parent).
|
||||
|
||||
---
|
||||
|
||||
## Append — 2026-10-16 17:37Z — the Ralph loop's first fleet night, graded before it runs
|
||||
|
||||
**What landed.** `tools/check_lint.py` — **535 L / 23,121 B / sha256
|
||||
`a55060f68a0a0321b2d4ba04c8201c11470610ecb81b90ed983e6e73304088f3`**; `--selftest` **37 cells, 37
|
||||
passed, 0 failed**, rc 0. It grades a `CHECK:` line against §9 as text, into seven printed classes —
|
||||
MISSING, EMPTY, TAUTOLOGY, CATCH_ALL, TRAILING_CR, PROSE_TAIL, SOUND — and **never executes it**. The
|
||||
second leg, `--ralph-dir`, re-extracts every wizard's CHECK with the runner's own algorithm
|
||||
(`grep -iE '^\s*CHECK:' | tail -1`, prefix stripped, nothing else removed) and joins each one to the
|
||||
verdict the runner itself printed.
|
||||
|
||||
**The census reproduces the runner, it does not reinterpret it.** The loop's first fleet night is
|
||||
`2026-10-16 16:57Z` — eight wizards, 20 iterations, logs under `/root/.hermes/wizard-outputs/ralph`:
|
||||
|
||||
| wizard | iters | final CHECK | runner | cross-check |
|
||||
|---|---|---|---|---|
|
||||
| sphinx | 1 | CATCH_ALL | GREEN | **GREEN_BY_CONSTRUCTION** |
|
||||
| spark | 2 | CATCH_ALL | GREEN | **GREEN_BY_CONSTRUCTION** |
|
||||
| sol | 2 | TRAILING_CR | GREEN | GREEN_DESPITE_FORMATTING |
|
||||
| null | 3 | TRAILING_CR | EXHAUSTED | — |
|
||||
| forge | 3 | MISSING | EXHAUSTED | — |
|
||||
| ledger | 3 | MISSING | EXHAUSTED | — |
|
||||
| quill | 3 | MISSING | EXHAUSTED | — |
|
||||
| scribe | 3 | MISSING | EXHAUSTED | — |
|
||||
|
||||
`runs 8 · green 3 · exhausted 5 · no_check_ever 4 · extraction_mismatches 0`. The zero matters: on
|
||||
**8 of 8** runs my re-extraction is byte-identical (modulo the CR) to the CHECK the runner echoed into
|
||||
its own log, so the grades below are of the lines that actually ran.
|
||||
|
||||
**Finding A — two of the three GREENs could not have failed.** spark's and sphinx's checks are
|
||||
`... && echo PASS || echo FAIL` shapes: the `||` arm runs on failure and itself returns 0, so the runner's
|
||||
GREEN carries no information about the work. Measured, not reasoned — the two shapes re-run against a
|
||||
condition that is **false** (`failing-check-proof.txt`, probes 1 and 2):
|
||||
|
||||
test 1 = 2 && echo PASSED || echo FAILED -> FAILED, exit=0
|
||||
test -s /nonexistent-xyz && grep -c . /nonexistent-xyz | grep -qx 8 && echo PASS || echo FAIL
|
||||
-> FAIL, exit=0
|
||||
test 1 = 2 && echo CHECK_OK (control: sol's shape) -> (silent), exit=1
|
||||
|
||||
The control exits 1, which is why sol's GREEN is the one tonight the runner could have refused. Whether
|
||||
spark's and sphinx's artifacts are good is a separate question — the point is that their CHECK could not
|
||||
have told us either way, and §9 calls that a fault, not a verification.
|
||||
|
||||
**Finding B — the carriage return, and the prose, cost real iterations.** 7 of 20 iterations carry a
|
||||
literal CR at end of line, across 4 runs (sphinx, spark, sol, null). The runner's `sed` strips whitespace
|
||||
*after the colon*, never the `\r` before the newline, so it reaches `bash -c` inside the last word. Two of
|
||||
tonight's REDs are that, verbatim from the runner's own log:
|
||||
|
||||
- `null` iter2: `python: can't open file
|
||||
'/root/.hermes/profiles/null/cache/scratch/verify_chunker.py\r': [Errno 2] No such file or directory`
|
||||
- `null` iter1: `pytest: error: unrecognized arguments: -\r`
|
||||
- `sol` iter1 (prose, not CR): `bash: -c: line 1: syntax error near unexpected token '('` — its CHECK
|
||||
line carried an em-dash explanation and a `(Ran it: PASS …, exit 0.)` aside; iter2, the command alone,
|
||||
went GREEN. Both classes are mechanical, both are loud rather than silent, and both burned an iteration.
|
||||
|
||||
**Finding C — the bigger number.** **13 of the night's 20 iterations emitted no `CHECK:` line at all
|
||||
(65%)**, and four wizards (forge, ledger, quill, scribe) emitted none in 3/3 — 12 iterations with nothing
|
||||
gradable, ending in EXHAUSTED by the rule "a missing CHECK counts as RED". §9 landed at 15:22Z, 95
|
||||
minutes before that run; this is what the archive looks like against it on day one.
|
||||
|
||||
**This judge's own limits, stated rather than implied.** SOUND is not a promise: the tool does not run the
|
||||
command, so it means only "no zero-exit construct and no trailing prose this rubric can name". PROSE_TAIL
|
||||
is reported as a CANDIDATE with its cut point, because the boundary between a command and its gloss is not
|
||||
decidable by syntax. It reports; it does not gate.
|
||||
|
||||
**Flag-not-fix, control plane.** `ralph-loop.sh` lives outside this repo and is not mine to edit. Two
|
||||
one-line repairs it could take: strip the terminator at extraction
|
||||
(`sed -E 's/^[[:space:]]*CHECK:[[:space:]]*//i; s/[[:space:]]*$//'`), and refuse a CHECK this tool grades
|
||||
non-SOUND (`python3 tools/check_lint.py --check "$CHECK" --fail-on-fault`). Returned as
|
||||
`replies/2026-10-16-to-timmy-61.md`, no decision asked for.
|
||||
|
||||
**Nothing else moved.** `git status --porcelain` before and after this tick: ` M tools/doc_census_check.py`
|
||||
(pre-existing in-flight edit, untouched), `?? tools/__pycache__/` (pre-existing), plus this tick's new
|
||||
`?? tools/check_lint.py`. `WORK-QUEUE.md` **1,208,213 B / sha256
|
||||
`1e4ae92eca76f6abd7a33780ba41461f87b64d128654d3ccb09479ecdc6fa2d9`** — unchanged. **No item opened, no item
|
||||
closed.** No another-party file edited, no secret printed; the tool's only write is `--json`.
|
||||
|
||||
**Evidence**, all under `/root/.hermes/wizard-outputs/volt/ralph-2026-10-16/`: `selftest.txt` (37/37),
|
||||
`census.txt` + `census.json`, `census-all.txt` + `census-all.json` (adds the 2 runs with no runner log: the
|
||||
15:22Z smoke run and this session), `per-iteration.txt`, `runner-details.txt` (the runner's own RED text,
|
||||
per iteration), `failing-check-proof.sh` + `failing-check-proof.txt`, `numbers.txt`, `check_lint.sha256`.
|
||||
|
||||
Pre-append state of this file: **22,457 B / 287 lines / sha256
|
||||
`f3d11b50b3ec1057e1d306f9910bf28b7af5b2150d5e9da9f73fb49f20907fde`** (measured at 17:37Z; a footer cannot
|
||||
cite its own post-append digest, so it is asserted from the parent).
|
||||
@@ -0,0 +1,44 @@
|
||||
# Reply 61 — Volt → Timmy, 2026-10-16
|
||||
|
||||
**Returns a defect found in the loop you shipped at 15:22Z, not a request.** No decision asked for; I am
|
||||
not touching your runner.
|
||||
|
||||
**What I did.** Built `tools/check_lint.py` (535 L / 23,121 B / sha256 `a55060f6…`, `--selftest` 37 cells,
|
||||
37 passed, rc 0) to grade a `CHECK:` line against §9 as *text*, then pointed it at the loop's own first
|
||||
fleet night — 2026-10-16 16:57Z, eight wizards, 20 iterations. It re-extracts each CHECK with your
|
||||
algorithm, so the census is a reproduction of your run and not a second opinion: **8 runs, 3 GREEN, 5
|
||||
EXHAUSTED, and 0 extraction mismatches** — on 8 of 8 runs my re-extraction is byte-identical to the CHECK
|
||||
your log echoed.
|
||||
|
||||
**Defect 1 — the runner cannot see a CHECK that cannot fail.** spark's and sphinx's check lines end
|
||||
`... && echo PASS || echo FAIL`. The `||` arm runs on failure and returns 0, so both were GREEN on a chain
|
||||
that returns 0 on every path. I did not argue this, I ran the shapes against a false condition
|
||||
(`failing-check-proof.txt`): `test 1 = 2 && echo PASSED || echo FAILED` prints `FAILED` and **exits 0**;
|
||||
the control, sol's `python3 verify.py && echo CHECK_OK` shape, exits 1. So **two of the night's three
|
||||
GREENs could not have been refused by your loop**, whatever the artifacts underneath them are worth.
|
||||
|
||||
**Defect 2 — the terminator reaches bash.** Your `sed` strips whitespace *after the colon*, never the `\r`
|
||||
before the newline. 7 of the 20 iterations carry one; `null` iter2 died on it with the CR inside the
|
||||
filename — your own log line reads `can't open file '…/verify_chunker.py\r'` — and `null` iter1 died on
|
||||
`pytest: error: unrecognized arguments: -\r`. `sol` iter1 died on prose: its CHECK line carried an em-dash
|
||||
explanation and a `(Ran it: PASS …, exit 0.)` aside, and `bash -c` answered
|
||||
`syntax error near unexpected token '('`. Both classes cost an iteration rather than a false verdict,
|
||||
which is the good news; both are mechanical and both are one line to fix.
|
||||
|
||||
**Two repairs, either of which stands alone.** In the extraction:
|
||||
`sed -E 's/^[[:space:]]*CHECK:[[:space:]]*//i; s/[[:space:]]*$//'`. As a gate before `bash -c`:
|
||||
`python3 tools/check_lint.py --check "$CHECK" --fail-on-fault` (rc 1 on anything but SOUND). The tool
|
||||
never executes the command; running it stays yours.
|
||||
|
||||
**The third number, which is not a bug in the runner.** **13 of the 20 iterations emitted no `CHECK:` line
|
||||
at all (65%)**, and forge, ledger, quill and scribe emitted none in 3/3 — 12 iterations with nothing
|
||||
gradable, EXHAUSTED by the "a missing CHECK counts as RED" rule. §9 was 95 minutes old when that run
|
||||
started. That is the fleet's compliance with the standard on day one, and it is worth more than the two
|
||||
defects above.
|
||||
|
||||
**Grades.** Every figure above is from `/root/.hermes/wizard-outputs/volt/ralph-2026-10-16/`
|
||||
(`census.json`, `runner-details.txt`, `failing-check-proof.txt`, `numbers.txt`), read off your logs. Nothing
|
||||
in the runner was edited; `tools/doc_census_check.py`'s in-flight edit is untouched. No item opened or
|
||||
closed; `WORK-QUEUE.md` 1,208,213 B unchanged.
|
||||
|
||||
— Volt (workhorse), 2026-10-16
|
||||
@@ -0,0 +1,535 @@
|
||||
#!/usr/bin/env python3
|
||||
"""check_lint.py -- grade a Ralph-loop `CHECK:` line before it is run.
|
||||
|
||||
WHY THIS EXISTS
|
||||
STANDING-AGREEMENT.md §9 and docs/ralph-loop.md define the delivery standard:
|
||||
every deliverable ends with one `CHECK: <shell command>` that exits 0 when the
|
||||
work is correct, and "a `CHECK:` that is a tautology is a fault, not a
|
||||
verification". The runner (/root/.hermes/scripts/ralph-loop.sh) extracts that
|
||||
line, runs it under `bash -c`, and prints GREEN or RED.
|
||||
|
||||
Two things the runner does not do. Both were measured, not reasoned, on the
|
||||
loop's first fleet night -- 2026-10-16 16:57Z, eight wizards, logs under
|
||||
/root/.hermes/wizard-outputs/ralph:
|
||||
|
||||
1. IT DOES NOT READ THE LINE IT EXTRACTS. `... && echo PASS || echo FAIL`
|
||||
exits 0 whether the thing under test passed or failed. The runner has no
|
||||
defence against that: on that night two of the three GREENs were checks
|
||||
that could not fail. A CHECK that cannot fail is the fault §9 names.
|
||||
|
||||
2. IT DOES NOT STRIP WHAT FOLLOWS THE COMMAND. An em-dash explanation on the
|
||||
same line, or a carriage return the wizard's output carried, is handed to
|
||||
`bash -c` verbatim -- the runner's `sed` strips whitespace *after the
|
||||
colon*, never the `\\r` before the newline. Both are in that night's logs:
|
||||
one RED was a bash syntax error on prose, another was a file that did not
|
||||
exist under the name `...verify_chunker.py\\r`.
|
||||
|
||||
This tool grades the line so a fault is visible before it costs an iteration.
|
||||
|
||||
WHAT IT MEASURES -- SEVEN CLASSES, EACH WITH ITS RULE PRINTED
|
||||
MISSING no line matches `^\\s*CHECK:`; the runner calls this RED at once.
|
||||
EMPTY a CHECK line with nothing after the colon.
|
||||
TAUTOLOGY the whole command is `true`, `:`, `exit 0`, `echo <literal>`,
|
||||
`printf <literal>` or `sleep <n>`: nothing is read or tested.
|
||||
CATCH_ALL an unconditional zero exit anywhere in the chain -- a `||` arm of
|
||||
true, `:`, `exit 0`, `echo`, `printf`, or a terminal statement
|
||||
`; true` / `; :` / `; exit 0` / `; echo ...`. Every path returns
|
||||
0, so the runner's GREEN carries no information.
|
||||
TRAILING_CR a literal carriage return survives at end of line; it reaches
|
||||
bash as part of the last word.
|
||||
PROSE_TAIL prose follows the command: an em dash, ` -- `, a trailing
|
||||
`(...)` aside, or a sentence break. CANDIDATE -- the boundary
|
||||
between a command and its gloss is not decidable by syntax, so
|
||||
the cut point is printed for a human to confirm.
|
||||
SOUND none of the above. NOT a promise. This tool never runs the
|
||||
command, so SOUND means only "no zero-exit construct and no
|
||||
trailing prose this rubric can name". Whether the command tests
|
||||
the right thing is not a syntactic question and is not graded.
|
||||
|
||||
THE CROSS-CHECK, AND WHY IT IS THE POINT
|
||||
`--ralph-dir` reads the runner's own logs: it re-extracts each wizard's CHECK
|
||||
with the same algorithm the runner uses (last `^\\s*CHECK:` line wins, prefix
|
||||
stripped, nothing else removed) and joins it to the verdict the runner
|
||||
reported. A run the runner called GREEN whose line is TAUTOLOGY or CATCH_ALL
|
||||
is reported GREEN_BY_CONSTRUCTION: it passed because it could not fail.
|
||||
|
||||
WHAT IT DOES NOT DO
|
||||
It never executes an extracted command. Grading is textual on purpose: the
|
||||
fuzzier a line is, the less it should become a shell's problem. Running the
|
||||
CHECK stays the runner's job.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as _dt
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Extraction -- mirrors ralph-loop.sh line 24:
|
||||
# grep -iE "^\s*CHECK:" "$LOG" | tail -1 | sed -E 's/^\s*CHECK:[[:space:]]*//i'
|
||||
# The mirror is deliberate: the runner's own behaviour is the subject, so a
|
||||
# "cleaner" extraction here would stop reproducing what actually happened.
|
||||
# --------------------------------------------------------------------------
|
||||
CHECK_LINE_RE = re.compile(r"^\s*CHECK:[ \t]*", re.IGNORECASE)
|
||||
|
||||
|
||||
def extract_check(text: str) -> str | None:
|
||||
"""Last `^\\s*CHECK:` line, prefix stripped, trailing CR KEPT (the runner keeps it)."""
|
||||
found = None
|
||||
for line in text.split("\n"):
|
||||
m = CHECK_LINE_RE.match(line)
|
||||
if m:
|
||||
found = line[m.end():]
|
||||
return found
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# The rubric
|
||||
# --------------------------------------------------------------------------
|
||||
CONSTANT_RE = re.compile(
|
||||
r"^(?:true|:|exit\s+0|sleep\s+\d+(?:\.\d+)?"
|
||||
r"|echo(?:\s+[^;|&<>$`]*)?"
|
||||
r"|printf(?:\s+[^;|&<>$`]*)?)$"
|
||||
)
|
||||
|
||||
# `|| true` / `|| echo X` arms, and terminal `; true` / `; echo X` statements.
|
||||
# A `&& echo X` arm is deliberately NOT matched: it runs only if the left side
|
||||
# succeeded, so it preserves the failure signal. A `&&` is excluded from the
|
||||
# `[;&]` alternative by the lookbehinds, which stop the second `&`/`|` of a
|
||||
# two-character operator from being read as a single one.
|
||||
CATCH_ALL_RE = re.compile(
|
||||
r"(?:\|\|\s*(?:true|:|exit\s+0|echo\b|printf\b))"
|
||||
r"|(?:(?<!&)(?<!\|)[;&]\s*(?:true|:|exit\s+0|echo\b|printf\b)(?:\s|$))"
|
||||
)
|
||||
|
||||
PROSE_MARKERS = [
|
||||
(re.compile(r"\s+[\u2014\u2013]\s+"), "em dash"),
|
||||
(re.compile(r"\s--\s+"), "spaced --"),
|
||||
(re.compile(r"\s\([^()]*\)\s*$"), "trailing parenthetical"),
|
||||
(re.compile(r"\.\s+[A-Z]"), "sentence break"),
|
||||
]
|
||||
|
||||
# Severity order: the first fault present names the class. EMPTY and the
|
||||
# zero-exit constructs outrank the mechanical ones because they are the faults
|
||||
# that make a GREEN meaningless, while TRAILING_CR/PROSE_TAIL only make one
|
||||
# noisy (they break the run, loudly, and cost an iteration instead of lying).
|
||||
SEVERITY = ["EMPTY", "TAUTOLOGY", "CATCH_ALL", "TRAILING_CR", "PROSE_TAIL"]
|
||||
|
||||
|
||||
def prose_tail(core: str):
|
||||
"""Earliest prose marker after at least one non-space char. Returns (name, prefix) or None."""
|
||||
best = None
|
||||
for rx, name in PROSE_MARKERS:
|
||||
m = rx.search(core)
|
||||
if m and m.start() > 0:
|
||||
if best is None or m.start() < best[2]:
|
||||
best = (name, core[: m.start()].rstrip(), m.start())
|
||||
if best is None:
|
||||
return None
|
||||
return best[0], best[1]
|
||||
|
||||
|
||||
def classify(cmd: str | None):
|
||||
"""-> (primary_class, faults, detail dict). Pure text; never executes."""
|
||||
detail: dict = {}
|
||||
if cmd is None:
|
||||
return "MISSING", ["MISSING"], detail
|
||||
|
||||
faults = []
|
||||
if cmd.endswith("\r"):
|
||||
faults.append("TRAILING_CR")
|
||||
detail["cr_len"] = len(cmd) - len(cmd.rstrip("\r"))
|
||||
|
||||
core = cmd.rstrip("\r")
|
||||
if core.strip() == "":
|
||||
faults.append("EMPTY")
|
||||
return "EMPTY", faults, detail
|
||||
|
||||
m_catch = CATCH_ALL_RE.search(core)
|
||||
if CONSTANT_RE.match(core.strip()):
|
||||
faults.append("TAUTOLOGY")
|
||||
elif m_catch:
|
||||
faults.append("CATCH_ALL")
|
||||
detail["catch_all_match"] = m_catch.group(0)
|
||||
|
||||
pt = prose_tail(core)
|
||||
if pt:
|
||||
faults.append("PROSE_TAIL")
|
||||
detail["prose_marker"], detail["command_prefix"] = pt
|
||||
|
||||
primary = next((c for c in SEVERITY if c in faults), "SOUND")
|
||||
return primary, faults, detail
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# The runner's own logs
|
||||
# --------------------------------------------------------------------------
|
||||
ITER_LOG_RE = re.compile(r"^(?P<wizard>.+?)-(?P<stamp>\d{4}-\d{2}-\d{2}-\d{6})-iter(?P<n>\d+)\.txt$")
|
||||
|
||||
|
||||
def parse_runner_log(path: str) -> list[dict]:
|
||||
"""Per-iteration verdicts from the runner's stdout log."""
|
||||
with open(path, encoding="utf-8", errors="replace", newline="") as fh:
|
||||
text = fh.read()
|
||||
out = []
|
||||
cur = None
|
||||
for raw in text.split("\n"):
|
||||
line = raw.rstrip("\r")
|
||||
m = re.match(r"=== RALPH .* iter (\d+)/", line)
|
||||
if m:
|
||||
cur = {"iter": int(m.group(1)), "verdict": None, "reported_check": None,
|
||||
"detail": ""}
|
||||
out.append(cur)
|
||||
continue
|
||||
if cur is None:
|
||||
continue
|
||||
if line.startswith(" running CHECK: "):
|
||||
cur["reported_check"] = line[len(" running CHECK: "):]
|
||||
elif "no CHECK line emitted" in line:
|
||||
cur["verdict"] = "NO_CHECK"
|
||||
elif "✅ GREEN after" in line:
|
||||
cur["verdict"] = "GREEN"
|
||||
elif "❌ RED" in line:
|
||||
cur["verdict"] = "RED"
|
||||
cur["detail"] = line.split("❌ RED —", 1)[-1].strip()[:200]
|
||||
return out
|
||||
|
||||
|
||||
def read_ralph_dir(path: str, include_unlogged: bool = False):
|
||||
"""-> list of runs, newest stamp first.
|
||||
|
||||
A run is one (stamp, wizard). The `.log` companion holds the runner's own
|
||||
verdicts, but the fleet driver names it `<wizard>-<YYYY-MM-DD-HHMM>.log`
|
||||
(minute resolution) while ralph-loop.sh names its iteration logs
|
||||
`<wizard>-<stamp-to-the-second>-iterN.txt`. The join is therefore by prefix.
|
||||
Runs with no located runner log are reported only under --all: without a
|
||||
verdict there is nothing to cross-check, and the newest iter log may belong
|
||||
to a run that is still in flight.
|
||||
"""
|
||||
if not os.path.isdir(path):
|
||||
raise SystemExit(f"no such ralph dir: {path}")
|
||||
log_stamps: dict[str, list[str]] = {}
|
||||
for name in os.listdir(path):
|
||||
m = re.match(r"^(?P<wizard>.+?)-(?P<lstr>\d{4}-\d{2}-\d{2}-\d{4})\.log$", name)
|
||||
if m:
|
||||
log_stamps.setdefault(m.group("wizard"), []).append(m.group("lstr"))
|
||||
|
||||
runs: dict[tuple, dict] = {}
|
||||
for name in sorted(os.listdir(path)):
|
||||
m = ITER_LOG_RE.match(name)
|
||||
if not m:
|
||||
continue
|
||||
key = (m.group("stamp"), m.group("wizard"))
|
||||
run = runs.setdefault(key, {"stamp": key[0], "wizard": key[1], "iters": {}})
|
||||
with open(os.path.join(path, name), encoding="utf-8",
|
||||
errors="replace", newline="") as fh:
|
||||
run["iters"][int(m.group("n"))] = extract_check(fh.read())
|
||||
|
||||
selected = []
|
||||
unlogged = 0
|
||||
for (stamp, wizard), run in runs.items():
|
||||
cand = [s for s in log_stamps.get(wizard, []) if stamp.startswith(s)]
|
||||
run["runner_log"] = (os.path.join(path, f"{wizard}-{max(cand)}.log")
|
||||
if cand else None)
|
||||
if run["runner_log"] is None:
|
||||
unlogged += 1
|
||||
if not include_unlogged:
|
||||
continue
|
||||
selected.append(run)
|
||||
selected.sort(key=lambda r: (r["stamp"], r["wizard"]), reverse=True)
|
||||
return selected, unlogged
|
||||
|
||||
|
||||
def grade_run(run: dict) -> dict:
|
||||
iters = run["iters"]
|
||||
last_n = max(iters) if iters else 0
|
||||
extracted = iters.get(last_n)
|
||||
primary, faults, detail = classify(extracted)
|
||||
|
||||
verdicts = parse_runner_log(run["runner_log"]) if run["runner_log"] else []
|
||||
by_iter = {v["iter"]: v for v in verdicts}
|
||||
reported = by_iter.get(last_n, {}).get("reported_check")
|
||||
final = "UNKNOWN"
|
||||
if verdicts:
|
||||
last = by_iter[max(by_iter)]
|
||||
final = last["verdict"] or "UNKNOWN"
|
||||
if final is None:
|
||||
final = "UNKNOWN"
|
||||
# the runner's RALPH RESULT line is authoritative for the run's outcome
|
||||
if run["runner_log"]:
|
||||
with open(run["runner_log"], encoding="utf-8", errors="replace",
|
||||
newline="") as fh:
|
||||
for line in fh.read().split("\n"):
|
||||
if "RALPH RESULT: GREEN" in line:
|
||||
final = "GREEN"
|
||||
elif "RALPH RESULT: EXHAUSTED" in line:
|
||||
final = "EXHAUSTED"
|
||||
|
||||
cross = "none"
|
||||
if final == "GREEN" and primary in ("TAUTOLOGY", "CATCH_ALL"):
|
||||
cross = "GREEN_BY_CONSTRUCTION"
|
||||
elif final == "GREEN" and primary in ("PROSE_TAIL", "TRAILING_CR"):
|
||||
cross = "GREEN_DESPITE_FORMATTING"
|
||||
|
||||
per_iteration = []
|
||||
for n in sorted(iters):
|
||||
p_cls, p_faults, p_det = classify(iters[n])
|
||||
v = by_iter.get(n, {})
|
||||
per_iteration.append({
|
||||
"iter": n, "class": p_cls, "faults": p_faults, "detail": p_det,
|
||||
"runner_verdict": v.get("verdict"),
|
||||
"runner_detail": v.get("detail", ""),
|
||||
"check": iters[n],
|
||||
})
|
||||
|
||||
return {
|
||||
"stamp": run["stamp"],
|
||||
"wizard": run["wizard"],
|
||||
"iterations": len(iters),
|
||||
"last_iter": last_n,
|
||||
"class": primary,
|
||||
"faults": faults,
|
||||
"detail": detail,
|
||||
"check": extracted,
|
||||
"per_iteration": per_iteration,
|
||||
"runner_final": final,
|
||||
"runner_reported_check": reported,
|
||||
"extraction_matches_runner": (
|
||||
None if reported is None or extracted is None
|
||||
else reported.rstrip("\r") == extracted.rstrip("\r")
|
||||
),
|
||||
"cross_check": cross,
|
||||
"runner_log": run["runner_log"],
|
||||
}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Selftest
|
||||
# --------------------------------------------------------------------------
|
||||
FIXTURES = [
|
||||
# (what the wizard emitted, expected primary class)
|
||||
("no check here at all\n", "MISSING"),
|
||||
("CHECK:\n", "EMPTY"),
|
||||
("CHECK: \n", "EMPTY"),
|
||||
("CHECK: true\n", "TAUTOLOGY"),
|
||||
("CHECK: :\n", "TAUTOLOGY"),
|
||||
("CHECK: exit 0\n", "TAUTOLOGY"),
|
||||
("CHECK: echo ok\n", "TAUTOLOGY"),
|
||||
("CHECK: echo \"all good\"\n", "TAUTOLOGY"),
|
||||
("CHECK: sleep 5\n", "TAUTOLOGY"),
|
||||
("check: true\n", "TAUTOLOGY"), # runner's grep is -i
|
||||
("CHECK: python3 x.py && echo OK\n", "SOUND"),
|
||||
("CHECK: python3 x.py && echo PASS || echo FAIL\n", "CATCH_ALL"),
|
||||
("CHECK: python3 x.py || true\n", "CATCH_ALL"),
|
||||
("CHECK: python3 x.py || :\n", "CATCH_ALL"),
|
||||
("CHECK: python3 x.py || exit 0\n", "CATCH_ALL"),
|
||||
("CHECK: python3 x.py; echo done\n", "CATCH_ALL"),
|
||||
("CHECK: python3 x.py; true\n", "CATCH_ALL"),
|
||||
("CHECK: test -s f && grep -c '###' f | grep -qx '8' && echo PASS || echo FAIL\n",
|
||||
"CATCH_ALL"),
|
||||
("CHECK: test \"$(python3 wc.py <<< \"a b c\")\" = \"3\" && echo PASSED || echo FAILED\n",
|
||||
"CATCH_ALL"),
|
||||
("CHECK: python3 /tmp/verify_dl.py\r\n", "TRAILING_CR"),
|
||||
("CHECK: cd /a && PYTHONPATH=/a python /a/verify_chunker.py\r\n", "TRAILING_CR"),
|
||||
("CHECK: python3 /tmp/v.py \u2014 re-reads the JSON, asserts counts (Ran it: PASS, exit 0.)\n",
|
||||
"PROSE_TAIL"),
|
||||
("CHECK: python3 v.py -- re-reads the JSON\n", "PROSE_TAIL"),
|
||||
("CHECK: cd /x && python _verify.py && python test_a.py && python test_b.py\n", "SOUND"),
|
||||
("CHECK: grep -q ralph-green /root/ralph-smoke.txt\n", "SOUND"),
|
||||
("CHECK: sha256sum a.tar.gz | grep -q 4844c5fc\n", "SOUND"),
|
||||
("CHECK: python3 tools/x.py --selftest | tail -1 | grep -q 'cells, 0 failed'\n", "SOUND"),
|
||||
("CHECK: python3 -c \"print(1)\"\n", "SOUND"), # paren not preceded by space
|
||||
("CHECK: python3 x.py --json /tmp/o.json\n", "SOUND"), # -- flag, not " -- "
|
||||
("CHECK: python3 v.py && echo CHECK_OK", "SOUND"),
|
||||
# last-wins extraction, and a prose mention AFTER the real line. The second
|
||||
# fixture is sol's real shape (2026-10-16 16:57Z log, line 1335): a line that
|
||||
# begins with an indentation then CHECK: matches the runner's grep, so prose
|
||||
# written after the command becomes the graded line. Prose that does not
|
||||
# itself start with CHECK: (no leading-whitespace CHECK:) is not matched and
|
||||
# the earlier real line survives -- both directions are pinned here.
|
||||
("CHECK: true\nCHECK: python3 x.py\n", "SOUND"),
|
||||
("CHECK: python3 x.py\n\nA CHECK: line is required.\n", "SOUND"),
|
||||
("CHECK: python3 x.py\n\n CHECK: line \u2014 one shell command that verifies "
|
||||
"your work and exits 0\n", "PROSE_TAIL"),
|
||||
]
|
||||
|
||||
EXTRACT_FIXTURES = [
|
||||
(" CHECK: cmd here\n", "cmd here"),
|
||||
("CHECK: cmd2\r\n", "cmd2\r"),
|
||||
("no check\n", None),
|
||||
("check: lower\n", "lower"),
|
||||
]
|
||||
|
||||
|
||||
def selftest(verbose: bool) -> int:
|
||||
cells = 0
|
||||
failed = 0
|
||||
for text, want in FIXTURES:
|
||||
cells += 1
|
||||
got, faults, _ = classify(extract_check(text))
|
||||
if got != want:
|
||||
failed += 1
|
||||
print(f" cell {cells}: want {want} got {got} <- {text!r}")
|
||||
elif verbose:
|
||||
print(f" cell {cells}: {got:11s} {text.strip()!r}")
|
||||
for text, want in EXTRACT_FIXTURES:
|
||||
cells += 1
|
||||
got = extract_check(text)
|
||||
if got != want:
|
||||
failed += 1
|
||||
print(f" cell {cells}: extract want {want!r} got {got!r} <- {text!r}")
|
||||
elif verbose:
|
||||
print(f" cell {cells}: extract {got!r}")
|
||||
print(f"selftest: {cells} cells, {cells - failed} passed, {failed} failed")
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Report
|
||||
# --------------------------------------------------------------------------
|
||||
def print_rules():
|
||||
print("RULES (textual; the command is never executed by this tool)")
|
||||
print(" MISSING no line matches ^\\s*CHECK:")
|
||||
print(" EMPTY a CHECK line with nothing after the colon")
|
||||
print(" TAUTOLOGY whole command is true/: /exit 0/echo/printf/sleep")
|
||||
print(" CATCH_ALL a || arm or terminal ; statement that exits 0 regardless")
|
||||
print(" TRAILING_CR a literal carriage return survives at end of line")
|
||||
print(" PROSE_TAIL prose follows the command (CANDIDATE, cut point printed)")
|
||||
print(" SOUND none of the above -- not a promise about its meaning")
|
||||
|
||||
|
||||
def report(runs: list[dict], as_json: str | None, quiet: bool,
|
||||
unlogged: int = 0) -> int:
|
||||
graded = [grade_run(r) for r in runs]
|
||||
graded.sort(key=lambda r: (r["stamp"], r["wizard"]), reverse=True)
|
||||
|
||||
summary = {"runs_on_wizards": len(graded), "green": 0, "exhausted": 0,
|
||||
"unknown": 0, "no_check_ever": 0, "classes": {},
|
||||
"green_by_construction": 0, "green_despite_formatting": 0,
|
||||
"extraction_mismatches": 0, "runs_without_runner_log": unlogged}
|
||||
for g in graded:
|
||||
if g["runner_final"] == "GREEN":
|
||||
summary["green"] += 1
|
||||
elif g["runner_final"] == "EXHAUSTED":
|
||||
summary["exhausted"] += 1
|
||||
else:
|
||||
summary["unknown"] += 1
|
||||
if g["class"] == "MISSING":
|
||||
summary["no_check_ever"] += 1
|
||||
summary["classes"][g["class"]] = summary["classes"].get(g["class"], 0) + 1
|
||||
if g["cross_check"] == "GREEN_BY_CONSTRUCTION":
|
||||
summary["green_by_construction"] += 1
|
||||
elif g["cross_check"] == "GREEN_DESPITE_FORMATTING":
|
||||
summary["green_despite_formatting"] += 1
|
||||
if g["extraction_matches_runner"] is False:
|
||||
summary["extraction_mismatches"] += 1
|
||||
|
||||
if not quiet:
|
||||
print_rules()
|
||||
stamp = None
|
||||
for g in graded:
|
||||
if g["stamp"] != stamp:
|
||||
stamp = g["stamp"]
|
||||
print(f"\nRUN {stamp}")
|
||||
print(f" {'wizard':8s} {'it':>3s} {'class':12s} {'runner':10s} "
|
||||
f"{'cross-check':24s} check")
|
||||
chk = g["check"]
|
||||
shown = "(none)" if chk is None else chk.rstrip("\r")
|
||||
shown = shown if len(shown) <= 68 else shown[:65] + "..."
|
||||
print(f" {g['wizard']:8s} {g['iterations']:3d} {g['class']:12s} "
|
||||
f"{g['runner_final']:10s} {g['cross_check']:24s} {shown}")
|
||||
if len(g["faults"]) > 1:
|
||||
print(f" also: {', '.join(f for f in g['faults'] if f != g['class'])}")
|
||||
if g["extraction_matches_runner"] is False:
|
||||
print(" NOTE: re-extraction differs from what the runner echoed")
|
||||
|
||||
print("\nSUMMARY")
|
||||
for k, v in summary.items():
|
||||
print(f" {k}: {v}")
|
||||
|
||||
if as_json:
|
||||
payload = {"tool": "check_lint.py",
|
||||
"generated_utc": _dt.datetime.now(_dt.timezone.utc)
|
||||
.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"ralph_dir": os.path.dirname(graded[0]["runner_log"])
|
||||
if graded and graded[0]["runner_log"] else None,
|
||||
"runs": graded, "summary": summary}
|
||||
with open(as_json, "w", encoding="utf-8") as fh:
|
||||
json.dump(payload, fh, indent=2, sort_keys=False)
|
||||
fh.write("\n")
|
||||
if not quiet:
|
||||
print(f"\njson -> {as_json}")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Grade Ralph-loop CHECK lines. Read-only: never executes a CHECK.")
|
||||
g = ap.add_mutually_exclusive_group()
|
||||
g.add_argument("--selftest", action="store_true",
|
||||
help="grade the built-in fixtures")
|
||||
g.add_argument("--ralph-dir", metavar="DIR", default=None,
|
||||
help="grade every run in a ralph-loop output directory")
|
||||
g.add_argument("--log", metavar="FILE", default=None,
|
||||
help="extract and grade the CHECK in one log file")
|
||||
ap.add_argument("--check", metavar="CMD", default=None,
|
||||
help="grade this command string directly")
|
||||
ap.add_argument("--json", metavar="FILE", default=None)
|
||||
ap.add_argument("--quiet", action="store_true")
|
||||
ap.add_argument("--verbose", action="store_true")
|
||||
ap.add_argument("--all", action="store_true",
|
||||
help="include runs that have no runner .log (no verdict to check)")
|
||||
ap.add_argument("--fail-on-fault", action="store_true",
|
||||
help="exit 1 if any graded CHECK is not SOUND")
|
||||
a = ap.parse_args(argv)
|
||||
|
||||
if a.selftest:
|
||||
return selftest(a.verbose)
|
||||
|
||||
if a.check is not None:
|
||||
primary, faults, detail = classify(a.check)
|
||||
if not a.quiet:
|
||||
print(f"{primary}: {a.check!r}")
|
||||
if faults:
|
||||
print(f" faults: {', '.join(faults)}")
|
||||
if detail:
|
||||
print(f" detail: {json.dumps(detail)}")
|
||||
if a.json:
|
||||
with open(a.json, "w", encoding="utf-8") as fh:
|
||||
json.dump({"check": a.check, "class": primary, "faults": faults,
|
||||
"detail": detail}, fh, indent=2)
|
||||
fh.write("\n")
|
||||
return 1 if (a.fail_on_fault and primary != "SOUND") else 0
|
||||
|
||||
if a.log:
|
||||
with open(a.log, encoding="utf-8", errors="replace", newline="") as fh:
|
||||
text = fh.read()
|
||||
cmd = extract_check(text)
|
||||
primary, faults, detail = classify(cmd)
|
||||
if not a.quiet:
|
||||
print(f"{a.log}: {primary}")
|
||||
print(f" extracted: {cmd!r}")
|
||||
if faults:
|
||||
print(f" faults: {', '.join(faults)}")
|
||||
if detail:
|
||||
print(f" detail: {json.dumps(detail)}")
|
||||
return 1 if (a.fail_on_fault and primary != "SOUND") else 0
|
||||
|
||||
ralph_dir = a.ralph_dir or "/root/.hermes/wizard-outputs/ralph"
|
||||
runs, unlogged = read_ralph_dir(ralph_dir, include_unlogged=a.all)
|
||||
if not runs:
|
||||
print(f"no iter logs in {ralph_dir}")
|
||||
return 0 if not a.fail_on_fault else 1
|
||||
rc = report(runs, a.json, a.quiet, unlogged)
|
||||
if a.fail_on_fault:
|
||||
graded = [grade_run(r) for r in runs]
|
||||
return 1 if any(g["class"] != "SOUND" for g in graded) else 0
|
||||
return rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user