-
Notifications
You must be signed in to change notification settings - Fork 1
citations
Now I have enough to write the page.
- src/repodocs/citations.py
- tests/test_fix_citation_repair.py
Every generated wiki page must tie its prose to real locations in the source repository. src/repodocs/citations.py defines the citation format, a validator (citation_error), a deterministic repair pass (repair_citations) that fixes honest-but-malformed citations, and an enforcement gate (enforce_citations) that blocks publishing when a page's claims aren't backed by a valid citation.
Sources: src/repodocs/citations.py:L1-L12
A full citation is a Markdown link whose label and href both encode a path and line range:
<a href="https://github.com/aryrabelo/repodocs/blob/51ba8b18a6abbf0ed1789af067cb1c932993f747/path/to/file.py#L10-L42" target="_blank" rel="noopener">path/to/file.py:L10-L42</a>
Two regexes recognize this shape. CITATION_RE matches the bare label [path:La-Lb], and FULL_CITATION_RE matches the label plus its linked href [path:La-Lb](href). The href itself is parsed by _CITE_HREF (defined in gitlinks.py), which requires a non-http(s) repo-relative path followed by #La[-Lb].
Sources: src/repodocs/citations.py:L12-L12, src/repodocs/citations.py:L38-L38
citation_error(repo, label_path, a, b, href) checks one citation and returns a reason string when it is dishonest, or None when it's valid. It never echoes file contents. The checks, in order:
- The href must match
_CITE_HREF(a repo-relativepath#La[-Lb]anchor). - The label's path must equal the href's path.
- The label's line range must equal the href's line range.
- The range must be non-degenerate (
a >= 1,a <= b). - The cited path must resolve inside the repo via
safe_repo_file(rejects missing files and paths that escape the repo). - The end line
bmust not exceed the file's real line count (count_lines).
Sources: src/repodocs/citations.py:L55-L75
Models sometimes generate citations that are honest in intent but malformed in mechanical ways. repair_citations(repo, text) rewrites the text deterministically, canonicalizing each [label](href) link whose href is a repo-relative line anchor into [path:La-Lb](path#La-Lb). It handles three specific cases:
| Case | Repair |
|---|---|
Label missing the L prefix, or a single-line label (path:3) |
Normalized to path:La-Lb matching the href |
Path doesn't exist but uniquely matches a tracked file by suffix (model dropped a leading src/<pkg>/ prefix) |
Path rewritten to the unique tracked match |
| End line overshoots the file's real length (off-by-one, e.g. citing L6 in a 5-line file) | Clamped to the file's actual line count |
The rewrite only fires when the label and href agree on path and range to begin with (repl bails via lm.group(1) != hm.group(1) or (la, lb) != (ha, hb)), and when resolve() finds a tracked file with the start line still in bounds. Anything ambiguous — path/range disagreement, no unique suffix match, or a start line past EOF — is left untouched for enforce_citations to block later.
resolve(path, a, b) first tries safe_repo_file directly; if that fails, it searches git ls-files output for tracked paths ending in "/" + path and only proceeds if exactly one match exists. It then clamps b to min(b, count_lines(target)) and requires 1 <= a <= end.
Sources: src/repodocs/citations.py:L81-L124
The test suite in tests/test_fix_citation_repair.py exercises each repair case directly: clamping an end-of-file overshoot (test_repair_clamps_end_of_file_overshoot), leaving an already-valid citation untouched, leaving a hallucinated/missing path untouched, leaving a start-past-EOF citation untouched, resolving a dropped src/pkg/ prefix via a unique tracked-file suffix match (and combining that with an overshoot clamp), adding a missing L prefix to a label, normalizing a single-line citation to a range, and leaving a label/href path mismatch untouched as ambiguous.
Sources: tests/test_fix_citation_repair.py:L1-L74
requires_evidence(md_text) decides whether a page needs at least one citation. It walks the page line by line, tracking whether it is inside the ## Relevant source files section. Bullet list items (- / * ) are exempt only while inside that section — a bullet list elsewhere in the body still counts as content requiring a citation. Lines starting with sources: (case-insensitive) are always skipped. The function returns True as soon as it finds any other non-empty content, meaning the page has prose beyond its title and source-file list and therefore needs backing citations.
Sources: src/repodocs/citations.py:L127-L145
citation_problems(repo, mds) scans a list of generated Markdown files and returns (page, citation-or-'-', reason) tuples for every violation. For each file:
- Text is passed through
_strip_noncounted, which removes HTML comments (_HTML_COMMENT_RE), fenced code blocks (_FENCED_CODE_RE), and inline code spans (_INLINE_CODE_RE) — so a citation hidden inside a comment or code sample can't satisfy the gate; only citations in rendered prose count. - Every
FULL_CITATION_REmatch is validated withcitation_error; failures are recorded as problems, successes increment avalidcounter. - Every bare
CITATION_RElabel match that wasn't already covered by a full-citation span is itself flagged as"citation label has no linked href"— a[path:La-Lb]with no linked href is a violation, not just a malformed linked one. - If no valid citation was found anywhere in the page and
requires_evidencesays the page has real content, the page itself is flagged with"content page has no valid source citation".
Sources: src/repodocs/citations.py:L148-L174
wiki_content_pages(out, subdirs) collects the generated *.md pages under the output directory, excluding index.md. With subdirs=True (the default), it also includes one level of subdirectories other than assets/ — this is how translated pages (e.g. out/pt/*.md) are picked up.
Sources: src/repodocs/citations.py:L177-L185
enforce_citations(repo, out, subdirs, cmd) is the blocking gate: it calls citation_problems over wiki_content_pages, and if any problems exist, calls die(...) with a message naming the command (cmd), listing up to 12 page cite -- reason entries, and a (+N more) suffix if there are more. This is what prevents publishing pages whose "source-cited" promise doesn't hold.
Sources: src/repodocs/citations.py:L188-L194
Separately, lint_citations(repo, out, pages) performs a lighter-weight, warning-only check: for each page's output file, it scans for bare CITATION_RE labels (not full linked citations) and reports to stderr any citation whose file doesn't resolve via safe_repo_file, or whose range is out of bounds (a < 1 or a > b or b > n). Unlike enforce_citations, this does not raise or block — it prints "citation lint (warnings):" with details, or "citation lint: ok" if nothing is found.
Sources: src/repodocs/citations.py:L15-L35

Sources: src/repodocs/citations.py:L81-L124, src/repodocs/citations.py:L148-L194
- 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