Skip to content

fix(docs-check): validate local Markdown section anchors - #127

Merged
bketelsen merged 2 commits into
mainfrom
quality-gap/docs-check-anchors
Aug 28, 2026
Merged

fix(docs-check): validate local Markdown section anchors#127
bketelsen merged 2 commits into
mainfrom
quality-gap/docs-check-anchors

Conversation

@bketelsen

Copy link
Copy Markdown
Contributor

Summary

The docs-integrity gate stripped fragments before checking a link
(target.split("#")[0]) and skipped same-document links entirely, so a link
to an existing Markdown file with a stale or misspelled #anchor still counted
toward link_integrity: 1.000. AGENTS.md requires a touched doc to keep both
its targets and its section anchors valid; only the first half was enforced,
so a heading rename could silently break navigation with the gate still green.

scripts/check-docs.mjs now resolves a local fragment against the headings of
the resolved target — for same-document links against the source file itself:

  • GitHub-style heading slugs (lowercase, punctuation dropped, spaces to
    hyphens), including the -1/-2 suffixes for duplicate headings.
  • Headings inside fenced code blocks are stripped, as link scanning already
    does; explicit <a id="…"> / <a name="…"> anchors are honored.
  • Fragments are percent-decoded and case-folded before matching.
  • Only Markdown targets are anchor-checked, so a fragment on a non-.md file
    is left alone.

Path resolution, skill-tree containment, index coverage, symlink resolution,
and the pinned-heading check are untouched. Precedence is preserved: an
unresolvable path is still reported as does not resolve, never as a missing
anchor.

Resolves the gap reported by the Snowcat quality-gap-discovery root
7a7579ed-5cde-4bca-bbf5-86bd51848e0e (implementation item
ae87cf94-6fee-4b69-b7e3-f572ed0fd32d). No issue to close.

Risk tier

Risk tier: 1 — repository-local CI gate script and its hermetic tests; no
workflow, no published contract, no runtime or organization data touched.

Docs housekeeping

No docs touched — the change is limited to the gate and its tests, so the
docs-housekeeping rows do not apply.

Verification

  • node scripts/check-docs.mjs green
ok   docs_index_coverage: 1.000 (required 1)
ok   link_integrity: 1.000 (required 1)
ok   symlink_resolution: 1.000 (required 1)
checked: 58 docs, 429 links, 10 symlinks
  • make check (= npm run verify: check:docs, check:organization,
    check:scripts, npm test) green — 43/43 tests pass, up from 39, on the
    Node 22.23.2 pinned by mise.toml
# tests 43
# pass 43
# fail 0
  • The two new negative tests fail against the unpatched gate (checked by
    reverting scripts/check-docs.mjs alone and re-running the suite):
not ok 7 - docs gate rejects a same-document link to a nonexistent section anchor
not ok 8 - docs gate rejects a cross-document link to a nonexistent section anchor
  • Behavioral spot-check on the repository's one real anchored link — the
    valid docs/plans/0002-org-portfolio-roadmap.md -> 0001-docs-shape-rollout.md#phase-4--supply-chain-cleanup passes, and
    pointing it at #phase-9--nope fails with the source link and target
    named:
FAIL link: docs/plans/0002-org-portfolio-roadmap.md -> 0001-docs-shape-rollout.md#phase-9--nope has no matching section anchor in docs/plans/0001-docs-shape-rollout.md
FAIL link_integrity: 0.998 (required 1)
  • Scaffold changed? — not changed
  • Workflows changed? — not changed

bketelsen and others added 2 commits August 27, 2026 21:26
The docs-integrity gate stripped fragments before checking a link, so a
link to an existing file with a stale or misspelled #anchor counted as
healthy link integrity and same-document anchors were skipped entirely.
AGENTS.md requires touched docs to keep both targets and section anchors
valid; nothing enforced the second half.

Resolve fragments against GitHub-style heading slugs in the resolved
target (same-document links included), honoring duplicate-heading
suffixes, explicit HTML id/name anchors, and fenced-code stripping. Path
resolution, skill containment, index coverage, and symlink checks are
unchanged, and an unresolvable path is still reported as such before its
fragment is considered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UigLsmd17TVDuaaSN4yfdU
Review round 1 on #127 blocked defect:scripts/check-docs.mjs:tilde-fence-
heading-counted: anchorsOf() removed fenced code with a backtick-only
regular expression, so a heading inside a CommonMark tilde fence became a
real anchor and a link to it passed even though GitHub renders no such
section. The same regular expression also mismatched across a stray ```
line inside a tilde fence, silently swallowing the real headings between
it and the next backtick fence.

Replace the regular expression with a line-oriented stripFences() that
follows CommonMark: up to three leading spaces, three or more backticks
or tildes, closed only by at least as many of the same character (or by
the end of the document), with a backtick fence's info string barred from
containing a backtick. Fenced lines are blanked rather than removed so
the surviving heading regular expression keeps its line anchoring.

Two fixture tests cover it: a link to a heading that exists only inside a
~~~ block now fails with the missing-anchor diagnostic, and real headings
before, between, and after mixed backtick and tilde fences still resolve.
Both fail against the previous script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UigLsmd17TVDuaaSN4yfdU
@bketelsen
bketelsen marked this pull request as ready for review August 28, 2026 02:10
@bketelsen
bketelsen added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit 44dfd0b Aug 28, 2026
3 checks passed
@bketelsen
bketelsen deleted the quality-gap/docs-check-anchors branch August 28, 2026 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant