| name | docs-sync |
| description | Detect and close drift between code and the Docusaurus site in `website/`. Use when the user asks to sync docs with code, audit docs drift, or after adding/removing packages, CLI commands, or MCP tools. Trigger words - sync docs, docs drift, update docs, website out of date, missing docs page. |
docs-sync
Three-level pipeline for keeping website/ in sync with the code. Each level
runs independently — do not skip level 1 before running the later ones.
1. check-structure (deterministic, no LLM) → find structural drift
2. generate-stubs (template, no LLM) → create missing doc files from code facts
3. refresh-content (agentic, diff-scoped) → suggest updates to existing pages [not implemented yet]
Nx targets (on the adt-cli-docs project):
bunx nx docs-check adt-cli-docs
bunx nx docs-stubs adt-cli-docs
Why this shape
Most of website/docs/ is a 1-to-1 projection of the code:
| Docs path | Code source |
|---|
website/docs/sdk/packages/*.md | packages/* |
website/docs/cli/*.md | packages/adt-cli/src/lib/commands/* |
website/docs/mcp/tools/*.md | server.tool('name', …) in adt-mcp |
website/docs/sdk/contracts/*.md | packages/adt-contracts/src/adt/* |
website/sidebars.ts | Index of all of the above |
For this 80% of the docs, drift is a set-difference problem, not an
LLM problem. Level 1 computes that difference in a few milliseconds with no
model calls and no false positives. Run it first, always.
The remaining 20% (guides/, architecture/, plugins/writing-*) is genuine
prose that requires human judgement — level 3 handles that, but only as an
advisor that flags sections for review. It does not rewrite prose.
Level 1 — check-structure (implemented)
bun .agents/skills/docs-sync/scripts/check-structure.ts
Exit code 0 if clean, 1 if drift. Emits a machine-readable-ish report with:
missing in docs — exists in code, absent from docs/sidebar
orphan in docs — exists in docs/sidebar, absent from code
Detection rules:
- Packages: every dir under
packages/ must have
website/docs/sdk/packages/<name>.md and an entry under sdkPackages in
sidebars.ts.
- MCP tools: every tool name registered in
packages/adt-mcp/src/lib/tools/
(both direct server.tool('name', …) and indirect
toolName: 'name' wrappers) must have website/docs/mcp/tools/<name>.md
and an entry under mcpTools in sidebars.ts.
- Sidebar ↔ files: every doc id quoted in
sidebars.ts must resolve
to an .md/.mdx file, and every doc file must be referenced from
sidebars.ts (except generated-index categories).
Not yet covered (known gaps):
- CLI commands (name mapping is fuzzy —
cli/cts-transport in sidebar vs
commands/cts/ in code). Needs an alias table before it can be automated
without false positives.
- Contracts (
sdk/contracts/*). Mapping is one-to-many per domain dir;
needs a convention decision first.
When extending, add new rules to scripts/check-structure.ts only if the
mapping is unambiguous. Fuzzy mappings belong in level 3.
Level 2 — generate-stubs (implemented)
bun .agents/skills/docs-sync/scripts/generate-stubs.ts
bun .agents/skills/docs-sync/scripts/generate-stubs.ts --write
For each missing in docs item from level 1, creates a stub file populated
from code facts only:
- Package: title/description from
package.json, links to the package
source and AGENTS.md/README.md if present.
- MCP tool: title and description from the
server.tool(name, description, …)
call itself. Input schema extracted from the third argument — both object
literals ({ ...connectionShape, foo: z.string() }) and bare identifiers
referencing shared shapes (sessionOrConnectionShape) are supported.
Fields keep their .optional() and .describe(…) annotations.
- Sidebar: new ids are inserted into
sdkPackages / mcpTools in
website/sidebars.ts, then the array is re-sorted alphabetically
(keeping */overview first).
Stubs carry zero hallucinated facts. When extraction is ambiguous — for
example a schema expression that is neither a recognised shared shape nor an
object literal — the stub links to the source instead of guessing.
Known limits of the Zod extractor (regex-based, no TS AST):
- Types beyond
string/number/boolean/array/object/enum
fall back to unknown.
- Complex refinements (
z.string().min(1).regex(…)) are reported as the
base type; the extra constraints are not shown.
- Conditional schemas (
z.union, z.discriminatedUnion) are not supported
yet — add a branch in zodTypeOf if needed.
When the extractor cannot determine the shape, it emits
See [source](…) for the full Zod schema. instead of a fake block. This is
intentional — do not teach the extractor to guess.
Level 3 — refresh-content (planned, not yet implemented)
Diff-scoped advisor for existing pages. Runs only when level 1 is clean.
Baseline: last commit carrying a Docs-Sync: true trailer (fallback: last
commit touching website/, with a warning).
For each page whose backing code region changed since baseline, emit an
advisory: <page>: possibly stale because of <files>. Does not rewrite
prose — the human decides whether the page actually drifted.
Rewriting is deliberately out of scope. The failure mode of
"confidently-written but subtly-wrong prose" is worse than the failure mode
of "prose is a bit stale". Stubs close the former; advisories catch the
latter without creating new lies.
Invocation recipe
When the user asks to "sync docs" or "check docs drift":
- Run
bun .agents/skills/docs-sync/scripts/check-structure.ts.
- Report the result verbatim — do not summarise away items.
- For each
missing item, confirm with the user before creating any files.
Level 2 stubs are safe, but even stubs land in a published site.
- For each
orphan item, ask: was this renamed, or removed? Renames need
redirects in docusaurus.config.ts; removals need the sidebar entry and
file both deleted.
Never run level 3 on a repo with level 1 drift — the advisor will point at
sections that should not exist yet.
Don'ts
- Don't write to
website/docs/ based on LLM output alone. Every claim
in a generated stub must trace to a code symbol or configuration value.
- Don't treat
sidebars.ts as an append-only log. The category arrays
(cliCommands, mcpTools, sdkPackages, sdkContracts) are the spec —
keep them alphabetically sorted when editing so diffs stay small.
- Don't add detection rules for fuzzy mappings (CLI aliases, multi-file
contracts) until the mapping is decided. Better no rule than a noisy rule.