Files
Sylpheed/tools/port/check-citations
MechaCat02 25bfa1553f
Some checks failed
CI / Native — ubuntu-latest (pull_request) Failing after 10m46s
CI / WASM — Web (pull_request) Failing after 9m2s
CI / Formatting (pull_request) Failing after 46s
CI / Native — macos-latest (pull_request) Has been cancelled
CI / Native — windows-latest (pull_request) Has been cancelled
protocol: findings before citing code, and checks that were kind once
Two rules that look unrelated and are one failure, plus the change that makes
the second enforceable.

1. A FINDING REACHES `main` BEFORE THE CODE THAT CITES IT. A citation resolving
   only on a peer branch is dead the moment it merges. Not hypothetical: 495
   decoder and 366 port commits sit off `main`, and `port/scripts/boot.gd`
   already cites two docs/re pages present on neither its own branch nor main.

2. A CHECK MAY ONLY SOFTEN AGAINST A CONDITION IT CAN TEST -- the Pi agent's
   wording, and better than mine, because it is applicable while writing rather
   than a call to be vigilant. The mechanical form:

       Can this branch tell the difference between "not yet" and "no longer"?

   `gitea-protect --verify` printed  "not a collaborator (yet)" and continued,
   so the only instrument checking Write-not-Admin could not report that gate
   being REMOVED. `check-citations` reported peer citations instead of failing
   them, because under the old topology that was unfixable from the container.
   Both were correct AND kind when written; neither recorded that the kindness
   had a scope. Nobody edits these into being wrong -- the world moves and the
   allowance stays, which is why they survive review. The smell is leniency with
   an expiry date nobody set; the fix is the testable-condition rule.

check-citations gains `--for-merge`, which turns the peer class into a failure.
A flag rather than a new default because BOTH readings are still live: mid-work
on a topic branch the peer class really is unfixable noise. What the old code
could not express is where the code is GOING, and that is a condition the caller
can state. Measured on this tree: 19 citations resolve only on a peer branch --
which is the size of the #7-depends-on-#8 edge, not the 2 I had counted in
boot.gd.

The selftest gains that third class, because a flag whose classification is
unexercised is the shape this rule exists to catch. Controlled: emptying
PEER_REFS makes the peer case collapse into "nowhere" and the selftest reports
🔴 BROKEN, rc=2.

⚠️ Pre-existing and NOT from this change: the default run already exits 1 on 4
citations of `export/...` paths. Those are the generated tree, gitignored by
design, and main's copy of the tool fails identically. The CITE regex treats
`export/` as a repo prefix. Reported, not fixed -- it is the port's file and its
call whether the regex or the citations are wrong.
2026-09-04 18:27:39 +02:00

