bug(docs): five broken relative links on main, and the anchor gate grades in-document anchors only #1557

Closed
opened 2026-10-06 00:58:57 +02:00 by claude · 1 comment
Member

Symptom

A repo-wide relative-link and anchor check, run on 2026-10-04 while splitting ADR-0013 (PR #1544), found broken links in files that PR did not touch. Re-measured on live origin/main at a4aab90 (2026-10-06). The anchors were graded with scripts/anchor-check.py's own renderer call (Forgejo POST /api/v1/markdown), not by slug guessing.

# Source Link Measured
1 docs/dead-script-namespace.md:464 0001-language-shell.md resolves to docs/0001-language-shell.md, which does not exist. The file is docs/adr/0001-language-shell.md. The link sits in an excerpt quoted from ADR-0004, where it was relative to docs/adr/.
2 docs/dead-script-namespace.md:465 0002-conventional-commits-from-day-one.md same shape: the file is under docs/adr/.
3 docs/adr/0006-rna-integration-design-note.md:272 ../../AGENTS.md#10-substrate-grounding-arc-converged-at-v062-2026-06-26 the renderer emits 10-substrate-grounding-arc-converged-at-v0-6-2-2026-06-26 (the dots in v0.6.2 become hyphens).
4 docs/integration.md:686 conventions.md#pre-10-opt-in-pre_v1_breaking_to_minor the renderer emits pre-1-0-opt-in-pre_v1_breaking_to_minor.
5 docs/migration/tmux-tell.md:27 ../integration.md#post-bump-hooks the heading is Post-bump hooks: post_bump_hooks, which renders post-bump-hooks-post_bump_hooks.

Why CI let them through

Measured by the reviewer on PR #1544 (reviews 7220 and 7229):

  • The #1451 anchor gate (scripts/anchor-check.py, step c6 of tests / checks) extracts only ](#...) links. A cross-file anchor such as Y.md#missing-heading is never graded, and a link to a missing file is never graded either (mutant M2 in review 7229 stayed rc=0).
  • TestCitedDocPathsResolve scans .go files only, so a broken relative link in markdown stays green (review 7220).

So no gate on main grades a relative file link or a cross-file anchor in markdown.

Plan

Extend scripts/anchor-check.py rather than add a new gate. It already has the renderer, the could-not-grade control and its CI step (c6) plus controls (c7). The extension:

  • resolves every relative link in the selected markdown (README.md, AGENTS.md, docs/, .forgejo/) against the linking file's directory, and refuses a target that does not exist (case-exact, since the forge is case-sensitive and a Windows checkout is not);
  • for a .md target with a fragment, renders that file's headings and refuses an anchor it does not emit;
  • keeps rc 0/1/2 as today.

The c7 controls gain two mutations, so no new step and no new verdict id is needed.

Acceptance criteria

  • The five links above are retargeted (or dropped if the target is truly gone), each disposition stated in the PR. — DONE @ #1558 (11aba7f, reviewed as 11aba7f): all five retargeted, none dropped; dispositions in the PR body's table.
  • scripts/anchor-check.py grades relative file links and cross-file anchors in README.md, AGENTS.md and docs/**/*.md, and is clean on the PR's tree. — DONE @ #1558 (11aba7f, reviewed as 11aba7f): 0 unresolved on the merged tree (62 files, 246 relative file links, 54 cross-file anchors).
  • Mutation-verified: a broken cross-file anchor turns it red (rc=1), and a link to a missing file turns it red (rc=1). Both run as CI controls in tests / checks. — DONE @ #1558 (11aba7f, reviewed as 11aba7f): c7 runs both mutants, plus a titled-link mutant from review 7267. Each exits 0 on the a4aab90 script and 1 on the merged one.
  • Workflow YAML and the script stay ASCII-only (AGENTS.md section 11). — DONE @ #1558 (11aba7f, reviewed as 11aba7f): both files carry 0 non-ASCII bytes.
  • If a new step is added to tests / checks, it writes its own verdict and is declared in GATE_IDS and both guard steps (the #1540 guards). — DONE @ #1558 (11aba7f, reviewed as 11aba7f): not triggered. The mutants were folded into c7, so GATE_IDS and both guard steps are unchanged.
## Symptom A repo-wide relative-link and anchor check, run on 2026-10-04 while splitting ADR-0013 (PR #1544), found broken links in files that PR did not touch. Re-measured on live `origin/main` at `a4aab90` (2026-10-06). The anchors were graded with `scripts/anchor-check.py`'s own renderer call (Forgejo `POST /api/v1/markdown`), not by slug guessing. | # | Source | Link | Measured | |---|---|---|---| | 1 | `docs/dead-script-namespace.md:464` | `0001-language-shell.md` | resolves to `docs/0001-language-shell.md`, which does not exist. The file is `docs/adr/0001-language-shell.md`. The link sits in an excerpt quoted from ADR-0004, where it was relative to `docs/adr/`. | | 2 | `docs/dead-script-namespace.md:465` | `0002-conventional-commits-from-day-one.md` | same shape: the file is under `docs/adr/`. | | 3 | `docs/adr/0006-rna-integration-design-note.md:272` | `../../AGENTS.md#10-substrate-grounding-arc-converged-at-v062-2026-06-26` | the renderer emits `10-substrate-grounding-arc-converged-at-v0-6-2-2026-06-26` (the dots in `v0.6.2` become hyphens). | | 4 | `docs/integration.md:686` | `conventions.md#pre-10-opt-in-pre_v1_breaking_to_minor` | the renderer emits `pre-1-0-opt-in-pre_v1_breaking_to_minor`. | | 5 | `docs/migration/tmux-tell.md:27` | `../integration.md#post-bump-hooks` | the heading is `Post-bump hooks: post_bump_hooks`, which renders `post-bump-hooks-post_bump_hooks`. | ## Why CI let them through Measured by the reviewer on PR #1544 (reviews 7220 and 7229): - The #1451 anchor gate (`scripts/anchor-check.py`, step c6 of `tests / checks`) extracts only `](#...)` links. A cross-file anchor such as `Y.md#missing-heading` is never graded, and a link to a missing file is never graded either (mutant M2 in review 7229 stayed rc=0). - `TestCitedDocPathsResolve` scans `.go` files only, so a broken relative link in markdown stays green (review 7220). So no gate on main grades a relative file link or a cross-file anchor in markdown. ## Plan Extend `scripts/anchor-check.py` rather than add a new gate. It already has the renderer, the could-not-grade control and its CI step (c6) plus controls (c7). The extension: - resolves every relative link in the selected markdown (README.md, AGENTS.md, docs/**, .forgejo/**) against the linking file's directory, and refuses a target that does not exist (case-exact, since the forge is case-sensitive and a Windows checkout is not); - for a `.md` target with a fragment, renders that file's headings and refuses an anchor it does not emit; - keeps rc 0/1/2 as today. The c7 controls gain two mutations, so no new step and no new verdict id is needed. ## Acceptance criteria - [x] The five links above are retargeted (or dropped if the target is truly gone), each disposition stated in the PR. — DONE @ #1558 (11aba7f, reviewed as 11aba7f): all five retargeted, none dropped; dispositions in the PR body's table. - [x] `scripts/anchor-check.py` grades relative file links and cross-file anchors in README.md, AGENTS.md and docs/**/*.md, and is clean on the PR's tree. — DONE @ #1558 (11aba7f, reviewed as 11aba7f): 0 unresolved on the merged tree (62 files, 246 relative file links, 54 cross-file anchors). - [x] Mutation-verified: a broken cross-file anchor turns it red (rc=1), and a link to a missing file turns it red (rc=1). Both run as CI controls in `tests / checks`. — DONE @ #1558 (11aba7f, reviewed as 11aba7f): c7 runs both mutants, plus a titled-link mutant from review 7267. Each exits 0 on the a4aab90 script and 1 on the merged one. - [x] Workflow YAML and the script stay ASCII-only (AGENTS.md section 11). — DONE @ #1558 (11aba7f, reviewed as 11aba7f): both files carry 0 non-ASCII bytes. - [x] If a new step is added to `tests / checks`, it writes its own verdict and is declared in GATE_IDS and both guard steps (the #1540 guards). — DONE @ #1558 (11aba7f, reviewed as 11aba7f): not triggered. The mutants were folded into c7, so GATE_IDS and both guard steps are unchanged.
Author
Member

Landed on main via PR 1558 as a fast-forward:

  • 9726eab grades relative links and cross-file anchors, and retargets the five links.
  • 11aba7f fixes the four nits from review 7267.

Landed vs reviewed: git range-diff a4aab90..11aba7f a4aab90..origin/main shows both commits as identical (=), and the trees match.

Approval: alex, review 7269, on 11aba7f75c9daf987f0dc018cff292f135039e47. The verify-at-source verdicts from the claude identity are reviews 7267 (on 9726eab) and 7268 (on 11aba7f).

Checks: the push run on main at 11aba7f has 30 of 30 statuses success, including tests / checks, which runs the extended gate (c6) and its controls (c7).

All five acceptance criteria are ticked above. The head branch is deleted. Closing this by hand.

Not done here (non-blocking nits from review 7268):

  • scripts/anchor-check.py:184 is missing a space after =.
  • A non-UTF-8 .md file exits 1 with a traceback instead of could-not-grade. This predates the PR, and nothing in the tree triggers it.
Landed on main via PR 1558 as a fast-forward: - `9726eab` grades relative links and cross-file anchors, and retargets the five links. - `11aba7f` fixes the four nits from review 7267. Landed vs reviewed: `git range-diff a4aab90..11aba7f a4aab90..origin/main` shows both commits as identical (`=`), and the trees match. Approval: alex, review 7269, on `11aba7f75c9daf987f0dc018cff292f135039e47`. The verify-at-source verdicts from the claude identity are reviews 7267 (on 9726eab) and 7268 (on 11aba7f). Checks: the push run on main at `11aba7f` has 30 of 30 statuses success, including `tests / checks`, which runs the extended gate (c6) and its controls (c7). All five acceptance criteria are ticked above. The head branch is deleted. Closing this by hand. Not done here (non-blocking nits from review 7268): - `scripts/anchor-check.py:184` is missing a space after `=`. - A non-UTF-8 `.md` file exits 1 with a traceback instead of could-not-grade. This predates the PR, and nothing in the tree triggers it.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
frankenbit/release-toolkit#1557
No description provided.