Skip to content

citations

Ary Rabelo edited this page Jul 22, 2026 · 3 revisions

Have enough to write the page.

Citation Extraction and Linting

Relevant source files

  • src/repodocs/citations.py
  • src/repodocs/gitlinks.py
  • CHANGELOG.md

Overview

repodocs.citations implements the mechanism behind the source-cited guarantee: every generated wiki page must back its prose with [path:La-Lb](href) citations that actually resolve to real files and real line ranges in the target repository. The module provides a warning-level linter (lint_citations), a strict validator used as a publish-time gate (citation_error, citation_problems, enforce_citations), and a repair pass (repair_citations) that fixes a common class of near-miss citation the model produces.

Sources: src/repodocs/citations.py:L1-L14

Citation regexes

Two regexes drive extraction:

Pattern Matches Used for
CITATION_RE [path:La-Lb] (label only) detecting any citation-shaped label, including ones missing a linked href
FULL_CITATION_RE [path:La-Lb](href) (label + link) full validation and repair

_CITE_HREF (in gitlinks.py) matches the href half, path#Lstart(-Lend)?, and explicitly excludes http(s):// targets so rewritten GitHub blob URLs (produced later by rewrite_citation_links) are not mistaken for local citations.

Sources: src/repodocs/citations.py:L12-L14, src/repodocs/citations.py:L38-L38, src/repodocs/gitlinks.py:L14-L14

Warning-level linting: lint_citations

lint_citations(repo, out, pages) scans each page's generated .md file for CITATION_RE matches, resolves the cited path with safe_repo_file (path-traversal/symlink-escape safe resolution), and checks the line count via count_lines. Problems (missing file, out-of-repo path, or a < 1 or a > b or b > n) are collected and printed to stderr as non-fatal warnings; a clean run prints citation lint: ok. This is the older, non-blocking check.

Sources: src/repodocs/citations.py:L15-L35

Strict validation: citation_error

citation_error(repo, label_path, a, b, href) is the authoritative single-citation validator used by both the repair pass and the publish gate. It checks, in order:

  1. The href matches _CITE_HREF (rejects non repo-relative hrefs, e.g. full URLs).
  2. The href's path equals the label's path.
  3. The href's line range equals the label's (a, b) — label and link must agree.
  4. a >= 1 and a <= b (non-inverted, non-zero range).
  5. The path resolves inside the repo via safe_repo_file.
  6. b does not exceed the file's real line count (count_lines).

Any failure returns a human-readable reason string; success returns None. The docstring notes it "never echoes file contents" — only line counts and paths appear in error text.

Sources: src/repodocs/citations.py:L55-L75

EOF overshoot repair: repair_citations

Models frequently cite "to the end of the file" with an off-by-one end line (b = n + 1 or similar), which citation_error would otherwise reject outright. repair_citations(repo, text) runs a regex substitution over FULL_CITATION_RE matches and, for each one:

  1. Skips citations that are already honest (citation_error(...) is None).
  2. Leaves untouched any citation whose path cannot be resolved (safe_repo_file returns None) — unrepairable, left for enforce_citations to block.
  3. Only proceeds if the mismatch is a pure end-of-file overshoot: 1 <= a <= n < b, i.e. the start line is valid but the end line runs past the file's last line n. Any other kind of invalid range (inverted, start past EOF) is left untouched.
  4. Re-parses the href and refuses to guess if the label and href are already out of sync with each other (hm.group(1) != label_path or the href's own start/end don't match a/b) — repair only touches citations where label and href already agree with each other, just not with the file's real length.
  5. Otherwise rewrites both label and href in lockstep, clamping the end line down to n: [path:La-Ln](path#La-Ln).
flowchart TD
    A["FULL_CITATION_RE match: path, a, b, href"] --> B{citation_error is None?}
    B -- yes --> C["leave unchanged, already honest"]
    B -- no --> D{"path resolves via safe_repo_file?"}
    D -- no --> E["leave unchanged, unrepairable"]
    D -- yes --> F{"1 <= a <= n < b ?"}
    F -- no --> G["leave unchanged, not a pure EOF overshoot"]
    F -- yes --> H{"label/href already in sync on a,b?"}
    H -- no --> I["leave unchanged, don't guess"]
    H -- yes --> J["rewrite to [path:La-Ln](path#La-Ln)"]
Loading

This is the fix referenced in the changelog entry "fix(citations): repair EOF line-range overshoot at generation" — repair runs when a page is generated so an otherwise-correct citation with a slightly-too-long end line is clamped rather than failing the publish gate.

Sources: src/repodocs/citations.py:L78-L99

Evidence requirement: requires_evidence

A page "needs" at least one citation once it contains prose beyond its # Title and its ## Relevant source files bullet list. requires_evidence walks the page line by line, tracking whether it is inside the "Relevant source files" section (list items there are exempt), skipping blank lines and Sources: lines, and returns True as soon as it finds any other non-empty content — meaning a bare bullet list in the body (outside that specific section) still triggers the requirement.

Sources: src/repodocs/citations.py:L102-L120

Publish-time gate: citation_problems and enforce_citations

citation_problems(repo, mds) is the blocking check run before publish/publish-wiki. For each page:

  1. HTML comments and code (fenced ``` blocks and inline `code`) are stripped via `_strip_noncounted` first, so a citation hidden inside a comment or code block cannot satisfy the requirement.
  2. Every FULL_CITATION_RE match is validated with citation_error; failures are recorded as problems, successes increment a valid counter.
  3. Every bare CITATION_RE match whose span isn't already covered by a full citation is flagged as "citation label has no linked href" — a label without a link is itself a violation, not just an unlinked-but-otherwise-fine citation.
  4. If no valid citation was found anywhere on the page and requires_evidence says the page needs one, "content page has no valid source citation" is added.

wiki_content_pages(out, subdirs) enumerates the candidate .md files (excluding index.md and the assets/ directory, optionally including one level of translated subdirectories such as out/pt). enforce_citations(repo, out, subdirs, cmd) combines these: it calls citation_problems over wiki_content_pages and, if any problems exist, calls die with up to 12 formatted problem lines (plus a +N more suffix) — aborting the command (publish or publish-wiki) entirely.

Sources: src/repodocs/citations.py:L123-L169

Enforcement history

Per CHANGELOG.md, publish and publish-wiki previously treated invalid or missing citations as warnings only; they now block the command on a cited path that doesn't exist, escapes the repository, has an out-of-range or reversed line range, or carries a label whose path/range disagrees with its link target — the behavior implemented by citation_error and enforced via enforce_citations.

Sources: CHANGELOG.md:L21-L21

Clone this wiki locally