Skip to content
docuconf
docuconf on GitHub

Generated docs

Every input in a contract has a description, and may have details. The app's developers write both where they already document a setting, in a doc comment beside the code that reads it, and the SDK exports them into contract.cue. One generator, docuconf docs in the CLI, turns the contract into documentation for every language: CONFIG.md for developers and CONFIG.agents.md for AI agents. SDKs export the text; they do not render it.

Description and details

FieldRule
descriptionRequired on every variable and file input. Plain text, at least 5 characters: what the input is, in one sentence or phrase. Renderers keep it on one line.
detailsOptional. CommonMark: why the input exists and when to change it. Not blank, and at most 4000 characters, counted in Unicode code points. Used only in generated docs, never at runtime.

Each SDK takes both from the language's natural doc location: usually the first paragraph of the doc comment is the description and the rest is the details. Language-specific doc syntax (Javadoc tags, XML doc elements, Go doc links, Doxygen commands) is converted to CommonMark or dropped. Export fails when a description is missing or too short, or when details are blank or too long. In the Go example, WORKER_COUNT is documented like this:

internal/config/config.go
// Number of background workers processing orders.
//
// Each worker holds one database connection, so keep it below the
// database's connection limit divided by the number of replicas.
// Raise it when the order queue grows faster than it drains.
WorkerCount int `env:"WORKER_COUNT" envDefault:"4" min:"1" max:"64"`
contract.cue
WORKER_COUNT: {
	type:        "int"
	description: "Number of background workers processing orders"
	details:     "Each worker holds one database connection, so keep it below the database's connection limit divided by the number of replicas. Raise it when the order queue grows faster than it drains."
	default:     4
	min:         1
	max:         64
}

In the generated docs, details keep their Markdown, with one change: their headings are demoted to nest under the input's own heading, so they cannot break the document's outline.

The pipeline

contract.cue | contract.json ──build──▶ docs model (docs.json) ──render──▶ CONFIG.md          (developers)
                                                               └─render──▶ CONFIG.agents.md   (AI agents)
  1. Contract. The app's SDK exports contract.cue, with each input's description and details.
  2. Docs model. The CLI unifies the contract with the meta-schema, so that defaults such as required: false and a list's encoding are explicit, and builds the docs model: a versioned JSON document (kind: ConfigDocs, apiVersion: docs.docuconf.dev/v1alpha1) that holds every fact a renderer shows, already phrased. Each input's type label, wire format, values-file form, constraints, allowed sources and possible boot errors are in it, and every model is checked against #DocsModel. A secret input never has a default, an example or a profile value in the model, whatever the contract held.
  3. Render. A renderer reads only the model, so a website, an MCP server or a Backstage plugin can render the same facts in the same words. The output is deterministic: the same model always gives the same bytes.
  • CONFIG.md is the reference for developers: a table of contents by group, then a section per input with its description, a table (type, required, secret, default, constraints, wire format, values-file form, config key, path, sources, boot errors), its schema, examples and details; then profiles and overlays when the contract has them, what each source means, and each boot error's meaning and fix.
  • CONFIG.agents.md is for AI agents: coding agents working in the app's repository, and agents that set deployment values. It starts with hard rules: never put a secret value in code, a .env file, a values file, a ConfigMap or an annotation; use each input's wire format; validate with docuconf vet or docuconf check before proposing a change; never invent an input the contract lacks. Then how to use the config in code, one key: value block per input, and the boot errors. It can be included in an AGENTS.md or served as an llms.txt-style file.

See them for the Go orders example: CONFIG.md and CONFIG.agents.md.

The command

docuconf docs <contract.cue | contract.json | docs.json> [--format model|markdown|agents] [-o file | --check file]
FlagMeaning
--format markdownThe reference for developers, such as CONFIG.md. The default.
--format agentsThe rules and per-input facts for AI agents, such as CONFIG.agents.md.
--format modelThe docs model itself, such as docs.json.
-o fileWrite to the file instead of standard output.
--check fileWrite nothing: exit 0 when the file is what would be generated, otherwise exit 1 with a diff. Cannot be combined with -o.
-contract fileThe contract, if not given as the argument.

The input is a contract, in CUE or as JSON, or a docs model, recognised by its apiVersion. Commit the docs next to contract.cue and keep them current in CI:

docuconf docs contract.cue -o CONFIG.md
docuconf docs contract.cue --format agents -o CONFIG.agents.md
docuconf docs contract.cue --check CONFIG.md
docuconf docs contract.cue --format agents --check CONFIG.agents.md

Where each SDK takes them from

From each SDK's README:

Where each SDK takes description and details from
SDKdescriptiondetails
GoThe first paragraph of the field's doc comment, on one line, without its final period; a desc tag is the fallbackThe rest of the doc comment: headings (# Heading), lists and indented code carry over as Markdown
TypeScript (T3 Env).describe(), or .meta({ description })The TSDoc comment above the variable, read at export; or .meta({ details }), or annotate() for other validators
TypeScript (NestJS)@Describe("...")The TSDoc comment above the property and its decorators, read at export; or @Details("...")
.NET[Description] (or [Display(Description = ...)]), else the XML doc <summary>The XML doc <remarks>, from the documentation file the project generates (GenerateDocumentationFile)
PythonField(description=...), else the first paragraph of the attribute docstringThe attribute docstring (the rest of it, without a description), or Field(json_schema_extra={"details": ...})
Rubydescribe :attr, "..."The YARD comment directly above describe, or its details: option
Java (Spring Boot)The first sentence of the property's Javadoc (the record component's @param, the field or the getter); @Description overrides itThe rest of the Javadoc, read by the annotation processor at compile time
Kotlin@Doc("..."), else the first sentence of the property's KDoc@Doc(details = "..."), else the rest of the KDoc, indexed by the dev.docuconf Gradle plugin
RustThe first paragraph of the field's /// doc comment, or #[docuconf(description = "...")]The rest of the doc comment, or #[docuconf(details = "...")]
SwiftThe first paragraph of the @Env description argument, which may be a whole multi-line doc comment: a property wrapper cannot read ///The rest of that text, or .details("...")
ElixirThe first paragraph of the @doc above env, or description:The rest of the @doc, or details:
GleamThe description argument of each builder, such as docuconf.int(name, description)details (file_details for a file); Gleam cannot read a comment at run time
C++The description argument of add_var or add_file, .description(), or the first paragraph of a Doxygen comment given to .doc()The rest of the .doc() comment, or .details("...")
PHP (Laravel)The description argument of Env::int(...) and the other helpers, else the first paragraph of the PHPDoc comment above the config entryThe rest of the PHPDoc comment, or details:
PHP (Symfony)The description key of each variable in docuconf.yamlThe details key
COBOLThe first paragraph of the comment above the copybook field, or @descThe rest of the comment, or @details

The normative text is SPEC.md §14.