Documentation that parses
docujoint is a deterministic engine for markdown documentation. You describe your format in one YAML file — types, typed tables, evidence schemes, the rules that matter to you — and it parses every document into data it can check, chart, answer questions about, and refuse to let drift.
dj init
## Features | ID | Feature | Implemented | Gap | |----|--------------------|-----------------------|-----| | f1 | Buyer pays by card | repo://shop/pay.ts | — | | f2 | Saved wallets | repo://shop/wallet.ts | — | | f3 | Apple Pay | — | not built yet |
wallet.ts is gone from the inventory — the claim outlived the code.
Drift
01 · The format is yours
The engine ships no vocabulary. It does not know what built
means, or that a table called Features is special. Your
format.yaml says which document types exist, what tables they
carry, which columns are evidence, and how state is computed from them.
Two teams can use the same word for opposite things, and both are right.
blocks: features: heading: Features columns: - { name: ID, id_grammar: "f<n>", unique: error } - { name: Implemented, refs: [repo, api] } - { name: Gap } # state is computed, never typed into a column derive: state: - { when: "implemented && !gap && ref_broken", then: drift } - { when: "implemented && !gap", then: built } - { when: "!implemented && gap", then: missing } forbid_columns: - { name: Status, severity: error }
02 · Nothing is stored twice
Anything that can be computed is computed: from what the row says, and from whether the code it cites still exists. A document cannot claim done while pointing at a file nobody has — that combination has a name, and it shows up as a number.
Point it at a scan of your codebase and the reverse also holds: what exists and nothing documents.
Apps/Storefront/Pages/Checkout Page.md error f2: cites evidence the scan no longer confirms (drift) warn f5: complete but no test:// evidence (feature-untested) warn q1: open question blocks f3 (unspecified) 498 file(s), 488 concept(s): 0 error(s), 627 warning(s) exit 0 — pass --warnings-as-errors to make CI mean it
03 · One vault, three surfaces
The same parse becomes an interactive dashboard you declare in YAML — a page per team member, tables you act on in place, forms that write answers back through the linter so a bad edit is refused rather than committed.
And a graph: every document, row, link and citation projected into SQLite, so "what would this change touch" is a query instead of an afternoon.
4,187 nodes (488 document · 3,699 artifact) 7,408 edges (4,394 rows · 1,960 citations · 631 links · 423 joins) -- who cites the endpoint you are about to change? SELECT r.node, r.key FROM cite c JOIN row_ r ON r.id = c.row WHERE c.uri = 'api://payments/charges'; 11 rows · 0.03 ms
Measured on a 498-file vault
Sixty seconds
# a working vault: format, dashboard, sample documents $ npm i -g docujoint $ dj init my-docs && cd my-docs # zero findings is the bar, and CI can hold you to it $ dj lint --all --vault vault --format format.yaml # one self-contained file — opens offline, no server $ dj dashboard --vault vault --format format.yaml \ --dashboard dashboard.yaml --out dashboard.html # or serve it, and answer questions in the page $ dj serve --vault vault --format format.yaml \ --dashboard dashboard.yaml
Agents write documentation and the engine decides when it is done —
zero errors, deterministically, from the same binary CI runs.
dj skills install --all teaches them the format.
Your format is a profile over Google's Open Knowledge Format. One markdown file per concept, ordinary links, no database — a vault stays readable in any editor and diffable in any repo.
Full-text over every row, and semantic search when you bring an embedding model. A Spanish question finds the right English rows — the engine stores the vectors and never calls a model itself.
Open core