This is the full developer documentation for docsgen
# docsgen
> Your headers are already the docs. docsgen just publishes them.
**docsgen** turns a directory of annotated C++ headers into a browsable MDX API reference and an `llms.txt` bundle for AI agents. It parses your real source with the [codex](https://landing.codexx-dtdk.com) C++ frontend — namespaces, templates, member access, enumerators, `///` doc comments — and emits one MDX page per public symbol, cross-linked and catalogued.
It ships as a single binary. The core generator calls nothing home. It runs the same on every machine. It is **free to use**.
## How docsgen is built
[Section titled “How docsgen is built”](#how-docsgen-is-built)
docsgen is a terminal stage on top of the codex parsing pipeline:
1. **Parsing** — handled by codex. Your headers are collected, preprocessed, parsed, and analysed into a symbol database with full qualified names, access, comments, and relationships.
2. **Generation** — handled by docsgen. It walks the public API surface, parses each symbol’s doc comment, resolves cross-links, and renders deterministic MDX (plus optional catalog pages and `llms.txt`).
You don’t write templates or scripts. You write good `///` comments in your headers, and docsgen publishes them.
***
## Core capabilities
[Section titled “Core capabilities”](#core-capabilities)
Deterministic output
Same headers ⇒ same MDX. Always. Symbols are sorted by qualified name and rendered by a pure pass over the codex symbol database, so diffs are meaningful and docs are reproducible across machines and CI.
Public-API only
docsgen documents what your users can actually call. Private and protected members, forward declarations, and excluded namespaces (`detail`, `impl`) never reach the output.
Doxygen-style comments
Reads the tags you already know — `@brief`, `@param`, `@tparam`, `@return`, `@note`, `@see` — and the first sentence of a plain comment becomes the summary. No new comment dialect to learn.
Automatic cross-linking
Type references in signatures, parameters, and `@see` entries resolve to links between generated pages. Your reference reads like a connected graph, not a flat dump.
Catalog pages
Alongside per-symbol pages, docsgen emits catalogs — namespaces, types, functions, files — so readers (and search) have an index into the surface.
Optional AI summaries
Point `llm:` at Anthropic, OpenAI, or a local Ollama model and docsgen adds an `:::note[AI Summary]` block per symbol. Cached and deterministic on warm runs (temperature 0). Off by default — the core generator stays local.
llms.txt out of the box
Emit a single plain-text bundle of your whole API for AI agents. Mark symbols `@llms-only` to ship them to agents without cluttering the rendered site.
Astro / Starlight native
Output is MDX with frontmatter, cross-link URLs, and `:::note` directives — drop it straight into an Astro docs site (this one is built that way).
***
## Free to use
[Section titled “Free to use”](#free-to-use)
docsgen has no tiers, no licence keys, and no paywalled features. Install it, point it at your headers, and publish. See [Installation](/getting-started/installation/) to get started.
# LLM Summaries
> Optionally enrich each page with an AI-written summary and key takeaways — cached, deterministic, and provider-agnostic.
docsgen can add a short, AI-written summary to each symbol page — an `:::note[AI Summary]` callout with a paragraph and a “Key takeaways” list. It is **off by default**; the core generator stays fully local unless you opt in.
## Enabling it
[Section titled “Enabling it”](#enabling-it)
Add an `llm:` block to your `config.docsgen.yaml`:
```yaml
llm:
provider: ollama # anthropic | openai | ollama
model: gemma2:27b
# base_url: "http://localhost:11434" # default for ollama
max_concurrent: 2
max_tokens: 2048 # raise for large symbols or thinking models
```
When `llm:` is present, summaries are generated; when it’s absent, every LLM code path is a no-op.
## Providers
[Section titled “Providers”](#providers)
| Provider | `provider:` | Notes |
| -------------- | ----------- | ----------------------------------------------------------------------------- |
| Ollama (local) | `ollama` | Runs on your machine — nothing leaves the host. Best for a local-first setup. |
| Anthropic | `anthropic` | Claude models. Needs `api_key` (or the provider’s env var). |
| OpenAI | *(default)* | Used when `provider:` is omitted or unrecognised. Needs `api_key`. |
Other keys: `api_key`, `base_url` (override host/port), `system_prompt_append` / `system_prompt_override` (steer the summary style), `max_concurrent` (parallel calls), `max_tokens`.
## What it produces
[Section titled “What it produces”](#what-it-produces)
For each public symbol, docsgen builds a prompt from the declaration and renders the response as:
```mdx
:::note[AI Summary]
net::Socket is an RAII-managed, asynchronous, non-blocking socket wrapper ...
**Key takeaways:**
- Employs a callback-based asynchronous I/O model via IoCallback.
- Thread-safety is limited to send(), receive(), and cancel() once connected.
- State-changing operations must run on the event-loop thread.
:::
```
## Caching and determinism
[Section titled “Caching and determinism”](#caching-and-determinism)
Summaries are cached on disk at:
```plaintext
/.codexx/cache/tools/docsgen/llm_summaries.json
```
where `` is the directory containing your `config.docsgen.yaml`. The cache key covers the **model ID + declaration text + system prompt**. On a warm run, docsgen reads the cache and calls nothing:
* **Warm cache, temperature 0 ⇒ deterministic.** Re-running produces identical summaries.
* **Cold cache, or a changed model / prompt ⇒** docsgen calls the provider for the misses, then writes them back.
A pre-pass fires all provider calls (semaphore-limited by `max_concurrent`) and populates the cache *before* the parallel MDX render begins, so the I/O-bound calls don’t serialise the CPU-bound generation.
The AI feature is the **only** part of docsgen that may reach the network, and only when you configure a hosted provider. Use `provider: ollama` to keep everything on-device. See [Local-First](/trust/local-first/).
Commit the cache file if you want reproducible docs in CI without re-calling the provider. Without a warm cache, a CI run with a hosted provider will make live calls (and may differ run-to-run if the model changes upstream).
## When to use it
[Section titled “When to use it”](#when-to-use-it)
AI summaries are a complement, not a replacement, for good `///` comments. They shine for orienting readers on large or subtle types — but the deterministic, hand-written brief is still the contract. Treat the summary as a helpful gloss layered on top.
# llms.txt
> Emit your whole API as one plain-text bundle for AI agents — and the @llms-only escape hatch.
Alongside the rendered MDX, docsgen can emit an [`llms.txt`](https://llmstxt.org/) file: a single plain-text document containing your entire public API, formatted for an LLM to ingest in one shot. Drop it into an agent and it has your library’s surface as context.
## Enabling it
[Section titled “Enabling it”](#enabling-it)
Pass the flag:
```sh
docsgen --llms-txt
```
or set it in config:
```yaml
llms_txt:
enabled: true
# output: "./public" # for Astro projects, llms.txt belongs in public/
```
By default the file is written next to your MDX output. Use `--llms-txt-output ` (or `llms_txt.output`) to redirect it — for an Astro site, `public/` so it’s served at `/llms.txt`.
## What’s in it
[Section titled “What’s in it”](#whats-in-it)
The bundle leads with a header built from your config — title, tagline, and a metadata line:
```text
# net
> Async non-blocking socket library
**Version:** 1.0.0 | **License:** MIT | **Authors:** CodeXX Team
---
```
Then one section per public symbol, in qualified-name order, each rendered in plain Markdown (no MDX escaping):
```text
## net::Socket
*class*
Async, non-blocking socket with an RAII lifetime. ...
**Parameters:**
- `proto` -- Transport protocol.
**Returns:** Handle for cancellation.
**Notes:**
- Does not support multi-homed binding in this version.
**See also:** Endpoint, AsyncHandle
---
```
The same public-API filter and `exclude_namespaces` list that govern the MDX output apply here, so the bundle stays in sync with the rendered site.
## The `@llms-only` tag
[Section titled “The @llms-only tag”](#the-llms-only-tag)
Some symbols are worth giving an agent but not worth a page on the site — internal-but-documented helpers, agent hints, fast paths. Tag the comment:
```cpp
/// @llms-only
/// Internal fast encoder used by the generated bindings.
void __fast_encode(...);
```
`@llms-only` symbols are **skipped during MDX generation** but **still included in llms.txt** (as long as they pass the public-API filter). It’s the one-way valve: visible to machines, hidden from the rendered docs.
This is the docsgen **tool** feature — it generates `llms.txt` for *your* C++ library. Separately, this very documentation site publishes its own [`/llms-full.txt`](/llms-full.txt) via the `starlight-llms-txt` plugin. Same idea, two layers: docs about docsgen, and the API docs docsgen produces.
## Why ship it
[Section titled “Why ship it”](#why-ship-it)
A rendered HTML site is for humans; `llms.txt` is for the agents your users increasingly code alongside. One file, deterministic, regenerated on every build — so the model’s view of your API never drifts from the source.
# Cross-Linking
> How docsgen turns type references into links between generated pages.
A good API reference reads like a connected graph: click a parameter’s type and land on its page. docsgen builds those links automatically.
## What gets linked
[Section titled “What gets linked”](#what-gets-linked)
Before rendering, docsgen builds an index from every public symbol’s name to its generated file. It then resolves references in:
* **Parameter types** in the Parameters table.
* **Return types** in the Returns line.
* **Member and field types** on struct/class pages.
* **`@see` entries** that name a symbol.
* **Base classes / related types** surfaced in See Also.
A reference like `const Endpoint&` renders as `const [Endpoint](/net_endpoint)&` — the type name becomes a link, the qualifiers stay as text.
## URLs and `--base-url`
[Section titled “URLs and --base-url”](#urls-and---base-url)
A symbol’s URL is derived from its qualified name: lowercased, with `::` flattened to `_`. So:
| Symbol | URL |
| ------------------------------- | ------------------------------ |
| `net::Socket` | `/net_socket` |
| `net::SocketOptions::keepAlive` | `/net_socketoptions_keepalive` |
By default links are rooted at `/`. If your docs are served under a sub-path, set a prefix:
```sh
docsgen --base-url /api
```
```yaml
# or in config.docsgen.yaml
api_base_url: /api
```
Trailing slashes are stripped, so `/api/` and `/api` behave the same. Every generated link is then prefixed (`/api/net_socket`).
## `@see` resolution order
[Section titled “@see resolution order”](#see-resolution-order)
For each `@see` entry, docsgen tries, in order:
1. **URL** — starts with `http://` or `https://` ⇒ rendered as a plain link.
2. **Symbol** — matches a known symbol name ⇒ linked to its page (the raw, unescaped name is used for the lookup so it matches the index).
3. **Plain text** — no match ⇒ rendered as-is.
```cpp
/// @see Endpoint // -> link to /net_endpoint
/// @see https://datatracker.ietf.org/doc/html/rfc793 // -> external link
/// @see the design notes in DESIGN.md // -> plain text
```
If a `@see` you expected to link renders as plain text, the name didn’t match a documented symbol — usually because it’s private, in an excluded namespace, or spelled differently than its qualified name. Check [Public API Surface](/concepts/public-api/).
## Stable links across runs
[Section titled “Stable links across runs”](#stable-links-across-runs)
Because URLs are derived deterministically from qualified names, links don’t churn between runs unless you rename a symbol. That keeps your generated docs diff-friendly and safe to commit.
# Doc Comments & Tags
> Every comment style and tag docsgen understands, and how each one renders.
docsgen reads the doc comment attached to each symbol and turns it into structured page sections. It speaks a familiar Doxygen-style dialect — no new syntax to learn.
## Comment styles
[Section titled “Comment styles”](#comment-styles)
docsgen strips the usual comment markers before parsing, so any of these attach to the symbol below them:
```cpp
/// Triple-slash line comments (recommended).
/** Block comment
* spanning lines. */
// Plain double-slash also works.
```
Leading markers (`///`, `//`, `/**`, `/*`, `*`) and a single following space are stripped; trailing `*/` is removed. Inside fenced code blocks (` ``` `), nothing is parsed — `@` characters and markup pass through verbatim.
## The summary rule
[Section titled “The summary rule”](#the-summary-rule)
* If a comment has an `@brief`, that text is the summary.
* Otherwise, the **first sentence** of the description (up to the first `.`, `!`, `?`, or newline) becomes the summary.
The summary populates the page’s `description` frontmatter and the brief shown in catalogs.
## Tags
[Section titled “Tags”](#tags)
| Tag | Applies to | Renders as |
| ----------------------- | ---------- | ------------------------------------------------------------------- |
| `@brief` | any | The summary line. Ends at the first blank line. |
| `@param ` | functions | A row in the **Parameters** table (type auto-linked). |
| `@tparam ` | templates | A row in the **Template Parameters** table. |
| `@return` / `@returns` | functions | The **Returns** line (return type auto-linked). |
| `@note` | any | An entry in the **Notes** list (repeatable). |
| `@see` | any | An entry in **See Also** (symbol name, or URL, auto-linked). |
| `@llms-only` | any | Flag: ship to [llms.txt](/ai/llms-txt/) but skip the rendered page. |
Tags are multi-line: continuation lines are appended to the current tag until the next tag or a blank line (for `@brief`). Anything not under a tag is the symbol’s description body, rendered as Markdown.
## A worked example
[Section titled “A worked example”](#a-worked-example)
```cpp
/// @brief Connect to a remote host and start the handshake.
///
/// The socket transitions to `Connecting` immediately and reaches
/// `Connected` once the OS confirms the handshake, or `Error` on failure.
///
/// @param endpoint Remote host and port.
/// @param onConnect Fired once when the connection succeeds or fails.
/// @return Handle that can be passed to `cancel()` to abort the attempt.
/// @note No-op when the socket is not in the `Idle` state.
/// @see Endpoint
[[nodiscard]] AsyncHandle connect(const Endpoint& endpoint, IoCallback onConnect);
```
renders to a method page with: a brief, a description paragraph, a Parameters table (with `Endpoint` linked), a Returns line (with `AsyncHandle` linked), a Notes list, and a See Also pointing at the `Endpoint` page.
`@see` accepts a symbol name (resolved to a link when known), a bare URL (`https://...`), or plain text (rendered as-is when no symbol matches). See [Cross-Linking](/comments/cross-linking/).
## Markdown in comments
[Section titled “Markdown in comments”](#markdown-in-comments)
Comment bodies are Markdown. Headings, lists, inline code, and fenced code blocks all work and pass through to the MDX. Keep in mind docsgen escapes JSX-sensitive characters outside code fences so the output stays valid MDX — write `std::vector` freely; it will render correctly.
# Catalog Pages
> The index pages docsgen emits alongside per-symbol pages — namespaces, types, functions, and files.
Per-symbol pages are the leaves. **Catalog pages** are the index — generated automatically so readers and search have a way into the surface. docsgen emits four.
## Types
[Section titled “Types”](#types)
`catalog_types` — every public type (class, struct, union, enum, alias, concept), with its kind and brief, each linked to its page:
```mdx
---
title: "Types"
description: "All public types"
---
| Name | Kind | Description |
|------|------|-------------|
| [net::Endpoint](/net_endpoint) | Struct | IPv4 or IPv6 endpoint (address + port). |
| [net::Socket](/net_socket) | Class | Async, non-blocking socket with an RAII lifetime. |
| [net::SocketState](/net_socketstate) | Enum | Coarse-grained socket lifecycle state. |
```
## Functions
[Section titled “Functions”](#functions)
`catalog_functions` — every public free function and method, with signature summary and brief, linked to the owning page.
## Namespaces
[Section titled “Namespaces”](#namespaces)
`catalog_namespaces` — the surface grouped by namespace. Each namespace gets a heading and a table of its members:
```mdx
## net
| Name | Kind | Description |
|------|------|-------------|
| [Endpoint](/net_endpoint) | Struct | IPv4 or IPv6 endpoint (address + port). |
| [Socket](/net_socket) | Class | Async, non-blocking socket with an RAII lifetime. |
```
## Files
[Section titled “Files”](#files)
`catalog_files` — a reverse index from each indexed header to the symbols it declares:
```mdx
| File | Symbols |
|------|--------|
| `input.hpp` | [Endpoint](/net_endpoint), [Socket](/net_socket), [SocketOptions](/net_socketoptions), ... |
```
## Using them in a sidebar
[Section titled “Using them in a sidebar”](#using-them-in-a-sidebar)
Catalogs are ordinary MDX pages, so you can link them anywhere or fold them into your Starlight sidebar — a top-level “API Index” group pointing at the four catalogs works well. Because excluded namespaces and non-public symbols are filtered before catalogs are built, the indexes only ever list what’s actually documented. See [Astro / Starlight](/integrations/astro/) for wiring them in.
# How It Works
> The docsgen pipeline — from a directory of C++ headers to deterministic MDX and llms.txt.
docsgen is the terminal stage of the [codex](https://landing.codexx-dtdk.com) C++ analysis pipeline. It does not parse C++ itself — it consumes the symbol database codex produces and renders it.
## The pipeline
[Section titled “The pipeline”](#the-pipeline)
```
flowchart LR
A[Headers] --> B[collect]
B --> C[extract]
C --> D[preprocess]
D --> E[parse]
E --> F[analyze]
F --> G[Symbol DB]
G --> H[MDXGenerator]
H --> I[MDX files]
G --> J[llms.txt]
H -.optional.-> K[LLM summaries]
```
1. **collect** — walk the input directory for files matching the configured extensions (`.hpp`, `.h` by default), recursively.
2. **extract / preprocess / parse / analyze** — codex builds a `SourceNode` AST and resolves it into a **symbol database**: qualified names, kinds, access specifiers, member relationships, template parameters, and attached doc comments.
3. **MDXGenerator** — walks the public symbols, parses each one’s doc comment, resolves cross-links, and renders MDX.
4. **FileWriter** — writes one `.mdx` file per symbol (plus catalog pages) into the output directory.
5. **llms.txt** *(optional)* — a separate generator emits a single plain-text bundle of the API.
## What docsgen adds on top of codex
[Section titled “What docsgen adds on top of codex”](#what-docsgen-adds-on-top-of-codex)
The parsing is codex’s job. docsgen owns everything documentation-shaped:
* **Selecting the public surface** — private/protected members, forward declarations, and excluded namespaces are filtered out. See [Public API Surface](/concepts/public-api/).
* **Parsing doc comments** — stripping comment markers, splitting `@brief`/`@param`/`@return`/etc., and extracting the first sentence as a summary. See [Doc Comments & Tags](/comments/doc-comments/).
* **Cross-linking** — turning type names in signatures and `@see` entries into links between pages. See [Cross-Linking](/comments/cross-linking/).
* **Rendering MDX** — per-kind layouts for classes, structs, enums, functions, aliases, concepts, operators, and macros, with escaped JSX-sensitive characters.
* **Catalogs** — index pages over namespaces, types, functions, and files. See [Catalog Pages](/concepts/catalogs/).
* **AI summaries / llms.txt** — optional LLM-written summaries and an agent-ready text bundle.
## Determinism
[Section titled “Determinism”](#determinism)
Output is a pure function of your headers:
* Symbols are **sorted by qualified name** before rendering, so file order is stable.
* Generation runs in parallel (one TBB task per symbol writing to a disjoint slot) but the result is order-independent.
* The same input produces byte-identical MDX across machines and CI runs — diffs stay meaningful.
The one caveat is the optional `llm:` feature. AI summaries are deterministic on **warm-cache** runs (temperature 0; the cache key covers model ID + declaration text + system prompt). A cold run with a changed model or prompt will call the provider and may produce different text. The core MDX generation is always deterministic and offline. See [LLM Summaries](/ai/llm-summaries/).
## Where state lives
[Section titled “Where state lives”](#where-state-lives)
* **Output MDX** goes to `--output` (or `output:` in config).
* **The codex parse cache** lives under `.codexx/` in your workspace.
* **AI summary cache** lives under `.codexx/cache/tools/docsgen/llm_summaries.json`, keyed by the parent directory of your config file.
docsgen never modifies your input files — it only reads headers and writes to the output directory.
# Public API Surface
> How docsgen decides which symbols become documentation — and which are filtered out.
docsgen documents what your users can call. A symbol reaches the output only if it passes every filter below.
## What is documented
[Section titled “What is documented”](#what-is-documented)
A symbol is treated as public API when **all** of these hold:
1. **It is not a forward declaration.** Declarations without a definition are skipped.
2. **Its access is not private or protected.** Only public (and namespace-scope) symbols are rendered.
3. **It is not in an excluded namespace** (see below).
4. **Its kind is documentable** — class, struct, union, enum, function, variable, type alias, concept, operator, or macro. Other node kinds (e.g. raw namespaces as symbols) are not emitted as standalone pages.
Everything else is silently dropped. There is no flag today to include private members in the rendered site.
## Excluding namespaces
[Section titled “Excluding namespaces”](#excluding-namespaces)
Implementation lives in namespaces like `detail`, `internal`, or `impl`. Exclude them so they never reach the docs:
config.docsgen.yaml
```yaml
exclude_namespaces:
- detail
- internal
```
A symbol is excluded when its qualified name **equals** an entry or **starts with** `entry::`. So `detail` removes `net::detail` only if you list the fully qualified prefix you want — match against the qualified name as docsgen sees it. The same exclusion list is applied to both the MDX output and `llms.txt`.
## Nested symbols and the sidebar
[Section titled “Nested symbols and the sidebar”](#nested-symbols-and-the-sidebar)
Members of a type (a method, a nested struct) are documented, but you may not want every one cluttering the sidebar. With `--hide-nested` (or `hide_nested: true`):
* A nested symbol still generates its page.
* It is marked `hidden` when its parent is a **type** (class/struct/union), not a namespace.
Namespace-scoped symbols are never hidden by this rule — they are top-level API.
## The `@llms-only` escape hatch
[Section titled “The @llms-only escape hatch”](#the-llms-only-escape-hatch)
Sometimes you want a symbol available to AI agents but not rendered on the site (internal-but-documented helpers, agent hints). Tag its comment:
```cpp
/// @llms-only
/// Internal fast-path used by the codegen bindings.
void __fast_encode(...);
```
`@llms-only` symbols are **skipped during MDX generation** but still included in [llms.txt](/ai/llms-txt/). See [Doc Comments & Tags](/comments/doc-comments/) for the full tag list.
## Why public-only by default
[Section titled “Why public-only by default”](#why-public-only-by-default)
A reference that mixes private plumbing with the public contract is hard to read and easy to break against. By rendering only the surface your users can depend on, docsgen keeps the docs aligned with the API you actually support — and keeps generated diffs focused on changes that matter.
# What It Generates
> The shape of docsgen output — one MDX file per public symbol, with frontmatter, signatures, tables, and cross-links.
docsgen emits **one MDX file per public symbol**, plus a handful of catalog pages. This page describes the shape of those files so you know what to expect — and what your Astro/Starlight site will render.
## One file per symbol
[Section titled “One file per symbol”](#one-file-per-symbol)
Each of these symbol kinds becomes its own page:
* **Class**, **Struct**, **Union** — with constructors, destructor, public methods, operators, and members.
* **Enum** — with enumerators.
* **Function** — free functions and methods.
* **Variable** — public/global variables and constants.
* **TypeAlias** — `using` / `typedef`.
* **Concept** — C++20 concepts.
* **Operator** — overloaded operators.
* **Macro** — object-like and function-like macros.
Nested symbols can be hidden from the sidebar with `--hide-nested` (or `hide_nested: true`) — they still generate, but are marked `hidden` when their parent is a type rather than a namespace.
## Anatomy of a page
[Section titled “Anatomy of a page”](#anatomy-of-a-page)
Every page starts with Starlight frontmatter, then renders the comment and the structured sections:
````mdx
---
title: "Socket"
description: "Async, non-blocking socket with an RAII lifetime."
---
`Socket` wraps a single OS socket file descriptor and exposes a
callback-based async I/O model.
*Defined in `input.hpp:154`*
## Public Methods
### connect
```cpp
[[nodiscard]] AsyncHandle connect(const Endpoint& endpoint, IoCallback onConnect);
```
**Parameters:**
| Name | Type | Default | Description |
|------|------|---------|-------------|
| `endpoint` | const [Endpoint](/net_endpoint)& | - | Remote host and port. |
**Returns:** [AsyncHandle](/net_asynchandle) - Handle for cancellation.
````
Key elements:
* **`title`** is the unqualified name; **`description`** is the symbol’s brief (the `@brief` tag or the comment’s first sentence).
* **Signatures** are rendered in fenced `cpp` blocks.
* **Parameter tables** carry one row per `@param`, with the parameter *type* linked to its page when known.
* **Returns / Notes / See Also** sections come from `@return`, `@note`, and `@see`.
* **`*Defined in file:line*`** records the source location.
## Filenames and URLs
[Section titled “Filenames and URLs”](#filenames-and-urls)
A symbol’s file name is derived from its qualified name (lowercased, `::` flattened) — e.g. `net::SocketOptions::keepAlive` ⇒ `net_socketoptions_keepalive.mdx`. Cross-links point at the matching URL, prefixed by `--base-url` when set. See [Cross-Linking](/comments/cross-linking/).
## MDX safety
[Section titled “MDX safety”](#mdx-safety)
Generated text is escaped so it’s valid MDX: JSX-sensitive characters (`{`, `}`, `<`, `>`) in signatures and prose are encoded (`std::span` renders as `std::span<std::byte>`). Fenced code blocks in comments are passed through untouched.
Output is UTF-8 and overwrites the output directory in place — docsgen does not back up existing files. Point `--output` at a directory you control (typically `src/content/docs/...` in an Astro project).
## Catalog pages
[Section titled “Catalog pages”](#catalog-pages)
Beyond per-symbol pages, docsgen emits index pages — `catalog_types`, `catalog_functions`, `catalog_namespaces`, `catalog_files` — that tabulate the whole surface with links. See [Catalog Pages](/concepts/catalogs/).
# Your First Docs
> Annotate a single header with doc comments and see exactly how docsgen renders each tag.
docsgen turns the comments you already write into structured pages. This page takes one small header from bare to fully documented so you can see each tag’s effect.
## The starting point
[Section titled “The starting point”](#the-starting-point)
Create `include/net/endpoint.hpp`:
```cpp
#pragma once
#include
#include
namespace net {
struct Endpoint {
std::string host;
uint16_t port;
};
} // namespace net
```
Generate:
```sh
docsgen --input ./include --output ./docs
```
You get a page for `net::Endpoint` — but with no prose, just the declaration and its members. docsgen documents the public surface whether or not it’s commented; comments are what make it *useful*.
## Add a summary
[Section titled “Add a summary”](#add-a-summary)
The first sentence of a plain comment becomes the page summary (and the `description` frontmatter). Add a `///` comment above the struct:
```cpp
/// IPv4 or IPv6 endpoint: an address plus a port.
///
/// Used by `Socket::connect` and returned by `Socket::remoteEndpoint`.
struct Endpoint {
```
Regenerate, and the page now leads with that summary and carries the second paragraph as the body.
## Use explicit tags
[Section titled “Use explicit tags”](#use-explicit-tags)
For anything richer than a sentence, use the Doxygen-style tags docsgen parses. They work on types, functions, and members alike:
```cpp
/// @brief Connect to a remote host and start the handshake.
///
/// The socket transitions to `Connecting` immediately and reaches
/// `Connected` once the OS confirms the handshake.
///
/// @param endpoint Remote host and port.
/// @param onConnect Fired once when the connection succeeds or fails.
/// @return Handle that can be passed to `cancel()` to abort the attempt.
/// @note No-op when the socket is not in the `Idle` state.
/// @see Endpoint
[[nodiscard]] AsyncHandle connect(const Endpoint& endpoint, IoCallback onConnect);
```
docsgen renders this as:
* the `@brief` as the method summary,
* the prose paragraph as the description,
* a **Parameters** table (one row per `@param`, with the parameter *type* linked),
* a **Returns** line,
* a **Notes** list,
* a **See Also** entry — and because `Endpoint` is a known symbol, `@see Endpoint` becomes a link to its page.
Every tag docsgen understands — `@brief`, `@param`, `@tparam`, `@return`/`@returns`, `@note`, `@see`, and the `@llms-only` flag — is documented in [Doc Comments & Tags](/comments/doc-comments/).
## Hide the plumbing
[Section titled “Hide the plumbing”](#hide-the-plumbing)
Move helpers into a `detail` namespace and exclude it so they never reach the docs:
config.docsgen.yaml
```yaml
exclude_namespaces:
- detail
```
Private and protected members and forward declarations are dropped automatically — see [Public API Surface](/concepts/public-api/) for the exact rules.
## What you’ve learned
[Section titled “What you’ve learned”](#what-youve-learned)
* docsgen documents the public surface with or without comments.
* A plain comment’s first sentence becomes the summary; tags add structure.
* Type references in tags and signatures auto-link to their pages.
* `exclude_namespaces` and access rules keep implementation details out.
Next: [How It Works](/concepts/how-it-works/) for the full pipeline, or the [Config Schema](/reference/config-schema/) to tune output.
# Installation
> Install docsgen through the CodeXX DTDK Manager — the single entry point that installs, updates, and verifies every DTDK component.
## How CodeXX DTDK is installed
[Section titled “How CodeXX DTDK is installed”](#how-codexx-dtdk-is-installed)
docsgen ships as one component of the **CodeXX Dev-Tool Development Kit (DTDK)**. You don’t download `docsgen` directly. You install the **DTDK Manager** once — a small terminal app — and use it to install, update, and verify docsgen and every other component.
The manager keeps each component in a versioned, side-by-side layout under `~/.codexx` and exposes one binary per tool on your `PATH`. No `sudo`, no system package manager, no admin rights.
docsgen is **free to use** — there is no licence key and no activation step. Install it and run it.
## Install
[Section titled “Install”](#install)
One command downloads the latest stable manager, verifies its checksum, extracts it under `~/.codexx`, and adds `~/.codexx/bin` to your shell profile. No GitHub account or token required.
Linux Windows macOS
```sh
curl -fsSL https://get.codexx-dtdk.com/install.sh | bash
```
Open a new shell afterwards so the `PATH` change takes effect. To also verify the release signature, append `-s -- --verify` (requires `cosign` on `PATH`).
```powershell
iex ((New-Object System.Net.WebClient).DownloadString('https://get.codexx-dtdk.com/install.ps1'))
```
Installs under `%USERPROFILE%\.codexx` and prepends `%USERPROFILE%\.codexx\bin` to your user `PATH`. Open a new shell afterwards.
macOS builds are temporarily paused while the release pipeline is validated on Apple hardware. Until macOS archives ship, build from source on macOS — see the project `README` — or run the Linux build in an x86-64 environment.
The installer fetches the manager through a download proxy that holds a read-only token server-side — the releases live in a private repository, but you never need credentials. To pin a specific version or install a pre-release channel, download `install.sh` / `install.ps1` and pass `--tag` or `--channel`.
## Inspect the installer before running it
[Section titled “Inspect the installer before running it”](#inspect-the-installer-before-running-it)
Don’t want to pipe a remote script straight into a shell? Download it, read it, then run it.
Linux Windows
```sh
curl -fSL https://get.codexx-dtdk.com/install.sh -o install.sh
less install.sh # read it
chmod +x install.sh
./install.sh --help # list every flag
./install.sh # default install (stable, ~/.codexx, PATH)
./install.sh --verify # additionally cosign-verify the archive
```
```powershell
Invoke-WebRequest https://get.codexx-dtdk.com/install.ps1 -OutFile install.ps1
Get-Content .\install.ps1 # read it
Get-Help .\install.ps1 -Detailed # list every flag
.\install.ps1 # default install
.\install.ps1 -Verify # additionally cosign-verify the archive
```
This is the same artifact the proxy serves to `curl | bash` / `iex` — saving it to disk just lets you audit it first.
## Run the manager
[Section titled “Run the manager”](#run-the-manager)
Open a new shell so the `PATH` change takes effect, then launch:
```sh
codexx_dtdk_manager
```
The manager is a terminal UI. Move with the arrow keys; press `Enter` on the **docsgen** row to install it. `q` quits. The manager handles the download, checksum verification, and side-by-side version management for you. Because docsgen is free, there is no activation prompt — installing the component is all it takes.
## Verify the installation
[Section titled “Verify the installation”](#verify-the-installation)
```sh
docsgen --version
docsgen --help
```
`--version` prints the channel and version (e.g. `stable/docsgen@0.1.0`); `--help` lists every option. If the command isn’t found, confirm `~/.codexx/bin` (or `%USERPROFILE%\.codexx\bin`) is on your `PATH` and that you opened a new shell.
## Uninstall
[Section titled “Uninstall”](#uninstall)
One command removes the install tree and the `PATH` entry.
Linux Windows
```sh
curl -fsSL https://get.codexx-dtdk.com/uninstall.sh | bash
```
The script prints what it will remove and asks for confirmation, then deletes `~/.codexx` and strips the marker block from your shell rc files. For unattended use append `| bash -s -- --yes`. `--dry-run` previews without touching anything.
```powershell
irm https://get.codexx-dtdk.com/uninstall.ps1 | iex
```
The script prints what it will remove, asks for confirmation, then deletes `%USERPROFILE%\.codexx` and removes `%USERPROFILE%\.codexx\bin` from your user `PATH`. `-DryRun` previews without touching anything.
Key Takeaways
* docsgen is installed through the DTDK Manager, not downloaded directly.
* One PAT-free command installs the manager — download, checksum, extract, and `PATH` are all automatic.
* docsgen is free: no licence key, no activation step.
* Verify with `docsgen --version` once `~/.codexx/bin` is on your `PATH`.
# Quick Start
> Generate an MDX API reference from a directory of C++ headers in two commands.
This walks you from a folder of headers to a generated MDX reference in about a minute. It assumes docsgen is on your `PATH` — see [Installation](/getting-started/installation/) if not.
## 1. Point docsgen at your headers
[Section titled “1. Point docsgen at your headers”](#1-point-docsgen-at-your-headers)
The minimal invocation takes an input directory and an output directory:
```sh
docsgen --input ./include --output ./docs
```
docsgen collects every `.hpp` / `.h` under `./include` (recursively), parses them with the codex C++ frontend, and writes one MDX file per public symbol into `./docs`, plus catalog pages. The final line reports how many files were written:
```plaintext
wrote 27 MDX files to './docs'
```
Short flags work too: `docsgen -i ./include -o ./docs`. Run `docsgen --help` for the full list, or see the [CLI Reference](/reference/cli/).
## 2. Look at what came out
[Section titled “2. Look at what came out”](#2-look-at-what-came-out)
Each public class, struct, enum, function, alias, concept, operator, and macro becomes its own page. A class page carries the symbol’s doc comment, its constructors, methods, operators, and a **See Also** section — with types in signatures already linked to their own pages:
````mdx
---
title: "Socket"
description: "Async, non-blocking socket with an RAII lifetime."
---
`Socket` wraps a single OS socket file descriptor ...
## Public Methods
### connect
```cpp
[[nodiscard]] AsyncHandle connect(const Endpoint& endpoint, IoCallback onConnect);
```
**Returns:** [AsyncHandle](/net_asynchandle) - Handle that can be passed to `cancel()`.
````
docsgen also emits catalog pages — `catalog_types`, `catalog_functions`, `catalog_namespaces`, `catalog_files` — that index the whole surface. See [What It Generates](/concepts/what-it-generates/) and [Catalog Pages](/concepts/catalogs/).
## 3. Add a config file (recommended)
[Section titled “3. Add a config file (recommended)”](#3-add-a-config-file-recommended)
Re-typing flags gets old. Drop a `config.docsgen.yaml` next to your project and docsgen discovers it automatically (from the current directory, then the input directory):
```yaml
name: net
description: Async non-blocking socket library
version: "1.0.0"
license: MIT
input: "./include" # relative to this config file
output: "./docs"
extensions:
- .hpp
- .h
# Hide implementation namespaces from the output.
exclude_namespaces:
- detail
- internal
```
Now generation is just:
```sh
docsgen
```
CLI flags still win over config values when both are present. The full set of keys is in the [Config Schema](/reference/config-schema/).
## 4. (Optional) Emit llms.txt
[Section titled “4. (Optional) Emit llms.txt”](#4-optional-emit-llmstxt)
Add `--llms-txt` (or set `llms_txt.enabled: true`) to also emit a single plain-text bundle of your API for AI agents:
```sh
docsgen --llms-txt
```
See [llms.txt](/ai/llms-txt/) for what goes in the bundle and the `@llms-only` escape hatch.
## Next steps
[Section titled “Next steps”](#next-steps)
* [Your First Docs](/getting-started/first-docs/) — annotate a header and watch the page change.
* [How It Works](/concepts/how-it-works/) — the pipeline from headers to MDX.
* [Doc Comments & Tags](/comments/doc-comments/) — every tag docsgen understands.
# Astro / Starlight
> Wire docsgen output straight into an Astro Starlight docs site.
docsgen emits MDX with Starlight-compatible frontmatter and `:::note` directives — so its output drops into an [Astro](https://astro.build) / [Starlight](https://starlight.astro.build) site with no transformation. This documentation site is built exactly that way.
## Point output at your content directory
[Section titled “Point output at your content directory”](#point-output-at-your-content-directory)
Generate directly into your Starlight content collection:
```sh
docsgen --input ./include --output ./src/content/docs/api --base-url /api
```
* `--output ./src/content/docs/api` — Starlight serves this collection at `/api/...`.
* `--base-url /api` — so generated cross-links resolve to the served URLs. See [Cross-Linking](/comments/cross-linking/).
Or set both in `config.docsgen.yaml` so a bare `docsgen` does the right thing:
```yaml
input: "./include"
output: "./src/content/docs/api"
api_base_url: /api
```
## Frontmatter compatibility
[Section titled “Frontmatter compatibility”](#frontmatter-compatibility)
Each page carries `title` and `description`, which Starlight uses for the page heading, the sidebar label, and `` tags. The body uses standard Markdown plus `:::note[...]` callouts (for [AI summaries](/ai/llm-summaries/)) — both render natively in Starlight.
## Sidebar wiring
[Section titled “Sidebar wiring”](#sidebar-wiring)
docsgen does not write your sidebar; you point Starlight at the generated directory. The catalog pages make good entry points:
```js
// astro.config.mjs — sidebar entry
{
label: "API Reference",
items: [
{ label: "Types", slug: "api/catalog_types" },
{ label: "Functions", slug: "api/catalog_functions" },
{ label: "Namespaces", slug: "api/catalog_namespaces" },
{ label: "Files", slug: "api/catalog_files" },
{ label: "All Symbols", autogenerate: { directory: "api" } },
],
}
```
See [Catalog Pages](/concepts/catalogs/) for what each catalog contains.
## llms.txt in public/
[Section titled “llms.txt in public/”](#llmstxt-in-public)
For the agent-facing bundle, write it to `public/` so Starlight serves it at `/llms.txt`:
```yaml
llms_txt:
enabled: true
output: "./public"
```
Because output is deterministic, you can either commit the generated MDX or regenerate it in CI before `astro build`. Committing makes PR diffs reviewable; regenerating keeps it impossible to drift. See [CI Pipelines](/integrations/ci/).
# CI Pipelines
> Run docsgen in CI to keep generated docs in sync with your headers.
Because docsgen output is deterministic, it fits two clean CI patterns: **regenerate on build**, or **commit and verify**. Pick based on whether you want generated MDX in your repo.
## Pattern A — regenerate before building the site
[Section titled “Pattern A — regenerate before building the site”](#pattern-a--regenerate-before-building-the-site)
Keep generated docs out of git; produce them fresh in CI right before the static build:
.github/workflows/docs.yml
```yaml
steps:
- uses: actions/checkout@v4
- name: Install DTDK Manager
run: curl -fsSL https://get.codexx-dtdk.com/install.sh | bash
- name: Install docsgen
run: ~/.codexx/bin/codexx_dtdk_manager --install docsgen # or via the TUI
- name: Generate API docs
run: docsgen # reads config.docsgen.yaml
- name: Build site
run: pnpm build
```
No drift is possible — the docs are always rebuilt from current headers.
## Pattern B — commit generated docs, verify they’re current
[Section titled “Pattern B — commit generated docs, verify they’re current”](#pattern-b--commit-generated-docs-verify-theyre-current)
Commit the MDX so PR diffs show doc changes, and fail CI if someone forgot to regenerate:
```sh
docsgen
git diff --exit-code ./src/content/docs/api
```
A non-zero exit means the committed docs are stale versus the headers. This is only reliable because docsgen is deterministic — the same headers always produce byte-identical MDX.
## AI summaries in CI
[Section titled “AI summaries in CI”](#ai-summaries-in-ci)
If you enable [LLM Summaries](/ai/llm-summaries/), commit the cache so CI doesn’t make live provider calls:
```plaintext
.codexx/cache/tools/docsgen/llm_summaries.json
```
With a warm cache, generation is offline and reproducible. Without it, a hosted provider would be called on every run (slow, costly, and possibly non-reproducible). For a fully hermetic build, prefer `provider: ollama` or rely on the committed cache.
docsgen exits non-zero on pipeline failure (parse error) or write failure, so a bare `docsgen` is safe to gate a pipeline on. See the [CLI Reference](/reference/cli/) for exit codes.
# CLI Reference
> Every docsgen command-line flag, the config interplay, and exit codes.
docsgen is a single-shot CLI: it parses headers and writes MDX, then exits.
## Synopsis
[Section titled “Synopsis”](#synopsis)
```sh
docsgen [-i ] [-o ] [-c ] [--hide-nested]
[--base-url ] [--llms-txt] [--llms-txt-output ]
docsgen --version
docsgen --help
```
## Flags
[Section titled “Flags”](#flags)
| Flag | Argument | Description |
| ------------------- | -------- | -------------------------------------------------------------------------- |
| `-i`, `--input` | path | Input directory of C++ headers. Required unless `input:` is set in config. |
| `-o`, `--output` | path | Output directory for MDX. Required unless `output:` is set in config. |
| `-c`, `--config` | path | Explicit config file (otherwise discovered). |
| `--hide-nested` | — | Hide non-top-level entities from the sidebar. |
| `--base-url` | url | Prefix for cross-links (trailing `/` stripped). |
| `--llms-txt` | — | Also emit `llms.txt`. |
| `--llms-txt-output` | path | Directory for `llms.txt` (default: same as `--output`). |
| `--version` | — | Print `/docsgen@` and exit. |
| `--help` | — | Print the option list and exit. |
## Flags vs config
[Section titled “Flags vs config”](#flags-vs-config)
* A CLI flag always overrides the corresponding config value.
* `--input` / `--output` fall back to `input:` / `output:` in config; one of each (flag or config) is required.
* `--base-url` defaults to `/`; if left at the default, `api_base_url` from config is used.
* `--llms-txt` is also enabled by `llms_txt.enabled: true`.
See the [Config Schema](/reference/config-schema/) for discovery and path-resolution rules.
## Verbose output
[Section titled “Verbose output”](#verbose-output)
The shared `--verbose` flag (from the common CLI base) prints input/output paths, the resolved config name/version, the indexed symbol count, and each generated file.
## Exit codes
[Section titled “Exit codes”](#exit-codes)
| Code | Meaning |
| ---- | ------------------------------------------------------------- |
| `0` | Success. |
| `1` | Usage error — missing/invalid input or output, or a bad flag. |
| `2` | Pipeline failure (parse error) or file-write failure. |
The non-zero codes make a bare `docsgen` safe to gate a [CI pipeline](/integrations/ci/) on.
## Examples
[Section titled “Examples”](#examples)
```sh
# Explicit dirs
docsgen -i ./include -o ./docs
# Config-driven (config.docsgen.yaml discovered)
docsgen
# Into an Astro site, with link prefix and agent bundle
docsgen -o ./src/content/docs/api --base-url /api --llms-txt --llms-txt-output ./public
# Check the installed version
docsgen --version
```
docsgen overwrites the output directory in place and never modifies input files. Point `--output` at a directory you own.
# Config Schema (.yaml)
> Every key in config.docsgen.yaml — discovery rules, paths, filters, links, llms.txt, and the llm block.
docsgen reads a single YAML file named **`config.docsgen.yaml`**. It is optional — everything can be passed on the CLI — but it’s the recommended way to pin a project’s settings.
## Discovery and precedence
[Section titled “Discovery and precedence”](#discovery-and-precedence)
* With `-c/--config `, docsgen loads exactly that file.
* Otherwise it **discovers** `config.docsgen.yaml`, checking the **current working directory first**, then the input directory.
* **Paths in the config are relative to the config file’s directory**, not your shell’s CWD.
* **CLI flags win** over config values when both are set.
## Full schema
[Section titled “Full schema”](#full-schema)
```yaml
# --- Project metadata (used in llms.txt header) ---
name: net
description: Async non-blocking socket library
version: "1.0.0"
license: MIT
authors:
- CodeXX Team
# --- Paths (relative to this file) ---
input: "." # input directory of C++ headers
output: "./docs" # output directory for MDX
# --- File selection ---
extensions: # which file extensions to collect
- .hpp
- .h
include: # regex patterns to include
- ".*\\.hpp$"
exclude: # regex patterns to exclude
# --- Output shaping ---
exclude_namespaces: # drop these namespaces from output + llms.txt
- detail
- internal
hide_nested: false # hide non-top-level entities from the sidebar
api_base_url: / # URL prefix for cross-links
# --- External links (project metadata) ---
links:
homepage: https://example.com
github:
code: https://github.com/acme/net
issues: https://github.com/acme/net/issues
gitlab:
code: https://gitlab.com/acme/net
issues: https://gitlab.com/acme/net/issues
# --- llms.txt ---
llms_txt:
enabled: false
output: "./public" # for Astro projects (served at /llms.txt)
# --- Optional AI summaries ---
llm:
provider: ollama # anthropic | openai | ollama (default: openai)
model: gemma2:27b
api_key: "..." # for hosted providers
base_url: "http://localhost:11434"
system_prompt_append: "Prefer terse, factual summaries."
system_prompt_override: "..." # replace the built-in prompt entirely
max_concurrent: 2
max_tokens: 2048
```
## Key reference
[Section titled “Key reference”](#key-reference)
| Key | Type | Meaning |
| ------------------------------- | ------------- | --------------------------------------------------------- |
| `name` | string | Project name; the `# title` in llms.txt. |
| `description` | string | Tagline; the `> blockquote` in llms.txt. |
| `version`, `license`, `authors` | string / list | Metadata line in the llms.txt header. |
| `input` | path | Header directory (relative to config). |
| `output` | path | MDX output directory (relative to config). |
| `extensions` | list | File extensions to collect (default `.hpp`, `.h`). |
| `include` / `exclude` | list of regex | Path filters applied during collection. |
| `exclude_namespaces` | list | Namespaces dropped from output and llms.txt. |
| `hide_nested` | bool | Hide type-nested symbols from the sidebar. |
| `api_base_url` | string | Prefix for generated cross-links (trailing `/` stripped). |
| `links.*` | strings | Project links (homepage, GitHub/GitLab code + issues). |
| `llms_txt.enabled` | bool | Emit `llms.txt`. |
| `llms_txt.output` | path | Directory for `llms.txt` (default: same as `output`). |
| `llm.provider` | enum | `anthropic`, `ollama`, or OpenAI (default/fallback). |
| `llm.model` | string | Model ID. |
| `llm.api_key` | string | Credential for hosted providers. |
| `llm.base_url` | string | Override provider host/port. |
| `llm.system_prompt_append` | string | Text appended to the built-in system prompt. |
| `llm.system_prompt_override` | string | Replaces the built-in system prompt. |
| `llm.max_concurrent` | int | Parallel provider calls. |
| `llm.max_tokens` | int | Max tokens per summary. |
Only the keys you set take effect; everything else falls back to defaults. A malformed config emits a warning and docsgen proceeds as if it were absent. See [LLM Summaries](/ai/llm-summaries/) for the `llm:` block in depth.
# Local-First
> What docsgen does and does not send over the network — and how to keep it fully offline.
docsgen reads your source code. So it matters exactly what it does with it. The short version: the core generator is fully local, and the only path that can reach the network is one you opt into explicitly.
## The core generator calls nothing home
[Section titled “The core generator calls nothing home”](#the-core-generator-calls-nothing-home)
Collecting headers, parsing, building the symbol database, parsing comments, cross-linking, rendering MDX, and writing `llms.txt` all happen on your machine. No telemetry, no analytics, no phone-home. You can run the entire pipeline air-gapped.
docsgen also never writes to your input files — it only reads headers and writes to the output directory you name.
## The one network path: hosted AI summaries
[Section titled “The one network path: hosted AI summaries”](#the-one-network-path-hosted-ai-summaries)
The optional [LLM Summaries](/ai/llm-summaries/) feature is the **only** part of docsgen that can make a network request, and only when you configure a hosted provider:
| Configuration | Network? |
| ------------------------------ | ----------------------------------------------------------------------- |
| No `llm:` block | None. Fully offline. |
| `provider: ollama` | None — Ollama runs on your host. |
| `provider: anthropic` / OpenAI | Sends each symbol’s declaration to that provider to generate a summary. |
When you point `llm:` at Anthropic or OpenAI, the declaration text (and your system prompt) is sent to that provider under their terms. If that’s not acceptable for your code, use `provider: ollama` for on-device summaries, or omit `llm:` entirely.
Want summaries **and** zero egress? Run a local model with `provider: ollama`. You get the `:::note[AI Summary]` blocks with nothing leaving the machine.
## Keeping it reproducible and offline in CI
[Section titled “Keeping it reproducible and offline in CI”](#keeping-it-reproducible-and-offline-in-ci)
* Omit `llm:` (or use Ollama) for a hermetic build.
* If you use a hosted provider, commit the summary cache (`.codexx/cache/tools/docsgen/llm_summaries.json`). With a warm cache, CI makes no provider calls. See [CI Pipelines](/integrations/ci/).
## Summary
[Section titled “Summary”](#summary)
* Parsing and generation: **always local.**
* Input files: **never modified.**
* Network access: **only** via hosted AI summaries, **only** when you configure them — and avoidable with Ollama.
# Supply Chain
> How CodeXX DTDK components like docsgen are built, attested, and distributed to minimize supply chain risk.
## Distribution model
[Section titled “Distribution model”](#distribution-model)
docsgen is distributed as one of the **independently versioned components** of CodeXX DTDK on GitHub Releases. You install and update it through the **DTDK Manager**, which downloads each component, verifies it, and stages it into a versioned, side-by-side layout under `~/.codexx`.
There is no system package manager, no `npm install`, no Python environment to configure, and no install-time script running with elevated privileges. Each component archive is a self-contained, relocatable FHS tree (`bin/`, `lib/`, `include/`) — moving the install root never breaks library resolution.
## Every release is attested
[Section titled “Every release is attested”](#every-release-is-attested)
Each component release is a four-part **Package Bundle**: the binary archive, an SPDX SBOM, a SLSA Build L3 provenance record, and a Sigstore signature bundle — plus a SHA-256 sidecar on the archive. The release pipeline structurally rejects incomplete bundles.
The manager verifies the SHA-256 of every archive before it installs it; full Sigstore signature verification is available on demand.
## Build-time dependencies
[Section titled “Build-time dependencies”](#build-time-dependencies)
The build-time dependency tree is pinned end to end:
| Source | Pinned by |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| vcpkg ports (tree-sitter-cpp, yaml-cpp, abseil, nlohmann-json, …) | `vcpkg.json` plus the vcpkg baseline commit; resolved versions captured under `vcpkg_installed/` |
| Vendored submodules (`vendor/*`) | exact commit SHAs in `.gitmodules` / the recorded submodule state |
No dependency is fetched from a mutable tag or branch. The SBOM shipped with each release (`.spdx.json`) inlines every resolved vcpkg per-port package and every `vendor/*` submodule SHA, so the bill of materials reflects exactly what was linked.
## Release security gates
[Section titled “Release security gates”](#release-security-gates)
Before any archive is publishable, the pipeline enforces hard gates:
* **Header allowlist** — the distributed `include/codexx/` tree may contain only public API headers; any unrecognized file blocks the release.
* **Symbol stripping** — released binaries are checked (`nm` on Linux/macOS, `dumpbin` on Windows) against a symbol allowlist so internal symbols are not exposed.
* **Apple Notarization** — all macOS binaries are notarized via `notarytool`.
* **Authenticode** — all Windows binaries carry a valid Authenticode signature.
A build that fails any gate is never published.
## Why this shape
[Section titled “Why this shape”](#why-this-shape)
* **No dependency confusion** — there is nothing to resolve at install time; the manager pulls exact, attested archives.
* **No install-time code execution** — extraction places files; it does not run scripts.
* **Pre-extraction verification** — the detached `.sigstore` and `.sha256` siblings let you verify an archive before unpacking it.
* **Auditable** — every release signature is recorded in the public Rekor transparency log.
Key Takeaways
* docsgen is distributed as an independently versioned, attested archive and installed through the DTDK Manager — no system package manager, no privileged install scripts.
* Build-time dependencies are fully pinned (vcpkg ports plus vendored submodule SHAs) and enumerated in a shipped SPDX SBOM.
* Release gates enforce a header allowlist, symbol stripping, macOS notarization, and Windows Authenticode signing.
* Every archive carries a SHA-256 sidecar and a Sigstore bundle; the manager verifies checksums automatically.