fix(docs-check): validate local Markdown section anchors - #127
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The docs-integrity gate stripped fragments before checking a link
(
target.split("#")[0]) and skipped same-document links entirely, so a linkto an existing Markdown file with a stale or misspelled
#anchorstill countedtoward
link_integrity: 1.000.AGENTS.mdrequires a touched doc to keep bothits 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.mjsnow resolves a local fragment against the headings ofthe resolved target — for same-document links against the source file itself:
hyphens), including the
-1/-2suffixes for duplicate headings.does; explicit
<a id="…">/<a name="…">anchors are honored..mdfileis 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 missinganchor.
Resolves the gap reported by the Snowcat
quality-gap-discoveryroot7a7579ed-5cde-4bca-bbf5-86bd51848e0e(implementation itemae87cf94-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.mjsgreenmake check(=npm run verify:check:docs,check:organization,check:scripts,npm test) green — 43/43 tests pass, up from 39, on theNode 22.23.2 pinned by
mise.tomlreverting
scripts/check-docs.mjsalone and re-running the suite):valid
docs/plans/0002-org-portfolio-roadmap.md -> 0001-docs-shape-rollout.md#phase-4--supply-chain-cleanuppasses, andpointing it at
#phase-9--nopefails with the source link and targetnamed: