Skip to main content

linecite

Docs that cite code by line number (orders.py:310) go stale on the next commit that touches the lines above. linecite lets a citation name the code it means — a symbol and a quoted fragment — derives the line number from the source, and tells you which paragraphs to re-read after the code changes.

Locks are taken in product order at [orders.py:310](app/orders.py#L310 "create_order: order_by(\"product_id\")").

That is an ordinary markdown link: it renders as orders.py:310, clicks through to the line on GitHub, and its title says what the line is. When code above it moves, linecite sync rewrites both the #L310 fragment and the :310 in the link text; when the quoted code disappears, linecite check fails.

command what it does
linecite check reports drifted numbers, citations that no longer resolve, and number-only citations; exit 1 if any (CI)
linecite sync rewrites drifted numbers in place (pre-commit), then reports what still needs a human
linecite affected <rev> lists doc lines whose citations point into code changed since <rev> — the prose to re-read
linecite locate <path> <line> proposes a citation for an existing path:line
linecite list / where <spec> inspect what citations resolve to
linecite audit traces existing number-only citations through git history: which ones already point at the wrong line
linecite adopt [--write] converts number-only citations into links (markdown) or anchors (other files)
pip install linecite      # Python 3.11+, git

Run it where your configuration is (see Configuration), locally, as a pre-commit hook, or in GitHub Actions.

Existing docs: audit, then adopt

Docs you already have cite code as orders.py:310 or [orders.py:310](app/orders.py#L310). linecite audit judges them without changing anything. For each citation it asks git when the doc line was written, reads line 310 of the code as it was then, and follows that line to today's code:

docs/design.md:14  ok       orders.py:310                  written against 9b2f41d0c3
docs/design.md:31  stale    orders.py:118 -> orders.py:131  written against 4e1a9c2b07
docs/design.md:40  gone     orders.py:77                   `row.lock()` is gone; written against 4e1a9c2b07
docs/design.md:52  unknown  orders.py:12                   line 12 was blank: too little to identify; written against 4e1a9c2b07

number-only citations 4 · ok 1 · stale 1 · gone 1 · unknown 1 · unverifiable 0

Today's line 310 is never the reference — it holds some code, so judging by it would pass numbers that are already wrong. Where docs and code live in separate repositories, the code is read as of the doc line's date (following the first-parent line of HEAD); lines not committed yet are read against the working tree. The results are estimates: a doc line edited later (a typo fix) is dated by that edit. In a shallow clone (CI checkouts often fetch one commit) lines older than the clone are reported unverifiable — fetch full history.

linecite adopt uses the same trace to propose conversions — a titled link in markdown, an anchor elsewhere — with the number set to where the cited line is now. It writes nothing until --write; every proposal is resolved before it is shown, so converted citations pass check. Citations that are gone, ambiguous or undatable are listed and left alone.

Citation forms

Link (markdown) — a link to a code file with a #L<n> or #L<a>-L<b> fragment and a title:

title meaning
"create_order: row.lock()" the one line in create_order containing row.lock() (whitespace-insensitive)
"OrderService.create_order" the whole symbol, as a range
": xs.reduce" no symbol — for languages without symbol support, cite by fragment only
"OrderService `return`#2" backtick grammar: the 2nd hit; also `a` .. `b` for a range

A link with a #L fragment but no title is reported as legacy: nothing records which code it meant. Links inside fenced code blocks are examples and are not checked.

Anchor (hidden comment) — for HTML, for numbers in running prose, and for comments in code excerpts:

the lock is taken at line 310<!--@ app/orders.py::create_order `order_by("product_id")` -->

The anchor sits right after the number it owns. Its spec grammar:

[@<sha>:]<path>[::<symbol>] [<quote>[#n] [.. <quote>[#n]]]
  • path — suffix of a tracked file; must match exactly one file.
  • symbol — Python: qualified name or a unique suffix of one, or a module-level assignment. YAML: dotted key path.
  • quote — `fragment` (widen to `` when the code holds a backtick) or 「fragment」; #n picks the n-th hit.
  • @sha: — pin to a commit for code that no longer exists; pinned specs may use plain integers.
  • A bare <!--@--> marks a number that is not a code line.

Examples — docs that teach the syntax (a contributing guide, this README) wrap their examples in ignore markers, each on a line of its own and outside code blocks (a marker shown in a code block is an example itself); a marker that pairs with nothing fails check:

<!-- linecite-ignore-start -->
Cite code as [orders.py:310](app/orders.py#L310 "create_order: row.lock()").
<!-- linecite-ignore-end -->

Symbol reference — `orders.py::OrderService.cancel` in prose fails check once the method is gone.

Configuration

.linecite.toml (top-level keys) or [tool.linecite] in pyproject.toml:

code_root = "."                 # git repo of the cited code, relative to this file
docs = ["docs/**/*.md", "README.md"]
number_suffixes = ["`"]         # text allowed between a number and its anchor: `orders.py:12`<!--@ … -->
legacy = "error"                # number-only citations: "error" | "warn" | "off"
ignore_patterns = []            # regexes of regions to skip (the legacy scan also skips fenced code)

pre-commit

repos:
  - repo: https://github.com/gotoUSA/linecite
    rev: v0.1.1
    hooks:
      - id: linecite-sync     # or linecite-check, to report without rewriting

Both hooks read every configured document on every commit, whatever is staged: a commit that touches only code can move the lines a doc cites. linecite-sync rewrites drifted numbers and pre-commit stops the commit so you can stage the rewrite; citations whose code is gone still fail it.

GitHub Action

on: pull_request
permissions:
  contents: read
  pull-requests: write          # for the comment
jobs:
  linecite:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: gotoUSA/linecite@v0.1.1

The job fails when linecite check does. On a pull request, the action also comments with the doc lines whose cited code the pull request changed — one comment per use of the action, rewritten on every push, so a paragraph a later push made irrelevant drops off the list. The job summary always carries the full report; a failure to comment (pull requests from forks get a read-only token) is a warning, not a failed job.

input default
check true run linecite check and fail on its findings
comment true comment on pull requests
base the pull request's base commit revision the changes are measured from
working-directory . where the configuration is
config configuration file, if not the default
github-token github.token needs pull-requests: write

The output affected is the number of doc citations pointing into changed code.

Shallow checkouts. actions/checkout fetches a single commit by default. affected needs only the base commit's files, which the action fetches itself; audit needs the history that dated every doc line, so run it after actions/checkout with fetch-depth: 0 — in a shallow clone it reports old lines as unverifiable instead of guessing.

Other CI systems can post the same report: linecite affected origin/main --format markdown --link-base https://example.com/owner/repo/blob/<sha> prints it as markdown with links to the doc lines.

License

MIT

Release files for linecite 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for linecite 0.1.1
File Size Uploaded
linecite-0.1.1.tar.gz 55.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for linecite 0.1.1
File Interpreter ABI Platform
linecite-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 95.4 kB

Release files / linecite-0.1.1.tar.gz

Download URL linecite-0.1.1.tar.gz
Size 55.5 kB
Tags Source
SHA-256 checksum
How to use checksums
d77474b21d6b36634970a223fd16b814e417f6f7f6b2a4411a7379982f498104
BLAKE2b-256 checksum
How to use checksums
2fe6835a44a9fc58ed1b95a1479fc90b5363535d58c54fcf5af12a8dd5867867
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / linecite-0.1.1-py3-none-any.whl

Download URL linecite-0.1.1-py3-none-any.whl
Size 39.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3dd77b2e0a9b2f334ba9258c099df858110def5dcf8115b9505f4b468bd0f3fa
BLAKE2b-256 checksum
How to use checksums
b9f86cfdafc2a59219fb95d9f0423d4ee5da3f4c6a1bfb3dbc0cedeb23bfb97a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page