-
Notifications
You must be signed in to change notification settings - Fork 1
citations
Have enough to write the page.
- src/repodocs/citations.py
- src/repodocs/gitlinks.py
- CHANGELOG.md
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
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
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
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:
- The href matches
_CITE_HREF(rejects non repo-relative hrefs, e.g. full URLs). - The href's path equals the label's path.
- The href's line range equals the label's
(a, b)— label and link must agree. -
a >= 1 and a <= b(non-inverted, non-zero range). - The path resolves inside the repo via
safe_repo_file. -
bdoes 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
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:
- Skips citations that are already honest (
citation_error(...) is None). - Leaves untouched any citation whose path cannot be resolved (
safe_repo_filereturnsNone) — unrepairable, left forenforce_citationsto block. - 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 linen. Any other kind of invalid range (inverted, start past EOF) is left untouched. - 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_pathor the href's own start/end don't matcha/b) — repair only touches citations where label and href already agree with each other, just not with the file's real length. - 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)"]
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
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
citation_problems(repo, mds) is the blocking check run before publish/publish-wiki. For each page:
- 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. - Every
FULL_CITATION_REmatch is validated withcitation_error; failures are recorded as problems, successes increment avalidcounter. - Every bare
CITATION_REmatch 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. - If no valid citation was found anywhere on the page and
requires_evidencesays 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
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
- Home
- Installation & Setup
- Architecture
- CLI Reference
- LLM Backend Selection
- Repository Scanning
- Wiki Page Planning
- Page Generation
- Source Citations
- Translation
- HTML Rendering
- Diagram Rendering
- Diagram Poster Tool
- Git Remote Link Resolution
- Publishing
- GitHub Wiki Integration
- Shared Utilities
- Environment Configuration
- Security & Trust Boundaries
- Limitations & Non-goals
- Testing
- Development
- Contributing
- Upgrading
- Changelog