Files
ersatztv/scripts/tests/test_check_doc_narrative.py
T
timothyandClaude Fable 5.1 a7d91bf15a
Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 35s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 57s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 37s
PR Gates / Docs update reminder (pull_request) Successful in 1m0s
PR Gates / decisions lifecycle (pull_request) Successful in 20s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 17s
review-verdict/h10 Review-verdict: MERGEABLE @ a7d91bf (base: main)
Review verdict / Set review-verdict status (pull_request_target) Successful in 45s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 19m27s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m4s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
fix(876): sweep session narrative out of hooks, workflows, scripts, tests and code comments; grow the detector to the process corpus
`docs.no-session-narrative` reaches every durable artifact, but its detector scanned only
`docs/**/*.md` and root markdown, and nothing had ever swept the rest. The issue named four sites
from one grep and called them a floor. Deriving the population instead — a whitespace-joined sweep
over every tracked file outside the detector, for the detector's own phrasings plus the attribution
and review-round class #812 found — gave 453 sites in 108 files at `fb5592971`, and a second pass
for phrasings the first list missed (hyphenated `round-N`, "an earlier version", "the reviewer
proved") added residuals in the same files. Every site was classified with #812's three
dispositions (CUT / SEVER / KEEP with its sub-kind) under the who-benefits test; the per-site
manifests are on the PR. The rejected designs, tested-and-rejected fixtures, measurements and
traps stay; the attribution of who found them and the round in which they were found go.

The detector's population grows to `.claude/`, `.gitea/`, `.husky/` and `scripts/` regardless
of extension, minus the detector and its own test (whose fixtures ARE the phrasings) and minus
`scripts/tests/fixtures/` (test data, including decision-record copies — the same reasoning as
the records' own exemption, and what keeps the record's depth measurement true), and `--all`
lists tracked REGULAR files only — a symlink's content is its target and a gitlink has none. The #812
argument for leaving `docs/superpowers/**` in the population runs the other way here: `--diff`
sees only ADDED lines, and 287 of the 453 sites were under 30 days old — this corpus is where
narrative is being added, so the advisory nudge has reach. Density agrees: 56 line-mode hits over
the 113 regular files the predicate admits, against 9 over 66 docs files before #812. `web/` and C# stay out on the same
measurement (3 of 74 PATTERNS-matching sites, ~4,600 files). The predicate did not grow: PATTERNS
matched 74 of 453 sites, and widening the word list to the attribution class is the treadmill
the withdrawn parity test ran on. The population oracle is restated over segments with the new
arms, the synthetic cross product gains the process heads and non-markdown extensions, a fixture
witnesses that a tracked symlink is neither scanned nor counted, a `.py.bak` axis separates a
by-name exemption from a `startswith` over the same tuple, and eight mutants (drop the process
arm, drop the by-name exemption, exempt by `startswith`, drop or add a prefix, drop the fixtures
exemption, list only markdown, drop the symlink filter, test the mode per row instead of per
path) each
redden it. A pre-existing silent drop in `--diff` goes with it: git tab-terminates a `+++`
filename that contains a space, and the kept tab made `is_scanned_path` refuse the file with no
notice — fixed, with a positive control and its own mutant.

Code is unchanged by construction, measured per file type against `origin/main`: Python modules
are AST-equal with docstrings stripped, except `#` lines inside the embedded fixture programs
(string literals) of three test modules; workflows differ only in `#` lines inside `run:` block
scalars; shell, C#, TypeScript and jq are equal with comment lines stripped. The stated
exceptions: the detector and its test, 26 vitest titles that carried review-round or severity
labels or a reviewer attribution (call sites whose title changed — every changed title line
walked back to its `it(` / `it.each(...)(` anchor, so a `' + '` concatenation counts once), two
registry note strings and the mutation manifest's prose fields. scripts/tests: 1565 passed.
Web: lint, typecheck, 1319 tests green. Closes #876.

Decisions-Edit: yes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEcBoFw7ctrf3Nb7R7x7wk
2026-09-03 20:51:39 +02:00

642 lines
34 KiB
Python

"""Proofs for `scripts/check-doc-narrative.py` (ersatztv#784).
Every case below is a defect DEMONSTRATED in the first, shell implementation. They are
here because the never-fails invariant and the reported line numbers are both asserted in prose in
four places (the script header, the workflow comment, `docs/guard-inventory.md` and
`docs/remote-state-inventory.md`), and an invariant asserted only in prose is the shape this repo
keeps getting wrong.
The line-number cases all compare against the TRUTH computed from the file on disk, never against a
number written into the test — a hand-written expectation is a second copy of the parser.
"""
from __future__ import annotations
import importlib.util
import re
import subprocess
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[2]
SCRIPT = REPO / "scripts" / "check-doc-narrative.py"
NARRATIVE = "I initially thought otherwise"
HIT = re.compile(r"^::warning file=(?P<path>[^:]+)::(?P=path):(?P<line>\d+) ", re.MULTILINE)
def run(cwd: Path, *args: str) -> subprocess.CompletedProcess:
return subprocess.run([sys.executable, str(SCRIPT), *args], cwd=cwd, capture_output=True, text=True)
def hits(out: str) -> set[tuple[str, int]]:
return {(m.group("path"), int(m.group("line"))) for m in HIT.finditer(out)}
def truth(root: Path, rel: str) -> set[tuple[str, int]]:
"""Where the narrative marker ACTUALLY is, read back off disk."""
text = (root / rel).read_text(encoding="utf-8")
return {
(rel, i)
for i, line in enumerate(text.splitlines(), start=1)
if "initially thought" in line or "an earlier draft" in line.lower()
}
def git(repo: Path, *args: str) -> None:
subprocess.run(["git", *args], cwd=repo, check=True, capture_output=True)
@pytest.fixture()
def repo(tmp_path: Path) -> Path:
r = tmp_path / "r"
(r / "docs").mkdir(parents=True)
git(r.parent, "init", "-q", "r")
git(r, "config", "user.email", "t@example.com")
git(r, "config", "user.name", "t")
(r / "docs" / "seed.md").write_text("seed\n", encoding="utf-8")
git(r, "add", "-A")
git(r, "commit", "-qm", "base")
return r
def commit(r: Path, msg: str = "c") -> None:
git(r, "add", "-A")
git(r, "commit", "-qm", msg)
# --- the never-fails invariant -------------------------------------------------------------------
@pytest.mark.parametrize(
("args", "expected"),
[
((), "scanned "),
(("--all",), "scanned "),
(("--diff",), "--diff needs a base ref"), # no base ref at all
(("--diff", ""), "--diff needs a base ref"), # `origin/` with base_ref unset
(("--diff", "no-such-ref-xyz"), "SCANNED NOTHING"), # an unresolvable ref
(("--diff", "origin/"), "SCANNED NOTHING"),
(("--nonsense",), "unknown mode"),
],
)
def test_every_argument_shape_exits_zero_HAVING_HANDLED_IT(repo: Path, args, expected: str) -> None:
"""The header, the workflow comment and both inventories state exit 0 absolutely.
The expected message is asserted alongside the exit code on purpose: the script ends with a bare
`except` that returns 0, so an exit-code-only assertion is satisfied by an unhandled crash and
would pass against a script that handles none of these shapes.
"""
p = run(repo, *args)
assert p.returncode == 0, f"args={args} exited {p.returncode}: {p.stderr}"
assert expected in p.stdout, f"args={args} exited 0 but did not HANDLE it: {p.stdout!r}"
assert "internal error" not in p.stdout, f"args={args} reached the last-resort handler: {p.stdout!r}"
def test_an_unknown_mode_prints_usage_and_scans_nothing(repo: Path) -> None:
"""Stated as the observable behaviour it actually pins. It is NOT a proof that the else-branch
cannot reach `run_all`: the branch returns before the warnings are printed, so a mutant calling
`run_all` there is silent and no black-box test can see it."""
(repo / "docs" / "u.md").write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
p = run(repo, "--nonsense")
assert hits(p.stdout) == set()
assert "scanned" not in p.stdout
def test_a_DELETED_doc_is_not_reported_as_added_content(repo: Path) -> None:
"""A deletion contributes no added lines.
Stated exactly: this pins the BEHAVIOUR, and it is NOT a proof of the `+++ /dev/null` arm, which
is defensive — removing that arm reddens nothing, because a deletion yields no `+` lines either
way. The script comment says the same. A docstring claiming a proof it does not have is worse
than no docstring: it is the thing a later reader trusts instead of re-checking."""
(repo / "docs" / "del.md").write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
(repo / "docs" / "del.md").unlink()
commit(repo)
assert hits(run(repo, "--diff", "HEAD~1").stdout) == set()
# Every knob DEMONSTRATED to break the parse — not every knob that reshapes diff output, which is a
# universal nobody can check and which is the enumeration that failed three rounds running.
# (`diff.mnemonicPrefix` is a live example of one that reshapes the header and has no row: it emits
# `+++ w/f.md`. It is inert HERE for a reason worth stating exactly, because the obvious explanation
# is the wrong one — not because `--dst-prefix=b/` beats it, though it does, but because git only
# uses mnemonic prefixes when a diff side is the worktree or the index, and `run_diff` issues the
# three-dot `base...HEAD` form, where git emits plain `a/`…`b/` either way. Measured, not reasoned.)
# Three of these were each demonstrated turning
# a real hit into `scanned 0 file(s)`, one at a time, in three separate rounds — which is why the fix
# stopped pinning variants and removed the surface. The table is here so the next knob someone finds
# gets a row instead of a round.
FORMAT_KNOBS = [
("core.quotePath", "true"), # quotes non-ASCII paths out of the population
("diff.dstPrefix", "dst/"), # rewrites the header the path is read from
("diff.srcPrefix", "src/"),
("diff.noprefix", "true"),
("color.diff", "always"), # injects ANSI escapes into every line
("color.ui", "always"),
("diff.renames", "false"), # turns a `git mv` into a whole-file add
("diff.context", "9"), # a configured context must not beat the -U0 on the CLI
("diff.external", "/bin/echo"), # replaces the output wholesale
]
@pytest.mark.parametrize(("key", "value"), FORMAT_KNOBS)
def test_no_git_FORMAT_CONFIG_can_produce_a_false_clean(repo: Path, key: str, value: str) -> None:
"""A false clean is the worst outcome available to an advisory check: it is indistinguishable
from a real one and nobody looks twice."""
(repo / "docs" / "pfx.md").write_text("x\n", encoding="utf-8")
commit(repo)
(repo / "docs" / "pfx.md").write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
git(repo, "config", key, value)
assert hits(run(repo, "--diff", "HEAD~1").stdout) == truth(repo, "docs/pfx.md"), f"{key}={value}"
# Only PYTHONIOENCODING is listed. `LC_ALL=C` was here too and was VACUOUS — PEP 540 UTF-8 mode
# means the pre-fix script passed it as well, so it read as a second witness where there was one.
@pytest.mark.parametrize("env_name,env_value", [("PYTHONIOENCODING", "ascii")])
def test_a_NON_UTF8_stdio_does_not_break_the_never_fails_invariant(repo: Path, env_name, env_value) -> None:
"""Both the summary line and the last-resort handler carry U+2014, so under ascii stdio the code
guaranteeing exit 0 was itself what raised."""
import os
env = dict(os.environ, **{env_name: env_value})
env.pop("PYTHONUTF8", None)
p = subprocess.run([sys.executable, str(SCRIPT), "--all"], cwd=repo, capture_output=True, text=True, env=env)
assert p.returncode == 0, f"{env_name}={env_value} exited {p.returncode}: {p.stderr}"
def test_a_tracked_but_DELETED_doc_does_not_kill_the_run(repo: Path) -> None:
"""`git ls-files` lists index entries; deleting a doc before committing is ordinary."""
(repo / "docs" / "gone.md").write_text("x\n", encoding="utf-8")
commit(repo)
(repo / "docs" / "gone.md").unlink()
p = run(repo, "--all")
assert p.returncode == 0
assert "skipped docs/gone.md" in p.stdout
def test_an_unresolvable_base_REPORTS_that_it_scanned_nothing(repo: Path) -> None:
"""A silent zero-file scan is indistinguishable from a clean one — the whole point of #751."""
p = run(repo, "--diff", "no-such-ref-xyz")
assert p.returncode == 0
assert "SCANNED NOTHING" in p.stdout
# The guard names the line this branch must NOT also print. It previously named "nothing to
# flag", a string that occurs nowhere in this script (it belongs to the sibling parity step), so
# it could never fail — a negative assertion over a literal that does not exist is not a guard.
assert "scanned 0 file(s)" not in p.stdout, "printed a clean-looking summary after scanning nothing"
def test_a_genuine_clean_scan_REPORTS_its_population(repo: Path) -> None:
p = run(repo, "--all")
assert "scanned 1 file(s); 0 advisory warning(s)" in p.stdout
# --- line numbers, against truth read off disk ---------------------------------------------------
def test_a_file_with_NO_trailing_newline_does_not_shift_later_lines(repo: Path) -> None:
"""`\\ No newline at end of file` is a marker, not a line of the new file."""
(repo / "docs" / "n.md").write_text("a\nb\nc", encoding="utf-8") # no trailing newline
commit(repo)
(repo / "docs" / "n.md").write_text(f"a\nb\nZ\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
assert hits(run(repo, "--diff", "HEAD~1").stdout) == truth(repo, "docs/n.md")
def test_an_added_line_whose_TEXT_starts_with_plus_plus_is_content_not_a_header(repo: Path) -> None:
"""Docs here embed diff output in fenced blocks, so `++ ` at column 0 is real."""
(repo / "docs" / "p.md").write_text("p\n", encoding="utf-8")
commit(repo)
(repo / "docs" / "p.md").write_text(f"p\n++ a fenced diff line\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
assert hits(run(repo, "--diff", "HEAD~1").stdout) == truth(repo, "docs/p.md")
def test_a_multi_hunk_file_numbers_every_hunk_from_its_own_header(repo: Path) -> None:
body = [f"l{i}" for i in range(1, 31)]
(repo / "docs" / "m.md").write_text("\n".join(body) + "\n", encoding="utf-8")
commit(repo)
body[4] = NARRATIVE # replace, early hunk
body.insert(15, NARRATIVE) # pure insert, middle hunk
body[-1] = NARRATIVE # replace, last hunk
(repo / "docs" / "m.md").write_text("\n".join(body) + "\n", encoding="utf-8")
commit(repo)
assert hits(run(repo, "--diff", "HEAD~1").stdout) == truth(repo, "docs/m.md")
def test_a_NON_ASCII_path_is_scanned_rather_than_silently_skipped(repo: Path) -> None:
"""`core.quotePath` quotes the path, and a quoted path matches no scope rule — it vanishes."""
(repo / "docs" / "café.md").write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
assert hits(run(repo, "--all").stdout) == truth(repo, "docs/café.md")
assert hits(run(repo, "--diff", "HEAD~1").stdout) == truth(repo, "docs/café.md")
def test_a_final_UNTERMINATED_line_is_still_scanned_in_all_mode(repo: Path) -> None:
(repo / "docs" / "t.md").write_text(f"ok\n{NARRATIVE}", encoding="utf-8") # no trailing newline
commit(repo)
assert hits(run(repo, "--all").stdout) == truth(repo, "docs/t.md")
def test_a_RENAME_does_not_re_flag_the_whole_pre_existing_file(repo: Path) -> None:
"""Without rename detection a `git mv` reports every line of the file as newly added."""
(repo / "docs" / "r1.md").write_text("a\n" + f"{NARRATIVE}\n" + "b\n", encoding="utf-8")
commit(repo)
git(repo, "mv", "docs/r1.md", "docs/r2.md")
commit(repo)
assert hits(run(repo, "--diff", "HEAD~1").stdout) == set()
# `diff.renames` defaults to true, so the assertion above passes with or without the explicit
# `--find-renames`. Turning the config off is what makes that flag load-bearing and this test a
# real proof of it rather than a restatement of a git default.
git(repo, "config", "diff.renames", "false")
assert hits(run(repo, "--diff", "HEAD~1").stdout) == set(), "the --find-renames pin is not doing its job"
# --- the population ------------------------------------------------------------------------------
# Hand-written, one entry per exempt prefix. NOT derived from the script — a population read out of
# the code under test proves only that the code agrees with itself. The completeness guard below is
# what stops a new prefix from shipping without a case here.
EXEMPT_SAMPLES = {
"docs/decisions/": ("docs/decisions/records/x/y.md", "docs/decisions/archive/x/y.md"),
# test DATA under a process prefix (#876): a decision-record copy and a shell fixture
"scripts/tests/fixtures/": ("scripts/tests/fixtures/premigration/decisions.md", "scripts/tests/fixtures/x/y.sh"),
}
@pytest.mark.parametrize("mode", ["--all", "--diff"])
def test_every_exempt_genre_is_exempt_in_BOTH_modes(repo: Path, mode: str) -> None:
"""`docs/decisions/**` is exempt WHOLESALE (#784). Both modes, because an exemption that held
only for `--all` would still flag the file on the PR that adds it.
The CONTROLS are what make this a measurement. `hits(out) == set()` alone also passes when the
scan examined nothing at all — a scanned-zero run reads identical to a working exemption — so a
non-exempt file carrying the same marker must come back HIT in the same invocation.
Each exempt sample is also paired with a MINIMAL TWIN — the same path with the exempt directory
renamed — which raises the cost of a structural bypass but does NOT close it. Stated as a limit
rather than a guarantee, because three successive attempts to close it were each defeated by the
next: a lone depth-1 control fell to a depth-1 population rule; a depth-3 control fell to a
`count("/") <= 3` cap; the twins themselves fell to a directory-NAME rule keyed on the renamed
SECOND segment (`path.count("/") > 1 and "-" not in path.split("/", 2)[1]`), which skipped both
samples and kept both twins. It reddens here now, but only because the `docs/superpowers/`
fixture below happens to have a hyphen-free second segment — luck, not design, and not a
property this test establishes.
So this test proves the scan EXAMINED something; it does NOT prove a sample's absence is
attributable to `EXEMPT_PREFIXES` rather than to its shape. Do not add a fourth finite control —
each of the three was locally correct and the sequence did not converge, the same shape as the
withdrawn `test_review_verdict_vocabulary_parity.py` (six rounds, then deleted) that
`docs.no-session-narrative` cites as the case for stopping. What DOES close it is a closed-form
proof rather than a fixture:
`test_the_population_agrees_with_an_INDEPENDENT_RESTATEMENT_over_the_REAL_corpus` below.
"""
for rels in EXEMPT_SAMPLES.values():
for rel in rels:
f = repo / rel
f.parent.mkdir(parents=True, exist_ok=True)
f.write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
# The twin of `docs/decisions/records/x/y.md` is `docs/decisions-not/records/x/y.md`: identical
# in every structural respect, and NOT under the exempt prefix.
twins = {rel.replace(prefix, prefix[:-1] + "-not/", 1) for prefix, rels in EXEMPT_SAMPLES.items() for rel in rels}
# `docs/superpowers/` is deliberately NOT exempt (#812), and re-exempting it is the specific
# regression this change argues against. It is named here rather than left to the closed-form
# oracle, because that oracle proves a property of `is_scanned_path`, not that the SCAN consults
# it: a `continue` added in `run_all`/`run_diff` reinstates the exemption with the function
# untouched. Only a path that reaches the OUTPUT witnesses the wiring.
twins = twins | {"docs/superpowers/plans/2026-01-01-x.md"}
for rel in twins | {"docs/control.md"}:
control = repo / rel
control.parent.mkdir(parents=True, exist_ok=True)
control.write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
out = run(repo, mode, "HEAD~1").stdout if mode == "--diff" else run(repo, mode).stdout
assert hits(out) == {(rel, 2) for rel in twins | {"docs/control.md"}}
def test_the_population_agrees_with_an_INDEPENDENT_RESTATEMENT_over_the_REAL_corpus() -> None:
"""CLOSED-FORM. This is what three successive finite controls could not buy, and it is why the
retraction in the exemption test's docstring stops at "not another fixture" rather than at
"unprovable".
The rule as the record states it — `docs/**/*.md` minus `docs/decisions/**`, plus root-level
`*.md`, plus every file under the four process directories minus the detector and its test
(#876) — is restated below over path SEGMENTS rather than string prefixes, so the two are not
one expression copy-pasted, then compared against `is_scanned_path` on every tracked path in
this repo. A finite fixture can only witness the shapes someone thought to write down;
this witnesses every real path, so any population change that alters a verdict on one of them
reddens — the depth-1 rule and the directory-NAME rule among them.
It is NOT sufficient on its own, and the arm below says why with the measurement: a
`count("/") <= 3` cap changes no real path's verdict (re-measured 2026-09-03 after #876 grew the
population: 179 files, none deeper than three slashes — and only because `scripts/tests/fixtures/`
is exempt, whose record copies sit at four to six), so within this test only the synthetic arm
catches it. Do not trim that arm as belt-and-braces; it is the half that sees a
rule the corpus has no instance of yet. (The exemption test above happens to redden on that cap
too — its two minimal twins sit at four slashes — but that is incidental, not the arm carrying the claim.)
"""
def restated(path: str) -> bool:
parts = path.split("/")
if parts in (["scripts", "check-doc-narrative.py"], ["scripts", "tests", "test_check_doc_narrative.py"]):
return False # the detector and its test: exempt by NAME, their fixtures ARE the phrasings
if parts[:3] == ["scripts", "tests", "fixtures"] and len(parts) > 3:
return False # test DATA, including decision-record copies (#876)
if len(parts) > 1 and parts[0] in {".claude", ".gitea", ".husky", "scripts"}:
return True # the process corpus, any extension (#876)
if not parts[-1].endswith(".md"):
return False
if len(parts) == 1:
return True # root-level *.md
if parts[0] != "docs":
return False # nested markdown outside docs/ and the process corpus is out of scope
return parts[1] != "decisions" # docs/** minus docs/decisions/**
spec = importlib.util.spec_from_file_location("_cdn_pop", SCRIPT)
assert spec is not None and spec.loader is not None
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
rc = subprocess.run(["git", "ls-files", "-s", "-z"], cwd=REPO, capture_output=True, text=True, check=True)
entries = [e.partition("\t") for e in rc.stdout.split("\0") if e]
paths = sorted({path for _meta, _, path in entries})
# "regular" as `run_all` defines it: EVERY stage of the path a regular blob, not any one of them.
stage_modes: dict[str, set[str]] = {}
for meta, _, path in entries:
stage_modes.setdefault(path, set()).add(meta.split(" ", 1)[0])
regular = {path for path, ms in stage_modes.items() if all(m.startswith("100") for m in ms)}
assert len(paths) > 1000, f"corpus looks wrong: {len(paths)} paths"
disagree = [p for p in paths if mod.is_scanned_path(p) != restated(p)]
assert not disagree, f"population disagrees with the restated rule on {len(disagree)}: {disagree[:8]}"
# Anti-vacuity: an oracle that says False everywhere would agree with a scan-nothing mutant.
# `scanned` is what `--all` opens: the predicate over REGULAR blobs only, because a symlink or
# gitlink the predicate admits is excluded by mode, and a claim about the scanned corpus's shape
# must be measured over the corpus that is scanned.
scanned = [p for p in paths if restated(p) and p in regular]
exempt = [p for p in paths if p.startswith("docs/decisions/")]
process = [p for p in scanned if not p.endswith(".md")]
assert len(scanned) > 50 and len(exempt) > 50, f"{len(scanned)} scanned / {len(exempt)} exempt"
assert len(process) > 50, f"{len(process)} non-markdown process files — the #876 arm saw nothing"
# The record states a MEASUREMENT about this corpus — no scanned path deeper than three slashes,
# so a `count("/") <= 3` cap is a no-op against it — and nothing else couples the claim to the
# corpus. A red here is the claim going stale, not a defect in the path that broke it. Re-measure,
# then re-state every site that carries the claim: the record's `mechanics:`
# (`docs.no-session-narrative`, `mechanics:` AND the #876 paragraph of its body), this function's
# docstring, the comment on the cross product below, and the fixtures paragraph of the exemption
# comment in `check-doc-narrative.py`; then move this bound.
assert max(p.count("/") for p in scanned) <= 3, "the record's depth measurement is stale — re-state it"
assert not any(mod.is_scanned_path(p) for p in mod.EXEMPT_FILES)
assert all(p in paths for p in mod.EXEMPT_FILES), "an EXEMPT_FILES entry names nothing tracked"
# The real corpus alone cannot see a rule that differs only on a shape it does not currently
# contain — no scanned path carries more than three slashes (re-measured 2026-09-03, #876), so a
# `count("/") <= 3` cap is a no-op against it and would ship latent. So the same oracle is also
# compared over a cross product of FOUR dimensions a population rule has been observed to key on here — depth, first
# segment, second segment, extension — widened with basename, case and dotted directories after
# a mutant keyed on each was found. This ENUMERATES; it is not a universal over the
# space of rules, and a dimension on neither list has simply not been measured.
heads = ["docs", "Docs", "web", "scripts", "Scripts", ".github", ".gitea", ".claude", ".husky", "scripts-not", ""]
seconds = [
"decisions",
"superpowers",
"other",
"decisions-not",
"Decisions",
".hidden",
"tests",
"hooks",
"tests/fixtures",
"tests/fixtures-not",
"",
]
leaves = ["f", "index", "README", "check-doc-narrative", "test_check_doc_narrative"]
# `.py.bak` is the strict extension of an exempt NAME: it separates `path in EXEMPT_FILES` from
# a `startswith` over the same tuple, which the real corpus cannot (nothing tracked extends it).
exts = [".md", ".markdown", ".MD", ".txt", ".py", ".py.bak", ".sh", ".yml", ""]
synthetic = set()
for ext in exts:
for leaf in leaves:
for head in heads:
for second in seconds:
for depth in range(0, 3):
segs = [p for p in (head, second) if p] + ["x"] * depth
synthetic.add("/".join(segs + [leaf + ext]))
bad = [p for p in sorted(synthetic) if mod.is_scanned_path(p) != restated(p)]
assert not bad, f"population disagrees with the restated rule on {len(bad)} shapes: {bad[:8]}"
def test_every_exempt_prefix_has_a_case_above() -> None:
"""Completeness: adding a prefix to `EXEMPT_PREFIXES` without a sample above reddens here, so the
exemption test cannot silently stop covering the population it claims to cover."""
spec = importlib.util.spec_from_file_location("_cdn", SCRIPT)
assert spec is not None and spec.loader is not None
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
assert set(EXEMPT_SAMPLES) == set(mod.EXEMPT_PREFIXES)
for prefix, rels in EXEMPT_SAMPLES.items():
assert all(r.startswith(prefix) for r in rels), prefix
def test_markdown_outside_the_stated_population_is_not_scanned(repo: Path) -> None:
"""The population is `docs/**/*.md` minus `EXEMPT_PREFIXES`, plus root-level `*.md`, plus the
PROCESS corpus (#876) regardless of extension, minus `EXEMPT_FILES`. Nested markdown under
`web/` is out of scope, and the record's `mechanics:` says so — out of the DETECTOR, still bound
by the RULE. A skill under `.claude/`, a shell hook and a Python test are IN, and the detector's
own test file — which carries these phrasings as fixtures — is out BY NAME, so its twin one
directory over is still scanned."""
for rel in (
"web/docs/x.md",
".claude/skills/s/SKILL.md",
".claude/hooks/h.sh",
"scripts/tests/test_x.py",
"scripts/tests/test_check_doc_narrative.py",
"scripts/tests/x/test_check_doc_narrative.py",
"README.md",
):
(repo / rel).parent.mkdir(parents=True, exist_ok=True)
(repo / rel).write_text(f"# {NARRATIVE}\n", encoding="utf-8")
commit(repo)
assert hits(run(repo, "--all").stdout) == {
("README.md", 1),
(".claude/skills/s/SKILL.md", 1),
(".claude/hooks/h.sh", 1),
("scripts/tests/test_x.py", 1),
("scripts/tests/x/test_check_doc_narrative.py", 1),
}
def test_a_tracked_SYMLINK_is_not_scanned_and_not_counted(repo: Path) -> None:
"""The population is tracked REGULAR files. A tracked symlink under a process prefix is a path
the predicate admits whose worktree content is its TARGET: a directory (this repo's
`.claude/skills/jellyfin`) would be skipped with a notice, and a file outside the population
would be scanned under the link's name — the target's narrative attributed to a path the rule
does not reach. Both are excluded by mode, and the count says so."""
(repo / "web").mkdir()
(repo / "web" / "target.md").write_text(f"{NARRATIVE}\n", encoding="utf-8")
(repo / ".claude" / "skills").mkdir(parents=True)
(repo / ".claude" / "skills" / "linked.md").symlink_to("../../web/target.md")
(repo / ".claude" / "skills" / "dir").symlink_to("../../web")
(repo / ".claude" / "hooks").mkdir()
(repo / ".claude" / "hooks" / "h.sh").write_text(f"# {NARRATIVE}\n", encoding="utf-8")
commit(repo)
out = run(repo, "--all").stdout
assert hits(out) == {(".claude/hooks/h.sh", 1)}
assert "skipped" not in out
# the fixture's `docs/seed.md` plus the hook: neither symlink is counted, and neither is skipped
assert "scanned 2 file(s)" in out
def test_an_UNMERGED_path_is_scanned_once_and_counted_once(repo: Path) -> None:
"""`git ls-files -s` emits one row per index STAGE, so a path in conflict appears up to three
times. Without de-duplication `--all` opens the worktree file once per row: the same warning
three times over, and a population count that is not a count of files."""
git(repo, "checkout", "-qb", "side")
(repo / "docs" / "seed.md").write_text(f"side\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
git(repo, "checkout", "-q", "-")
(repo / "docs" / "seed.md").write_text(f"main\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
merge = subprocess.run(["git", "merge", "side"], cwd=repo, capture_output=True, text=True)
assert merge.returncode != 0, "the fixture must be in conflict"
stages = subprocess.run(["git", "ls-files", "-s", "--", "docs/seed.md"], cwd=repo, capture_output=True, text=True)
assert stages.stdout.count("\n") == 3, stages.stdout
out = run(repo, "--all").stdout
assert out.count("::warning") == 1
assert "scanned 1 file(s)" in out
def test_a_MIXED_MODE_conflict_is_not_opened(repo: Path) -> None:
"""Two stages of DIFFERENT mode under one path — a symlink beside a regular blob — with the
worktree holding one of them. A per-row mode test lets the regular row authorise opening the
path, which may be the symlink; a path is opened only when EVERY stage is a regular blob.
Built with `update-index --index-info` rather than a merge: git resolves a symlink/file add/add
conflict by RENAMING one side (`h.sh~HEAD`), so a merge never yields this index shape — an
older git, a tool, or a hand-edited index can, and `ls-files -s` reports it exactly like this."""
(repo / ".claude" / "hooks").mkdir(parents=True)
(repo / ".claude" / "hooks" / "h.sh").write_text(f"# {NARRATIVE}\n", encoding="utf-8")
commit(repo)
blob = subprocess.run(
["git", "rev-parse", "HEAD:.claude/hooks/h.sh"], cwd=repo, capture_output=True, text=True
).stdout.strip()
link = subprocess.run(
["git", "hash-object", "-w", "--stdin"], cwd=repo, input="../../web/t.sh", capture_output=True, text=True
).stdout.strip()
subprocess.run(
["git", "update-index", "--index-info"],
cwd=repo,
input=(
"0 0000000000000000000000000000000000000000\t.claude/hooks/h.sh\n"
f"100644 {blob} 2\t.claude/hooks/h.sh\n"
f"120000 {link} 3\t.claude/hooks/h.sh\n"
),
text=True,
check=True,
)
stages = subprocess.run(
["git", "ls-files", "-s", "--", ".claude/hooks/h.sh"], cwd=repo, capture_output=True, text=True
)
assert {line.split(" ", 1)[0] for line in stages.stdout.splitlines()} == {"100644", "120000"}, stages.stdout
out = run(repo, "--all").stdout
assert hits(out) == set()
assert "skipped" not in out
assert "scanned 1 file(s)" in out # the fixture's docs/seed.md only
def test_a_path_with_a_SPACE_is_scanned_in_diff_mode(repo: Path) -> None:
"""Git terminates the `+++` filename field with a TAB when the path contains a space. A parser
that keeps the tab asks `is_scanned_path("docs/my notes.md\\t")`, which is False, and the file is
dropped with no notice — the scanned-0 channel one level below the config pins. The positive
control is the same content at a space-free path in the same diff."""
(repo / "docs" / "my notes.md").write_text(f"{NARRATIVE}\n", encoding="utf-8")
(repo / "docs" / "mynotes.md").write_text(f"{NARRATIVE}\n", encoding="utf-8")
commit(repo)
out = run(repo, "--diff", "HEAD~1").stdout
assert hits(out) == {("docs/my notes.md", 1), ("docs/mynotes.md", 1)}
assert "scanned 2 file(s)" in out
# --- the detector actually detects ---------------------------------------------------------------
def test_a_MULTI_FILE_diff_scans_every_file_not_just_the_first(repo: Path) -> None:
"""Every real CI run is multi-file. Without the `in_hunk` reset on `diff --git`, the parser stays
inside the previous file's hunk and silently drops every file after the first."""
for name in ("a", "b", "c"):
(repo / "docs" / f"{name}.md").write_text("x\n", encoding="utf-8")
commit(repo)
for name in ("a", "b", "c"):
(repo / "docs" / f"{name}.md").write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
p = run(repo, "--diff", "HEAD~1")
assert hits(p.stdout) == {(f"docs/{n}.md", 2) for n in ("a", "b", "c")}
assert "scanned 3 file(s); 3 advisory warning(s)" in p.stdout
def test_the_reported_POPULATION_COUNT_matches_the_files_actually_scanned(repo: Path) -> None:
"""The count is the observable that made every false clean in this file's history visible. A
mutant that never populated the scanned set reported `scanned 0 file(s); 3 warning(s)` — green,
and self-contradictory."""
(repo / "docs" / "one.md").write_text("x\n", encoding="utf-8")
(repo / "docs" / "two.md").write_text("x\n", encoding="utf-8")
commit(repo)
(repo / "docs" / "one.md").write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
(repo / "docs" / "two.md").write_text("x\nharmless\n", encoding="utf-8")
commit(repo)
assert "scanned 2 file(s); 1 advisory warning(s)" in run(repo, "--diff", "HEAD~1").stdout
def test_a_NON_UTF8_LOCALE_does_not_silently_empty_the_scan(repo: Path) -> None:
"""`subprocess.run(errors="replace")` is the other half of the locale channel: without it a
UTF-8 doc under an ascii locale raises inside `git()`, the bare `except` catches it, and the
whole scan degrades to nothing while still exiting 0."""
import os
(repo / "docs" / "u8.md").write_text(f"héllo — em dash\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
env = dict(os.environ, LC_ALL="C", PYTHONUTF8="0", PYTHONIOENCODING="utf-8")
p = subprocess.run(
[sys.executable, str(SCRIPT), "--diff", "HEAD~1"],
cwd=repo,
capture_output=True,
text=True,
env=env,
)
assert p.returncode == 0, p.stderr
assert "internal error" not in p.stdout, p.stdout
assert hits(p.stdout) == {("docs/u8.md", 2)}
def test_the_pattern_is_case_insensitive(repo: Path) -> None:
(repo / "docs" / "case.md").write_text("AN EARLIER DRAFT of this said otherwise\n", encoding="utf-8")
commit(repo)
assert hits(run(repo, "--all").stdout) == {("docs/case.md", 1)}
def test_MUTATION_neutering_the_pattern_makes_a_known_hit_go_quiet(repo: Path, tmp_path: Path) -> None:
"""The clause-level disarm: with PATTERNS unable to match, a file that IS flagged stops being
flagged. Without this, every assertion above is satisfied by a detector that finds nothing."""
(repo / "docs" / "d.md").write_text(f"x\n{NARRATIVE}\n", encoding="utf-8")
commit(repo)
assert hits(run(repo, "--all").stdout) == truth(repo, "docs/d.md")
disarmed = tmp_path / "disarmed.py"
src = SCRIPT.read_text(encoding="utf-8")
marker = "PATTERNS = re.compile("
assert src.count(marker) == 1
disarmed.write_text(
src.replace(marker, 'PATTERNS = re.compile(r"(?!x)x" # disarmed\n or ', 1), encoding="utf-8"
)
p = subprocess.run([sys.executable, str(disarmed), "--all"], cwd=repo, capture_output=True, text=True)
assert p.returncode == 0, p.stderr
assert hits(p.stdout) == set(), "the disarmed detector still flagged something — the mutation did not take"