Deterministic drift detection between code and docs — language-agnostic, no LLM in the detection path.
Project description
staledocs
Deterministic drift detection between code and docs. Language-agnostic, no LLM in the detection path.
Documentation language: English (README.ja.md carries the
Japanese version).
The problem
Docs rot silently. You change the code, the doc keeps describing the old behaviour, and nobody notices until the doc misleads a teammate — or an AI agent, which then confidently implements against a stale spec. The fear of that rot is also why people stop writing docs at all: every page is a maintenance debt you have to remember.
staledocs removes the remembering. It pairs every doc with the code it describes, records a fingerprint when you confirm they match, and from then on any one-sided change is flagged mechanically — in either direction.
How it works
Four detection layers; the first three are deterministic, no AI anywhere:
- Pair ledger (L1) — each doc/code pair stores the git blob hashes from
the last time a human (or agent) confirmed coherence (an ack). Code
moved but the doc did not →
DOC_STALE. Doc moved but the code did not →CODE_LAG(unimplemented spec). Both moved in commits that travelled together →AMBER(provisionally coherent). Both moved separately →BROKEN. - Anchor liveness (L2) — docs naturally quote identifiers, CLI flags, and paths in backticks. staledocs extracts those anchors and verifies each one still exists on the paired code side, reporting the exact doc line that rotted. The doc is parsed; the code is only grepped — that is what keeps the tool language-agnostic.
- Coverage gates — every source file must belong to at least one doc, and every doc must be classified (paired, standalone, or global). New files with no owner show up red immediately. Silence is never coverage.
- Semantic reconciliation (L3, external by design) — judging whether
prose still matches behaviour is not a deterministic problem. staledocs
emits a machine-readable report (
check --json) and leaves the fixing to you or your AI agent, which then closes the loop with an ack.
Quick start
pip install staledocs
cd your-repo
staledocs init # scaffold .staledocs.yaml + ledger dir
$EDITOR .staledocs.yaml # declare the pairing (see below)
staledocs check # see what's unowned / unacked
staledocs ack --all # baseline: "everything is coherent as of now"
From then on:
staledocs check # after any change: what broke?
staledocs ack docs/auth.md # confirmed coherent again
Pairing model
CODEOWNERS-style globs, one YAML file:
version: 1
gate: warn # warn (report) | strict (non-zero exit on red)
source:
include: ["src/**"]
docs:
include: ["docs/**/*.md", "README.md"]
pairs:
- doc: docs/auth.md
code: ["src/auth/**"] # a folder
- doc: docs/token.md
code: ["src/auth/token_*.py"] # or a slice of one
mirror: # optional convention: docs/<x>.md <-> src/<x>/**
enabled: true
docs_root: docs
code_roots: [src]
standalone: # docs that intentionally have no code side
- "docs/ops/**"
global: # whole-repo docs: anchors only, no pair ledger
- README.md
Explicit pairs win over the mirror convention. N:M is natural — one file may be owned by several docs, one doc may own several globs.
Acks
An ack is the recorded statement "this pair is coherent right now". Three ways to give one:
staledocs ack docs/auth.md— explicit, after you reconciledstaledocs ack --broken/--all— bulk (refactor days, onboarding)- a
Staledocs-Ack: docs/auth.md(orStaledocs-Ack: all) commit-message trailer — ack in the same breath as the change
Editing code and doc in the same commit is treated as AMBER automatically:
honest "probably coherent, unconfirmed", never a silent green.
The ledger lives in .staledocs/pairs/ as one JSON file per pair and is
meant to be committed. A merge conflict in a ledger entry fails safe: the
entry stops parsing, the pair reverts to unacked, and gets re-checked.
CI and hooks
# GitHub Actions
- run: pip install staledocs
- run: staledocs check --gate strict
# pre-commit hook
staledocs check --gate strict || exit 1
Start with gate: warn while onboarding a brownfield repo, flip to
strict once check is quiet.
AI-agent integration
staledocs check --json is the agent API: every broken pair with its moved
files, every dead anchor with its doc line, every coverage hole. A coding
agent can consume it, decide per finding whether the doc or the code is
wrong, fix that side, and staledocs ack the pair. staledocs supplies the
trustworthy signal; the agent supplies the judgement. See
docs/agent-integration.md.
Design principles (also the non-goals)
- Detection is deterministic. Git blob hashes, commit topology, and anchor grepping. If staledocs says a pair broke, it broke.
- No doc generation. The tool never adds to your documentation burden; it guards what you chose to write.
- No code parsing. No per-language AST, no parser tiers, no silent degradation on language N+1. Works the same for Python, TypeScript, Rust, shell, or anything else.
- No LLM in the detection path. Semantic judgement is the ack's job — yours, or your agent's.
Prior art: the coherence-driven idea owes a nod to CoDD, which attacks the same problem from the generative side. staledocs deliberately takes the opposite bet: detect deterministically, generate nothing.
License
Apache-2.0 (LICENSE). Dependency notices in
THIRD_PARTY_NOTICES.md; vulnerability reporting
in SECURITY.md.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file staledocs-0.1.3.tar.gz.
File metadata
- Download URL: staledocs-0.1.3.tar.gz
- Upload date:
- Size: 32.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78179d2f1c9a64293dcbca8cf0dfe14bde9ec6cb48fd9e7841b8207084d668b9
|
|
| MD5 |
34af736322e388f831d2ac3b7c3a6e79
|
|
| BLAKE2b-256 |
90a2ffc643432caeefcf62fc42e99ebab30374f00ad39b2c90aeaa6ce2e4d848
|
Provenance
The following attestation bundles were made for staledocs-0.1.3.tar.gz:
Publisher:
publish.yml on Synforger/staledocs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
staledocs-0.1.3.tar.gz -
Subject digest:
78179d2f1c9a64293dcbca8cf0dfe14bde9ec6cb48fd9e7841b8207084d668b9 - Sigstore transparency entry: 2155712017
- Sigstore integration time:
-
Permalink:
Synforger/staledocs@b7f03ca4e8c84b5e1355e15666028ecf1c4776dd -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/Synforger
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b7f03ca4e8c84b5e1355e15666028ecf1c4776dd -
Trigger Event:
release
-
Statement type:
File details
Details for the file staledocs-0.1.3-py3-none-any.whl.
File metadata
- Download URL: staledocs-0.1.3-py3-none-any.whl
- Upload date:
- Size: 28.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a65453207ff3435f0d2fab9c352f00df3a082b49a7999a156028e9665563c2af
|
|
| MD5 |
952a8e53c699e1541752acf1fce24469
|
|
| BLAKE2b-256 |
39a5467cc8dd1253ff6777d1d9ba8aff2ef9aaad9cc741e67eb2cc089e7e2455
|
Provenance
The following attestation bundles were made for staledocs-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on Synforger/staledocs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
staledocs-0.1.3-py3-none-any.whl -
Subject digest:
a65453207ff3435f0d2fab9c352f00df3a082b49a7999a156028e9665563c2af - Sigstore transparency entry: 2155712051
- Sigstore integration time:
-
Permalink:
Synforger/staledocs@b7f03ca4e8c84b5e1355e15666028ecf1c4776dd -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/Synforger
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b7f03ca4e8c84b5e1355e15666028ecf1c4776dd -
Trigger Event:
release
-
Statement type: