Skip to content
docuconf
docuconf on GitHub

Draft, not final

v1beta1 does not exist yet. These pages describe the changes proposed for it in docuconf-go pull requests #27, #28, #29 and #30, which are not merged. Anything here can still change. Keep contracts on v1alpha1 until the format freeze lands. See the versioning policy and the draft SPEC.md.

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. The model's own version stays v1alpha1 in this draft: the field tables and rotation steps below are additions. 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.

Field tables and rotation steps

A JSON Schema is hard to read, and every renderer would show it differently, so the docs model turns the schema of a json variable or a config file into fields, one row per property:

ColumnContent
pathDotted for nested objects (database.host), with [] for list items (routes[].match). Rows are in path order, each object before its properties.
typestring, integer, number, boolean, object, list of strings, list of objects, string or null, or see schema.
requiredWhether the property is in its parent's required.
default, description, enumFrom the schema. A secret input's rows have no default.
constraintsRanges, lengths, patterns, formats, item counts and uniqueItems, each already phrased.

A part of the schema the table cannot show (anyOf, oneOf, $ref, a map, tuple items) is one see schema row that carries that subtree, and renderers show its raw schema after the table.

Every key set gets the same rotation text in the model: how the overlap works, and the three steps in order. CONFIG.md prints them under the input, so an app's details need not repeat them. CONFIG.agents.md also lists the deprecated inputs, and its hard rules tell agents not to add or use them.

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.