Why Ascribe
Teams that keep docs as code, in a repository beside what they document, report the same few problems. This page says what they are, what Ascribe does about each, and what it doesn’t do.
What teams report, and what Ascribe does
The problems come from research into what documentation teams report: surveys, the records of open-source projects, and the accounts of people who write and review docs as code. The answers are what Ascribe does today, and what’s in main and in no release yet is marked.
Examples stop matching the code
Keeping documentation in step with the product is the challenge surveys of documentation teams name most often, and engineers name it more often than writers do. Keeping the docs in the code’s repository doesn’t settle it: a study of the 1,000 most popular projects on GitHub found that more than a quarter had an outdated reference to code in their README or wiki. The fixes developers say they trust are structural ones, such as examples taken from code that’s tested.
Ascribe reads a code example from the code’s own file with @snippet, every time it checks or builds a page, so there’s no copy to fall behind, and ascribe check reports an example whose file or region is gone. When a pull request changes code, ascribe drift lists the pages whose examples changed, so someone rereads the words around them:
compared with main (94c2212), from its merge base with HEAD (8a43d4b)
Examples that no longer resolve. `ascribe check` reports them too:
guides/install.md
code:service/client.py#connect: the file has no region `connect`
Examples that changed. The page shows the new code; check the words around it:
reference/content-model.md
code:examples/quill/ascribe.toml#dimensions (+3 −1)
Examples that changed along with the page:
guides/review.md
code:examples/quill/ascribe.toml#builds (+1 −0)A review shows lines, not the page
On a pull request, a reviewer can read the rendered page or comment on a line of its source, but not both, and people have built browser extensions to work around it. Reading prose as lines has costs teams describe: text that moved reads as text that was deleted, and a small edit to a wrapped paragraph shows as a change to all of it. Writing with AI adds to the load. Teams that use it report spending more time checking and editing what it writes, so more of the work is now reading changes.
Ascribe compares pages as readers will see them, not lines. Review shows a pull request’s changes on the rendered page, in VS Code’s preview or in the site’s own, with what changed marked and the pull request’s review comments beside the blocks they’re about. Reformatting isn’t a change, a move is shown as a move, and a page that changed only through a fragment it includes is listed with the cause.
Here is the site’s preview of one of these docs’ pages, with a pull request’s change and the comments on it:

ascribe diff writes the same comparison as text, or as an HTML file that CI can attach to the pull request:
compared with main (375192d), from its merge base with HEAD (e49444a)
getting-started.md: 1 changed, 1 added (through _fragments/prereqs.md), in every build
site: 3 more pages changed
guides/rollouts.md: 4 changed, 5 added, 2 removed, 1 moved
guides/schedules.md: added
reference/limits.md: title changedAn AI coding agent can run the checks you run. ascribe agents sync gives it the project’s rules and the check to run after each edit, so its change reaches review with its links, names, and examples already checked.
One page, copied for each version
Teams that keep a copy of their docs for each version of the product report what it costs: authors edit the wrong copy, and a fix made in one has to be carried to each of the others, often as a pull request per version. Most documentation markup has no way to say that a section applies from version 3.4 on, which is what would let one copy serve them all.
Ascribe keeps one copy. A section says where it’s available, as @available: cloud, self-managed preview 3.4 does, and content that differs by platform, product, or deployment is marked once, with @variant. Each build is a selection from the same pages: the site can show everything, with badges and tabs, while another build keeps only what one deployment or version has. These are the builds of an example project:
[builds.site]
variants = "switch"
availability = "badge"
[builds.cloud]
variants = { deployment = "cloud" }
availability = { filter = "cloud" }
[builds."self-managed-3.5"]
variants = { deployment = "self-managed", pm = ["npm", "pnpm"] }
availability = { filter = "self-managed 3.5" }ascribe check reports a version or a value the content model doesn’t declare, and a section that says it’s available somewhere its page isn’t.
A link breaks when a page moves
When docs are reorganized, a page’s old address goes away. Links from other pages, from search results, from the product, and from what AI tools learned before the move still point at it. Of the docs tools the research looked at, one creates a redirect when a page moves.
In Ascribe, a link names the page’s file, not its address. A link to a file or a heading that isn’t there is an error, in the editor and in CI, and moving or renaming a file in VS Code updates the links to it. A link written as an address is reported, with the file to link to instead:
[ASC041] Warning: this looks like the published route of `install-agent.md`; link to the file instead: `/install-agent.md`
╭─[ docs/keys.md:8:15 ]
│
8 │ See [Install](/install-agent/).
│ ───────┬───────
│ ╰───────── link-route
│
│ Help: Link to the page's file instead of its route
───╯
checked 8 files for every build, 1 with problems: 0 errors, 1 warning, 0 adviceIn VS Code, the warning is on the link as you type, with a quick fix that makes the change:

For links from outside the docs, a page or a heading can have a permalink: a name, at an address that leads to it wherever it moves. With the list of permalinks committed, ascribe check warns when one goes away. Ascribe’s command and editor link to these docs by permalink, which is how the docs moved to /docs/ without breaking those links.
Pages that are code, and a site to keep
Teams that build their own docs site describe its upkeep as work nobody is funded for. Site generators stop releasing or change their plugin systems, sites move from one generator to the next, and where a page is compiled as code, as MDX pages are, an ordinary brace or angle bracket in a sentence can stop the build.
An Ascribe page isn’t code. A directive is a line that starts with @, and there’s no JSX, so a brace or an angle bracket in a sentence doesn’t stop the build: at most, a {name} the project doesn’t declare gets a warning. The pages read as Markdown without Ascribe, and the plain output is ordinary CommonMark, so leaving Ascribe means building once more, not rewriting. Ascribe is a dependency too, and a young one. What it lowers is the cost of leaving, not the risk of depending.
What it doesn’t do
- No hosting. Ascribe builds files, and the site is served by whatever serves it now. Review comments are the pull request’s own, on GitHub, so there’s no service of Ascribe’s to sign in to or to run.
- No model. Ascribe calls no AI model and holds no key for one. Its checks are rules, with the same answer on every run, and an agent you already use runs them.
- No check that the words are true. A page that’s well formed and says something false passes every check.
ascribe driftsays which pages to reread when their examples change; it can’t tell that behavior changed behind code that didn’t. - A section is a folder. A project’s order and labels are in
ascribe.nav, which every sidebar is drawn from (Navigation), but the file only orders and labels what the folders hold. A page is in its folder’s section and only there, so putting it in another group means moving the file, and a sidebar can’t list a page twice or link to one outside the project. - One site generator. The site output is made for Astro, through
@ascribed/astro: its routes are Astro’s, and only Astro’s plugin renders its heading ids and attributes. The element library that draws Ascribe’s tabs, notes, and badges works on any site, and the plain Markdown and JSON outputs work with anything that reads them, but another generator would have to render the site output’s markers itself. - No lighter pull request. A change is still a branch and a pull request. Ascribe makes a pull request easier to read and to check, not to open.
- No translations. A project is written in one language. A translation can be a second project, under its own base path, but nothing says which of its pages are behind the source; that’s an idea for later.
The same page in other formats
examples/comparison holds one page written four ways: as Ascribe writes it, and as three open-source documentation sites write theirs, with a table of how each says the same thing. ascribe check passes on the Ascribe version, and a test runs it on every change.
Try it
Getting started sets up a project, writes a first page, checks it, and publishes it. The landing page shows a page’s source beside what a reader sees.