tools (volt): reply_check learns the reported-interview form, so the gate's reply-headings leg is green again (41 -> 49 cells)
Timmy's c46d7dd filed replies/2026-10-19-to-zephyrus-interview.md, whose name
carries no number by construction. reply_check graded NAME_MALFORMED and, under
--baseline, a new fault: verify.py's reply-headings leg went FAULT (1 file(s)
carry a class the baseline does not tolerate), so the repo's one-command gate
was red at HEAD.
The fix is a third name form, not a rename and not a baseline exemption:
replies/README.md's numbered contract is about replies, and an interview is not
one. INTERVIEW_RE accepts YYYY-MM-DD-to-<recipient>-interview.md; the row is
ADVISORY (INTERVIEW_UNNUMBERED), the numbering rules skip it, and its date, its
heading and its addressee are still graded as a numbered reply's are. A
misspelled slug stays NAME_MALFORMED.
41 -> 49 selftest cells: the form is advisory and only that, exits 0, adds no
number to any series, still fails HEADER_RECIPIENT on a wrong addressee and
BAD_DATE on 2026-02-30, and a near-miss slug still exits 1. Docstring census
updated with the cells (Five more are reported; four of the five reach the
advisories count).
--repo . --baseline tools/reply_baseline.tsv: 196 file(s), 0 new fault(s), 1
known-faulting, rc 0. Bare run rc 1 with faults: 1, advisories: 20 (194 + 6).
This commit is contained in:
1 parent
c46d7dde33
commit
2f32c23976
2 files changed
+80
-21
No files matched your search
@@ -5,3 +5,9 @@ addresses and `<N>` is the next free integer in that recipient's own series. The
|
||||
inside the file, and the commit carries them. Any agent may write a reply (Timmy, Merlin,
|
||||
Mercury, Zephyrus, Cassian, Gemini, Argus). Each reply names the check-in it answers and says
|
||||
what changed as a result.
|
||||
|
||||
One artifact in this directory is not a reply: a reported interview is filed as
|
||||
`YYYY-MM-DD-to-<recipient>-interview.md`, with `-interview` where the number goes. It has no
|
||||
number by construction, so `tools/reply_check.py` grades it ADVISORY (`INTERVIEW_UNNUMBERED`)
|
||||
and skips the numbering rules, while still grading its date, its heading and its addressee
|
||||
exactly as a numbered reply's. Any other non-numeric suffix is still `NAME_MALFORMED`.
|
||||
+74
-21
@@ -22,9 +22,10 @@ WHAT IT MEASURES (read-only; the only write path is --json)
|
||||
file says about itself. Seven classes are graded:
|
||||
|
||||
NAME_MALFORMED the filename is neither the numbered contract form nor
|
||||
the one unnumbered legacy form -- the row is skipped
|
||||
by the header rules, because there is no name to check
|
||||
it against.
|
||||
either of the two unnumbered forms (the legacy file,
|
||||
the reported interview) -- the row is skipped by the
|
||||
header rules, because there is no name to check it
|
||||
against.
|
||||
BAD_DATE the date half is not a real calendar date.
|
||||
NO_HEADER no `# ` heading in the first --head-lines lines.
|
||||
UNREADABLE the file is not UTF-8 text, so no heading can be read
|
||||
@@ -39,14 +40,19 @@ WHAT IT MEASURES (read-only; the only write path is --json)
|
||||
FAULT: "the next free integer in that recipient's own
|
||||
series" cannot produce the same integer twice.
|
||||
|
||||
Four more are reported and printed, and are ADVISORY by construction -- each
|
||||
Five more are reported and printed, and are ADVISORY by construction -- each
|
||||
is a stated consequence of history that predates the naming rule, not a
|
||||
defect this tool can attribute to a writer. Three of the four reach the
|
||||
defect this tool can attribute to a writer. Four of the five reach the
|
||||
`advisories: N` count; HOLE rides the `series:` line instead, because a hole
|
||||
belongs to a recipient's series and not to any one file:
|
||||
|
||||
LEGACY_UNNUMBERED the one legacy form (no `-<N>`). Its number is implicit
|
||||
in its position, so numbering rules skip it.
|
||||
INTERVIEW_UNNUMBERED the reported-interview form, `-interview` in the
|
||||
number's place. It is not a reply and carries no
|
||||
number by construction, so the numbering rules skip it
|
||||
-- but its date, its heading and its addressee are
|
||||
still graded exactly as a numbered reply's are.
|
||||
NO_RECIPIENT_IN_HEADING a heading that names no addressee at all (the
|
||||
early "`# Reply 14 -- Q31 ...`" house style). Nothing
|
||||
contradicts the filename; the heading is silent.
|
||||
@@ -88,14 +94,16 @@ BASELINE -- the archive's one fault, stated instead of hidden
|
||||
The ledger names the tolerance, and the row stays visible in every report.
|
||||
|
||||
SELFTEST
|
||||
`--selftest` grades 41 cells, building every fixture in a fresh temporary
|
||||
`--selftest` grades 49 cells, building every fixture in a fresh temporary
|
||||
directory: each fault class fires on a fixture that carries it and only the
|
||||
classes that belong there (a wrong addressee, a wrong number, a duplicate
|
||||
number, an impossible date, a missing heading, a malformed name, non-UTF-8
|
||||
bytes), the advisory classes stay advisory and exit 0 (legacy form, hole,
|
||||
heading that names nobody), the prefix match accepts a hyphenated handle,
|
||||
README.md is not a row, and both rc 2 paths (missing directory, file where a
|
||||
directory belongs) answer 2. Four cells grade this docstring's own census --
|
||||
bytes), the advisory classes stay advisory and exit 0 (legacy form, interview
|
||||
form, hole, heading that names nobody), the prefix match accepts a hyphenated
|
||||
handle, the interview form still fails on a wrong addressee or an impossible
|
||||
date and a misspelled `-interview` is still NAME_MALFORMED, README.md is not
|
||||
a row, and both rc 2 paths (missing directory, file where a directory
|
||||
belongs) answer 2. Four cells grade this docstring's own census --
|
||||
the two spelled class counts against FAULTS/ADVISORIES, every graded class
|
||||
named here, and the pre-fix words refused on a fixture. A rule that stops
|
||||
firing and a rule that fires everywhere both fail a cell.
|
||||
@@ -113,6 +121,7 @@ import tempfile
|
||||
|
||||
NAME_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-to-([a-z][a-z0-9]*)-(\d{2,})\.md$")
|
||||
LEGACY_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-to-([a-z][a-z0-9]*)\.md$")
|
||||
INTERVIEW_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-to-([a-z][a-z0-9]*)-interview\.md$")
|
||||
HEADING_TO = re.compile(r"\bto\s+([A-Za-z][A-Za-z0-9-]*)")
|
||||
HEADING_ARROW = re.compile(r"(?:→|->)\s*([A-Za-z][A-Za-z0-9-]*)")
|
||||
HEADING_NUM = re.compile(r"\breply\s+(\d{1,4})\b", re.IGNORECASE)
|
||||
@@ -120,8 +129,8 @@ ROSTER_HANDLE_RE = re.compile(r"^[a-z][a-z0-9]*$")
|
||||
|
||||
FAULTS = ("NAME_MALFORMED", "BAD_DATE", "NO_HEADER", "UNREADABLE",
|
||||
"HEADER_RECIPIENT", "HEADER_NUMBER", "DUPLICATE_NUMBER")
|
||||
ADVISORIES = ("LEGACY_UNNUMBERED", "NO_RECIPIENT_IN_HEADING",
|
||||
"NO_NUMBER_IN_HEADING", "HOLE")
|
||||
ADVISORIES = ("LEGACY_UNNUMBERED", "INTERVIEW_UNNUMBERED",
|
||||
"NO_RECIPIENT_IN_HEADING", "NO_NUMBER_IN_HEADING", "HOLE")
|
||||
|
||||
WORD_NUMBERS = {"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6,
|
||||
"seven": 7, "eight": 8, "nine": 9, "ten": 10, "eleven": 11,
|
||||
@@ -277,8 +286,8 @@ def scan(dirpath, head_lines=10, roster=()):
|
||||
"""(rows, faults, advisories, series, holes) for one replies directory.
|
||||
|
||||
rows: one dict per file, in sorted filename order, each with `file`,
|
||||
`date`, `recipient`, `number` (None for the legacy form), `heading`, and
|
||||
`classes` (a list). Nothing is written.
|
||||
`date`, `recipient`, `number` (None on both unnumbered forms), `heading`,
|
||||
and `classes` (a list). Nothing is written.
|
||||
"""
|
||||
rows = []
|
||||
for name in sorted(os.listdir(dirpath)):
|
||||
@@ -290,23 +299,30 @@ def scan(dirpath, head_lines=10, roster=()):
|
||||
row = {"file": name, "date": None, "recipient": None, "number": None,
|
||||
"heading": None, "classes": []}
|
||||
m = NAME_RE.match(name)
|
||||
legacy = False
|
||||
legacy = interview = False
|
||||
if m:
|
||||
y, mo, d, row["recipient"], num = m.groups()
|
||||
row["number"] = int(num)
|
||||
else:
|
||||
m = LEGACY_RE.match(name)
|
||||
if not m:
|
||||
row["classes"].append("NAME_MALFORMED")
|
||||
rows.append(row)
|
||||
continue
|
||||
y, mo, d, row["recipient"] = m.groups()
|
||||
legacy = True
|
||||
if m:
|
||||
y, mo, d, row["recipient"] = m.groups()
|
||||
legacy = True
|
||||
else:
|
||||
m = INTERVIEW_RE.match(name)
|
||||
if not m:
|
||||
row["classes"].append("NAME_MALFORMED")
|
||||
rows.append(row)
|
||||
continue
|
||||
y, mo, d, row["recipient"] = m.groups()
|
||||
interview = True
|
||||
row["date"] = "%s-%s-%s" % (y, mo, d)
|
||||
if not _real_date(y, mo, d):
|
||||
row["classes"].append("BAD_DATE")
|
||||
if legacy:
|
||||
row["classes"].append("LEGACY_UNNUMBERED")
|
||||
if interview:
|
||||
row["classes"].append("INTERVIEW_UNNUMBERED")
|
||||
heading = read_heading(path, head_lines)
|
||||
unreadable = heading is False
|
||||
row["heading"] = heading if isinstance(heading, str) else None
|
||||
@@ -588,6 +604,43 @@ def selftest():
|
||||
classes(d, "2026-09-24-to-zephyrus.md") == ["LEGACY_UNNUMBERED"])
|
||||
cell("legacy form exits 0", rc(["--dir", d]) == 0)
|
||||
|
||||
# 7b the reported-interview form: no number by construction, so the
|
||||
# numbering rules skip it -- but everything a name can still contradict
|
||||
# (its date, its addressee) is graded exactly as on a numbered reply.
|
||||
d = os.path.join(root, "interview")
|
||||
os.makedirs(d)
|
||||
_write(d, "2026-10-19-to-zephyrus-interview.md",
|
||||
"# Interview to zephyrus -- 2026-10-19\n\nbody\n")
|
||||
cell("interview form is advisory, and only that",
|
||||
classes(d, "2026-10-19-to-zephyrus-interview.md") == ["INTERVIEW_UNNUMBERED"])
|
||||
cell("interview form exits 0", rc(["--dir", d]) == 0)
|
||||
_, _, _, series_i, holes_i = scan(d)
|
||||
cell("interview form contributes no number to any series",
|
||||
series_i == {} and holes_i == {})
|
||||
|
||||
d = os.path.join(root, "interview-wrongparty")
|
||||
os.makedirs(d)
|
||||
_write(d, "2026-10-19-to-zephyrus-interview.md",
|
||||
"# Interview to Merlin -- 2026-10-19\n")
|
||||
cell("interview heading still grades its addressee",
|
||||
classes(d, "2026-10-19-to-zephyrus-interview.md") ==
|
||||
["INTERVIEW_UNNUMBERED", "HEADER_RECIPIENT"])
|
||||
cell("interview with the wrong addressee exits 1", rc(["--dir", d]) == 1)
|
||||
|
||||
d = os.path.join(root, "interview-baddate")
|
||||
os.makedirs(d)
|
||||
_write(d, "2026-02-30-to-zephyrus-interview.md", "# Interview to zephyrus\n")
|
||||
cell("interview form still grades its date",
|
||||
classes(d, "2026-02-30-to-zephyrus-interview.md") ==
|
||||
["BAD_DATE", "INTERVIEW_UNNUMBERED"])
|
||||
|
||||
d = os.path.join(root, "interview-nearmiss")
|
||||
os.makedirs(d)
|
||||
_write(d, "2026-10-19-to-zephyrus-inteview.md", "# Interview to zephyrus\n")
|
||||
cell("a misspelled interview slug is still NAME_MALFORMED",
|
||||
classes(d, "2026-10-19-to-zephyrus-inteview.md") == ["NAME_MALFORMED"])
|
||||
cell("a misspelled interview slug exits 1", rc(["--dir", d]) == 1)
|
||||
|
||||
# 8 a real-calendar check, not a shape check
|
||||
d = os.path.join(root, "baddate")
|
||||
os.makedirs(d)
|
||||
|
||||
Reference in new issue
Block a user