fix(docs): grade relative links and cross-file anchors; retarget five broken links #1558
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind
bug
kind
chore
kind
docs
kind
epic
kind
feature
loop/paused
mirror-drift
priority
critical
priority
high
priority
low
priority
medium
size
L
size
M
size
S
size
XL
status
blocked
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit!1558
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/1557-relative-link-gate"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refs #1557
Summary
scripts/anchor-check.pynow grades every relative link in README.md, AGENTS.md, docs/** and .forgejo/**. The target file must exist with that exact case. A fragment on a markdown target must be an id that the target's headings render to, using the same Forgejo renderer call the in-document check already used.tests / checksgain two mutants, a broken cross-file anchor and a link to a missing file. Each must exit 1.no-changelog: toolkit-self CI gate and doc link targets only; no rt, reusable-workflow or adopter-facing behaviour changes
The five links
docs/dead-script-namespace.md:4640001-language-shell.mdadr/0001-language-shell.md. The link sits in an excerpt the ledger quotes from ADR-0004, where it was relative todocs/adr/, so the quoted text now differs from its source by that path prefix.docs/dead-script-namespace.md:4650002-conventional-commits-from-day-one.mdadr/0002-..., for the same reason.docs/adr/0006-...:272AGENTS.md#...-at-v062-...#10-substrate-grounding-arc-converged-at-v0-6-2-2026-06-26(the renderer's id)docs/integration.md:686conventions.md#pre-10-...#pre-1-0-opt-in-pre_v1_breaking_to_minordocs/migration/tmux-tell.md:27integration.md#post-bump-hooks#post-bump-hooks-post_bump_hooks(the heading isPost-bump hooks: post_bump_hooks)Each id was read from the Forgejo renderer, not guessed.
Measured
origin/main(a4aab90), the extended check reports exactly these five as UNRESOLVED and nothing else (rc=1). On this branch: 62 files, 70 in-document anchors (the old script's count too), 246 relative file links (54 of them with a markdown anchor), 0 unresolved, rc=0. The 99 links it does not grade are allhttps:links.tests.ymland run in apython:3.12-slimcontainer against git.frankenbit.de: both exit 0 and write their verdicts. All three mutants and the rc=2 outage control report as expected.[o](Other.md)with onlyother.mdon disk exits 0 on the old script and 1 on the new one.tests/workflows.batsin a container: 131 ok, 1 not ok. The failure is test 83 (mirror path, exit 127, command not found), which comes from the bare bats image missing a tool. This diff does not touch it. The #1451 assertion (exactly one captured-rc anchor invocation) passes, because c7 still exits withexit 1, notexit "$rc".tests.ymlandanchor-check.pyare ASCII-only (AGENTS.md section 11). The old script's non-ASCII punctuation was replaced while it was being rewritten.Decisions
/...links leave the tree, so the tree cannot answer for them. Fragments on non-markdown targets (main.go#L12) are line anchors in the file viewer, not rendered headings. The summary line counts all of these as "not graded".Self-bootstrap (AGENTS.md section 2)
No re-pin: toolkit-self floats
@main(AGENTS.md section 2.1). This PR touches no compose script anyway.tests.ymlis toolkit-self CI and is not ingates.DefaultComposeScripts.Review
@alex: please verify at source, both halves:
FORGEJO_BASE_URL=https://git.frankenbit.de python3 scripts/anchor-check.pyon this head (rc=0) and ona4aab90(rc=1, the five links);tests / checksrun, and check that the two newcontrol OKlines appear for the cross-file anchor and the missing file.Head:
9726eab464fd802f903da4a7d81003714c2b18cb🤖 Generated with Claude Code
Verdict: APPROVE at
9726eab464fd802f903da4a7d81003714c2b18cb. This is posted as COMMENT because the forge identity isclaude, the PR's author, so @alex has to click Approve.Refs #1557
Verified at source
anchor-check.py(live renderer) gives: 62 files, 70 in-document anchors, 246 relative file links (54 with a markdown anchor), 99 not graded, 0 unresolved, rc=0.a4aab90'sdocs/with the new script: rc=1, exactly the five links from #1557, and nothing else.control OKlines are there. That covers the clean fixture, the in-document mutant, the cross-file anchor mutant, the missing-file mutant, and the renderer outage staying rc=2.tests.ymland run in a Linux container against git.frankenbit.de: every control reports as expected.tests/workflows.bats -f anchor: the #1451 single-invocation test passes.a4aab90.](in the selected markdown against whatINLINEcaptures. Only one is missed:dead-script-namespace.md:251, a truncated](ht...inside a quoted excerpt, which is not a real link. No reference-style definitions exist in the tree today.adr/prefix. The body discloses this, and the ledger quotes are not read by any gate (the only reference to the file is a link fromoperations.md). That is acceptable.Non-blocking nits (fine as a follow-up or not at all)
REFDEFalso matches footnote definitions.[^1]: See the note.extractsSeeas a relative file link, so the first footnote anyone adds goes red withno file docs/See. Forgejo renders footnotes. Suggested fix: skip labels that start with^.[a](other.md 'title')and[a](other.md (title))extract nothing. They are neither graded nor counted in "not graded", so the summary's not-graded count undercounts. None are in the tree today, so this fails open without biting.[a](other.md?plain=1)reportsno file other.md?plain=1, which is a false red. None are in the tree.OSErrorfrom reading a file inside the grading loop is mislabelled. It is reported as "renderer stopped answering mid-run". It still fails closed (rc=2), so only the label is wrong.None of these affects today's tree or the ACs.
HOLD: fixing the four nits from review 7267 (footnote definitions, titled links, query strings, OSError label) as a new commit on this branch. Reviewed head stays
9726eab; I will post the new full head SHA when it is pushed.Pushed. New head:
11aba7f75c9daf987f0dc018cff292f135039e47(one new commit on top of the reviewed9726eab, no amend, no force-push). The HOLD is lifted.All four nits from review 7267 are addressed:
9726eab)[^1]: See the other file.no file .../SeeREFDEFskips labels starting with^[a](absent.md 't'),[a](absent.md (t))[a](other.md?plain=1)no file other.md?plain=1dir.md)COULD-NOT-GRADE - could not read <file>, still rc=2The c7 baseline fixture now carries a footnote, all three title forms and a query string. A fifth mutant points a
'sq title'link at a missing file. Run in a Linux container against git.frankenbit.de:control OKlines appear.9726eabscript: the baseline exits 1 on the footnote and the query string.9726eaband 1 on this head.Both files stay ASCII-only. c7 still ends in
exit 1, so the #1451 single-invocation bats assertion is unaffected.Delta to review:
9726eab..11aba7f.Verdict: APPROVE at
11aba7f75c9daf987f0dc018cff292f135039e47. This replaces review 7267, which was at9726eab. It is posted as COMMENT because the forge identity isclaude, the PR's author, so @alex has to click Approve.Refs #1557
Delta
9726eab..11aba7f, verified at source9726eabis an ancestor of11aba7f, so nothing was rewritten. The delta touches onlyscripts/anchor-check.pyandtests.yml(c7).9726eab.9726eabscript: rc=1, with exactly two UNRESOLVED:See(the footnote) andother.md?plain=1(the query string).11aba7f: rc=0.'sq'and(pt)titles: rc=0 on9726eab, rc=1 on11aba7f. The new mutant is not a placebo."dq"title: rc=1 on both, because it was already graded.dir.md): both versions return rc=2. The label is nowcould not read fx\dir.md: ...instead ofrenderer stopped answering mid-run.tests.ymland run in a Linux container against git.frankenbit.de: both exit 0 and writepass. All six controls report OK.control OKlines, including the new's/(other.md 'sq title')/(absent-titled.md 'sq title')/' is reported as rc=1.tests/workflows.bats -f anchor: the #1451 single-invocation test passes. c7 still ends inexit 1, notexit "$rc".Optional, non-blocking
anchor-check.py:184,dest =pathlib.Path(is missing a space after=. This is cosmetic only..mdfile that is not UTF-8 raisesUnicodeDecodeError. That is aValueError, not anOSError, so it escapes both handlers, and Python's traceback exits 1, which c6 labels "NOT MERGE-READY". This was already the case before this PR and nothing in the tree triggers it. If you want it fail-closed as rc=2, addUnicodeDecodeErrorto the could-not-read handler.All four nits from review 7267 are addressed.
Landing identity record
11aba7f75c9daf987f0dc018cff292f135039e4711aba7f75c9daf987f0dc018cff292f135039e47This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.