Three refutations written as prose under ### headings never entered the register:
check_refuted.py parses * "claim" lines, so the count stayed at 188. Registered
them properly (188 -> 192). A register that parses one syntax silently ignores
every other, and it is invisible from the author's side -- ask the register what it
holds, do not re-read what you wrote.
Both standing false positives were bullets under a header that retracts the whole
list, with no marker in the +-4-line window: scope marks them, not proximity. The
scan now includes the nearest preceding header and matches markers
case-insensitively ('An earlier version' was missed by the marker 'an earlier
version'). Controlled by planting a real revival and confirming it is still caught;
register now runs clean at 0.
Also records sylpheed-port's diagnosis of the phase-lock fallout: a number can be
inapplicable rather than wrong, and a tension built on one is manufactured. Plus
their point that some claims are not registrable in a substring register at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
83 lines
4.0 KiB
Python
Executable File
83 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Is any REFUTED claim still asserted, unmarked, elsewhere in the corpus?
|
|
|
|
`REFUTED.md` publishes deaths; it does not enforce them. `sylpheed-port`'s
|
|
`check-claims` register fails their run when a refuted claim is quoted without a
|
|
`[refuted]` token, and feeding it four withdrawals immediately flagged three still
|
|
asserted unmarked -- every one inside a correction they had written themselves.
|
|
Their point is the one worth stealing: **the token tests for something an author
|
|
must place, not for language that sounds retracted.** All three read as
|
|
corrections to a human and the marker fired anyway.
|
|
|
|
This is the equivalent for a prose corpus. For each `* "claim"` in REFUTED.md it
|
|
searches `docs/` for that exact claim text and reports every occurrence whose
|
|
neighbourhood carries no refutation marker.
|
|
|
|
⚠️ Its known weakness, stated rather than discovered: it matches the claim's
|
|
EXACT wording. A restatement in different words is invisible to it. So a clean run
|
|
means "no verbatim revival", not "no revival".
|
|
|
|
check_refuted.py [--context N]
|
|
"""
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
MARKERS = ("refuted", "REFUTED", "withdrawn", "WITHDRAWN", "retracted", "RETRACTED",
|
|
"🔴", "~~", "used to say", "used to read", "was wrong", "is wrong",
|
|
"no longer", "superseded", "corrected", "retracts", "an earlier version",
|
|
# text explicitly DECLINING to revive a claim reads as an assertion to
|
|
# a neighbourhood scan; two real hits were exactly this.
|
|
"does **not** revive", "does not revive")
|
|
|
|
# ⚠️ STRUCTURAL LIMIT, found by running this against the corpus. `BACKLOG.md` is an
|
|
# APPEND-ONLY DATED LOG: an entry under a 2026-08-12 header recording what was
|
|
# believed then is history, not revival, and reads identically to a live claim.
|
|
# A neighbourhood-language detector cannot separate "asserted now" from "recorded
|
|
# as believed then". sylpheed-port's `check-claims` avoids this by testing for a
|
|
# TOKEN AN AUTHOR MUST PLACE rather than for language -- their design is right and
|
|
# this one is a weaker approximation of it. Files that are chronological records
|
|
# are skipped rather than reported, and that is a real hole, not a fix.
|
|
CHRONOLOGICAL = {"BACKLOG.md"}
|
|
CTX = int(sys.argv[sys.argv.index("--context") + 1]) if "--context" in sys.argv else 4
|
|
|
|
root = Path("docs")
|
|
ref = root / "re" / "REFUTED.md"
|
|
claims = []
|
|
seen_report = set()
|
|
for line in ref.read_text().splitlines():
|
|
m = re.match(r'\s*\*\s*~?~?"([^"]{25,})"', line)
|
|
if m:
|
|
claims.append(m.group(1))
|
|
|
|
print(f"{len(claims)} quoted claims in REFUTED.md\n")
|
|
hits = 0
|
|
for c in claims:
|
|
needle = c.strip()
|
|
for f in root.rglob("*.md"):
|
|
if f == ref or f.name in CHRONOLOGICAL:
|
|
continue
|
|
lines = f.read_text(errors="replace").splitlines()
|
|
for i, l in enumerate(lines):
|
|
if needle in l:
|
|
lo, hi = max(0, i - CTX), min(len(lines), i + CTX + 1)
|
|
# ⚠️ A +-CTX neighbourhood misses the commonest real marker: a
|
|
# SECTION HEADER that retracts a whole list. Two false positives
|
|
# were exactly this -- bullets under "## 🔴 What this retracts",
|
|
# each bullet a claim being killed, no marker within 4 lines.
|
|
# Scope, not proximity, is what marks them, so include the
|
|
# nearest preceding header in the scan.
|
|
hdr = ""
|
|
for j in range(i, -1, -1):
|
|
if re.match(r"#{1,4} ", lines[j]):
|
|
hdr = lines[j]
|
|
break
|
|
near = "\n".join(lines[lo:hi]) + "\n" + hdr
|
|
if not any(m.lower() in near.lower() for m in MARKERS) and (f, i) not in seen_report:
|
|
seen_report.add((f, i))
|
|
hits += 1
|
|
print(f"🔴 {f}:{i+1}")
|
|
print(f' claim: "{needle[:80]}"')
|
|
print(f" line : {l.strip()[:110]}\n")
|
|
print(f"{hits} unmarked assertion(s) of a refuted claim")
|