feat: markdown link checking and doc-to-doc anchors#18
Merged
laulauland merged 9 commits intomainfrom Apr 10, 2026
Merged
Conversation
Two new capabilities documented: 1. Broken link detection — drift lint parses markdown links via tree-sitter and reports BROKEN for targets that don't exist. No lockfile entry needed. 2. Doc-to-doc anchors — lockfile bindings where target is a .md file with optional #Heading fragment. Headings resolved via tree-sitter markdown section nodes, fingerprinted for staleness. Also removes references to @./ inline anchors and frontmatter-based anchors (both being removed from the codebase). Adds Decision 14 (tree-sitter markdown for link checking). Updates CLI examples, JSON schema docs with links array and link_target_not_found reason code, heading anchor kind.
…ntmatter, plain # target parsing)
Unlink deleted files (src/frontmatter.zig, src/scanner.zig) from drift.lock, relink all stale docs to refresh signatures after code changes. Update SKILL.md to remove @./ inline anchor and frontmatter migration references, add doc-to-doc anchor and broken link checking documentation.
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
drift can now detect broken markdown links and track staleness between docs, not just between docs and code.
Two new capabilities:
Broken link detection — during
drift lint, all[text](path.md)links in drift-managed docs are parsed via tree-sitter markdown and checked for file existence. Missing targets are reported asBROKEN. No lockfile entry needed — any relative markdown link is checked automatically.Doc-to-doc anchors — lockfile bindings where the target is a
.mdfile with an optional#heading-slugfragment (e.g.docs/overview.md -> docs/auth.md#authentication sig:...). Headings are resolved via tree-sitter markdown'ssection/atx_headingnodes and fingerprinted for staleness detection — same model as code symbol anchors.Also removes the legacy
@./inline anchor syntax, frontmatter-based anchors (YAML frontmatter,<!-- drift: -->HTML comments),scanner.zig, andfrontmatter.zig. Net result: -658 lines.Changes by commit
Design docs — updated DESIGN.md, DECISIONS.md (new Decision 14), CLI.md, check-json-schema.md with markdown links and doc-to-doc anchor documentation. Removed references to
@./anchors and frontmatter.Remove
@./inline anchors and frontmatter — deletedfrontmatter.zig(1110 lines) andscanner.zig(297 lines). ExtractedanchorFileIdentity's#split logic into a smalltarget.zigmodule. Cleaned uplink.zig,refs.zig,unlink.zig.Add tree-sitter markdown grammar — added
tree-sitter-markdown(block + inline) as a lazy build dependency. Both grammars are separatets.Languageinstances fromMDeiml/tree-sitter-markdown, compiled vialinkGrammars()inbuild.zig.Extract markdown links — rewrote
markdown.zigwith tree-sitter two-pass parsing (block grammar for structure, inline grammar forinline_linknodes viasetIncludedRanges). Extracts relative links, skipping URLs, absolute paths, and fragment-only links.Broken link checking in lint — added
link_target_not_foundreason code,linksarray in JSON output (docs[*].links[*]with target, line, result, reason),links_total/links_brokenin summary. Text mode printsBROKENfor missing link targets. Exit code 1 if any link is broken. Updateddrift.check.v1schema and schema generator.Doc-to-doc anchor heading resolution —
.mdfiles with#fragmenttargets resolve headings via slug matching (GitHub-style: lowercase, non-alphanumeric → hyphens). Lockfile stores slug form to avoid space-tokenization issues in the line parser. Section content fingerprinted via normalized syntax tree hash.drift linkfor doc-to-doc — linkingdocs/a.md docs/b.md#Headingslugifies the heading, validates it exists in the target doc, computes section fingerprint, writes binding. Blanket relink also refreshes doc-to-doc anchors.Tests — integration tests for broken link detection (text + JSON), doc-to-doc anchor staleness (fresh, stale, heading removed),
drift linkfor doc-to-doc targets. Updated payload validation tests for new schema fields.Design decisions
Lockfile stores heading slugs, not raw text — the lockfile parser tokenizes on spaces, so
docs/a.md#Token Validationwould break parsing. Slugified formdocs/a.md#token-validationis a single token. Bothdrift link(write) and lint (match) slugify.Section fingerprinting includes nested subsections — anchoring to an H2 heading fingerprints everything under it until the next H2 or higher. Subsection changes trigger staleness. This is intentional: "the Authentication section changed" includes its children.
Reference links deferred — only
inline_link([text](url)) is extracted for now.full_reference_link([text][ref]) support is a follow-up since it requires building a label→destination map from block-levellink_reference_definitionnodes.Only lockfile-managed docs get link-checked — consistent with drift's model where a "drift doc" is a file with at least one binding in
drift.lock. Repo-wide link checking is a different tool's job.Test plan
zig build testpasses (unit + integration)drift lintreportsBROKENfor docs with dead markdown linksdrift lint --format jsonincludeslinksarray with broken entriesdrift link docs/a.md docs/b.md#Headingcreates lockfile binding with slug and sigdrift lintreportsSTALEwhen anchored heading section content changesdrift lintreportsSTALEwithsymbol_not_foundwhen anchored heading is deleted@./anchors and frontmatter no longer parsed (no regressions in existing tests)