145 lines
6.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Do the repo paths cited in `docs/port/*.md` actually resolve?
tools/port/check-citations # assert
tools/port/check-citations --selftest # can it fail?
`audit-kinds` checks citations in `authored/`. Nothing checked the PROSE, and
prose is where this port explains itself. A first run found **37 of 91**
non-resolving, 41 %, in two very different classes:
* **19 on the Decoder's topic branch** — real files, not merged here. Not
errors. A reader in this checkout still cannot follow them, which is worth
reporting and not worth failing on; the fix is a merge, not an edit.
* **7 that resolve NOWHERE** — `docs/BLOCKED.md`, `docs/FORMAT.md`,
`port/manifest.json`, `port/screens/title/*.json`. Left behind by the
monorepo move and the `export/` rename. Those are simply wrong: a reader
following one gets nothing, and nothing had ever told anyone.
So the two classes are separated and only the second fails. A check that failed
on the first would be red for a state nobody in this container can fix, which is
the shape the display guard exists to prevent.
⚠️ THE PEER-BRANCH CLASS IS THE OTHER AGENT'S POINT, TURNED ON MYSELF. They
observed that everything they hand over links into `docs/re/` files that live
only on their branch, so every link they send is dangling from here. The same is
true in reverse and neither of us was counting.
"""
import os
import re
import subprocess
import sys
import glob
# A repo path with a file extension, optionally in backticks or a markdown link.
CITE = re.compile(
r"`?((?:docs|crates|port|tools|authored|export)/[\w./-]+"
r"\.(?:md|rs|gd|json|txt|py|tsv|csv))`?"
)
PEER_REFS = ("origin/auto/frame-blend-draw-path", "origin/main")
def on_a_ref(path: str) -> str | None:
"""The first ref that carries `path`, or None."""
for ref in PEER_REFS:
if subprocess.run(["git", "cat-file", "-e", f"{ref}:{path}"],
capture_output=True).returncode == 0:
return ref
return None
def scan(files):
resolves, peer, nowhere = 0, {}, {}
for p in files:
try:
text = open(p, encoding="utf-8").read()
except OSError:
continue
for m in sorted(set(CITE.findall(text))):
if os.path.exists(m):
resolves += 1
elif (ref := on_a_ref(m)):
peer.setdefault(m, (p, ref))
else:
nowhere.setdefault(m, p)
return resolves, peer, nowhere
def main() -> int:
if "--selftest" in sys.argv:
# 🔴 A CHECK THAT CANNOT FAIL IS NOT A CHECK. This plants a citation of a
# path that exists on no ref and requires the scanner to catch it, and a
# citation of a real file and requires it NOT to. Both directions,
# because a scanner that flagged everything would also "pass" the first.
tmp = os.path.join(os.environ.get("TMPDIR", "/tmp"), "check-citations-selftest")
os.makedirs(tmp, exist_ok=True)
bad = os.path.join(tmp, "bad.md")
open(bad, "w").write("see `docs/port/this-file-does-not-exist-anywhere.md`\n")
good = os.path.join(tmp, "good.md")
open(good, "w").write("see `docs/port/PORT-MISSION.md`\n")
# The THIRD class, which `--for-merge` turns into a failure. It has to be
# told apart from both others: a peer citation is not dangling (the file
# exists) and does not resolve here (the reader still gets nothing), and
# a scanner that collapsed it into either would make the flag meaningless
# while still passing the two checks above.
peerfile = os.path.join(tmp, "peer.md")
open(peerfile, "w").write("see `docs/re/f5-a-press-snaps-the-plate.md`\n")
rp, pp, np_ = scan([peerfile])
_, _, nb = scan([bad])
r, _, ng = scan([good])
caught = len(nb) == 1
passed = len(ng) == 0 and r == 1
peer_ok = len(pp) == 1 and rp == 0 and len(np_) == 0
ok = caught and passed and peer_ok
print("selftest: planted dangling caught=%s, real citation passed=%s, "
"peer-branch classed separately=%s -> %s"
% (caught, passed, peer_ok, "ok" if ok else "🔴 BROKEN"))
if not peer_ok:
print(" 🔴 --for-merge cannot mean anything if the peer class is "
"not distinguished; got resolves=%d peer=%d nowhere=%d"
% (rp, len(pp), len(np_)))
return 0 if ok else 2
files = sorted(glob.glob("docs/port/*.md"))
resolves, peer, nowhere = scan(files)
total = resolves + len(peer) + len(nowhere)
print("citations of repo paths in docs/port/*.md: %d" % total)
print(" resolve here : %d" % resolves)
# 🔴 --for-merge TURNS THE PEER CLASS INTO A FAILURE.
#
# Reporting-not-failing was right when it was written: a peer-branch
# citation was "a state nobody in this container can fix", so failing on it
# would have been red for something unactionable. Under the pull-request
# workflow that stopped being true -- a PR into `main` is EXACTLY where it
# becomes fixable, by opening the finding's PR first and depending on it.
# The citation is dead the moment this merges, so the merge is the last
# place the leniency can still be withdrawn.
#
# Left as a flag rather than made unconditional, because both readings are
# still live: mid-work on a topic branch the peer class really is unfixable
# noise. The difference the old code could not express is WHERE the code is
# going, and that is a condition the caller can state.
merging = "--for-merge" in sys.argv
label = "🔴 FAILS (--for-merge)" if merging else "reported, not failed"
print(" on a peer branch, not merged: %d (%s)" % (len(peer), label))
for m, (src, ref) in sorted(peer.items()):
print(" %-52s %s <- %s" % (m, ref.split("/")[-1], os.path.basename(src)))
if peer and merging:
print("\n🔴 %d citation(s) resolve only on a peer branch." % len(peer))
print(" After this merges they resolve NOWHERE -- the reader gets a dead")
print(" path. Land the finding first and make it a dependency of this PR.")
return 1
if nowhere:
print(" 🔴 resolve NOWHERE : %d" % len(nowhere))
for m, src in sorted(nowhere.items()):
print(" %-52s <- %s" % (m, os.path.basename(src)))
print("\n🔴 a reader following those gets nothing. Fix the path or drop the citation.")
return 1
print(" 🔴 resolve nowhere : 0")
return 0
if __name__ == "__main__":
raise SystemExit(main())