🚧 Not ready — please don't use this yet. doc0 is an experiment: the API and the whole usage design are still being explored, the implementation is rough, and anything here can change or break without notice. It's public only to develop in the open and reserve the name. Please don't depend on it in anything real.
One navigable, searchable collection from the docs you already write.
You — and, more and more, your agents — write docs all the time: JSDoc, code comments, Markdown. But they're write-only: scattered across the repo, unsearchable, and forgotten the moment they're saved. As agents write more of the code, the hard part stops being typing and becomes staying on top of a codebase you didn't hand-write.
doc0 turns the docs you already write into one live, navigable, searchable collection, and serves it to everyone who needs it: to you in a local web UI and from the CLI, to your agents over MCP, to your build as generated files. No new format — it reads the JSDoc and Markdown you already have.
So it's not a doc-site generator, and not an agent-rules file. It's the layer
that keeps you in command of your own project: every convention, decision,
and gotcha is one query away — by name, tag, or meaning, and always pointing at
the exact file:line — even when an agent wrote the code. You write a doc once;
doc0 keeps it findable, linked, and fresh.
// docs/index.ts — point doc0 at the docs you already write
import { Doc0 } from '@devp0nt/doc0'
export const doc0 = Doc0.create({
glob: ['src/**/*.ts', 'docs/**/*.md', '!**/*.test.ts'],
})Any JSDoc with a doc0 directive becomes a doc — id, tags, and a @related
graph, all from a comment you'd write anyway:
/**
* Money is stored in minor units (cents). Never do currency math in the UI —
* format through here.
*
* @id money
* @tags rule, money
* @related cart-total
*/
export const formatMoney = (minor: number): string =>
`$${(minor / 100).toFixed(2)}`bunx doc0 web # you: browse + search every doc in the project
bunx doc0 mcp # your agents: list_docs / search_docs / get_doc — the same collectionThat money rule you wrote once is now one query away — for you in the browser,
for your agent over MCP — both landing on src/utils/money.ts.
doc0 is a dev tool — add it as a dev dependency:
bun add -d @devp0nt/doc0
# or: npm i -D / pnpm add -D / yarn add -DThe web UI, MCP, keyword search, and export work out of the box — nothing else to install. Only semantic search is opt-in (it pulls a local ML model, hundreds of MB):
bun add -d @huggingface/transformersBun 1+ or Node.js 20+. ESM only.
One config module exports the engine. Runners (the CLI, the MCP/web servers) load it — the config itself runs nothing.
// docs/index.ts
import { Doc0 } from '@devp0nt/doc0'
export const doc0 = Doc0.create({
// code files: a JSDoc joins the collection when it carries a doc0 directive.
// markdown files: every match joins, unless its frontmatter says `doc: false`.
glob: ['src/**/*.ts', 'docs/**/*.md', 'README.md', '!**/*.test.ts'],
// map a category id to a human title (drives grouping in the web UI and export)
category: { rule: 'Rules', util: 'Utilities' },
})A doc has a small, fixed shape: id, title, description, tags,
category, related, content, and source (file + line span). Missing
fields are filled from the content — title from the first heading, id from
the symbol or file name — so a bare ## Heading Markdown file is already a
valid doc. Write a comment for the why and the gotchas; doc0 turns it into
something the whole project can find.
This is the point doc0 exists for. When agents type most of the code, your job shifts to steering and reviewing, and that needs the knowledge that keeps a project coherent close at hand — the why, the "use X not Y", the canonical helper. doc0 makes that knowledge a living map you drive from:
- browse the whole project's docs in a local web UI;
- search by keyword, or by meaning;
- follow the
@relatedgraph from any doc to its neighbours; - jump straight to the
file:linea doc lives at.
bunx doc0 web # http://localhost:4000 — sidebar, search, rendered markdown
bunx doc0 search "how do we format prices" # => money, cart-total, …
bunx doc0 get money # full content + source + related, in the terminalSearch works out of the box (orama keyword/BM25). For "find by meaning", turn on embeddings — the small model downloads once, runs locally, no API key:
import { oramaSearch } from '@devp0nt/doc0/search'
export const doc0 = Doc0.create({
glob: ['src/**/*.ts', 'docs/**/*.md'],
search: oramaSearch({ embeddings: true }), // hybrid keyword + semantic
})Write a doc once and it stops rotting: it's indexed, linked, and always one query away — so you stay in tune with a codebase that's growing faster than you can read it line by line.
Your agents query the very same collection — so they follow your conventions instead of re-deriving (and sometimes mis-guessing) them. No parallel rules file that drifts from the code; one source you both read.
Point an agent at it with a single instruction:
<!-- AGENTS.md -->
Before any task: `list_docs({ tag: "rule" })`, read the rules that apply, and
`get_doc` them. Search the rest with `search_docs("…")` before guessing.bunx doc0 mcpThe MCP server exposes a small, fixed tool set any agent understands — it scales to a project with hundreds of docs, where one-tool-per-doc would not:
| Tool | What it returns |
|---|---|
list_docs |
table of contents — paged, filter by tag / category |
search_docs |
top matches with snippets |
get_doc |
one doc in full, with source and an enriched related list |
source carries the file path and line span, so an agent jumps from a rule
straight to the code it governs — the same way you do.
The collection is also data you can emit. Generate artifacts (Cursor rule files, a JSON index, anything) — written only when content changes — or dump the whole thing to one file for a hand-off, a PR, or another tool:
export const doc0 = Doc0.create({
glob: ['src/**/*.ts', 'docs/**/*.md'],
generate: (doc) =>
doc.tags.includes('rule')
? { path: `.cursor/rules/${doc.id}.mdc`, content: doc.content }
: undefined,
finalGenerate: (docs) => ({
path: 'docs/source.json',
content: docs.toJSON(),
}),
})bunx doc0 sync # parse → run generators (diff-only)
bunx doc0 export --format md > docs.md # one Markdown bundle: TOC + every doccollect() revalidates itself (it re-scans changed files by mtime), so the MCP
and web servers never serve stale docs — no watcher needed for reads.
doc0 list [path] # table of contents — --tag --category --fields --limit --offset --json
doc0 get <id> # one doc in full — --fields --json
doc0 search <query> # keyword + semantic — --limit --fields --json
doc0 export [path] # whole collection → file — --format md|json --tag --category -o
doc0 sync [path] # run generators (diff-only) — --watch
doc0 mcp [path] # serve over MCP (stdio)
doc0 web [path] # serve the web UI — --port
doc0 prune [path] # drop the on-disk cachepath is optional — defaults to docs/index.ts (or doc0.config.ts), or pass
any path to the config module.
In a JSDoc comment (any one of these makes the comment a doc), or in Markdown frontmatter:
| Directive | Meaning |
|---|---|
@id |
stable id (else the symbol or file name) |
@title |
title (else the first heading, else a humanized id) |
@description |
one-line summary (else the first content line) |
@tags |
comma list; rule is just a tag |
@category |
grouping tag(s) — drive folders and the web sidebar |
@related |
linked ids; a trailing ! (e.g. @related setup!) means must-read |
@doc |
force-include a comment that has no other directive |
Markdown joins by glob; opt a file out with doc: false in its frontmatter.
| Import | What |
|---|---|
@devp0nt/doc0 |
Doc0 / Docs, parsers, collect / sync / watch, export |
@devp0nt/doc0/parsers |
the built-in parsers + the Parser contract for your own |
@devp0nt/doc0/mcp |
serveMcp(src) — the MCP server |
@devp0nt/doc0/search |
oramaSearch() — keyword by default, semantic opt-in |
@devp0nt/doc0/web |
serveWeb(src) — the web server + UI |
How to author docs doc0 collects well — what earns a doc, the rule tag,
category areas, @related — lives in
docs/conventions.md. Copy it into your project as
docs/docs.md and tweak the area list.
A runnable end-to-end project lives in examples/minimal.
- Bun 1+ or Node.js 20+ (ESM only)
- TypeScript 5+ (optional — works in plain JS too)
Questions, bugs, or want to hang with other builders? Join the devp0nt community — one hub for all our open-source projects, this one included. Get help, share what you built, or just say hi: p0nt.dev/community
Issues and PRs welcome. See CONTRIBUTING.md and the Code of Conduct. Commits follow Conventional Commits. Security reports: SECURITY.md.
Building open-source software for the glory of the Lord Jesus Christ ☦️
With love for developers of all backgrounds around the world ❤️
Sergei Dmitriev, 2026 😎