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
| Field | Rule |
|---|---|
description | Required 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. |
details | Optional. 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:
// 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"`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)- Contract. The app's SDK exports
contract.cue, with each input's description and details. - Docs model. The CLI unifies the contract with the meta-schema, so that defaults such as
required: falseand a list'sencodingare 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 staysv1alpha1in 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. - 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.mdis 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.mdis 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.envfile, a values file, a ConfigMap or an annotation; use each input's wire format; validate withdocuconf vetordocuconf checkbefore proposing a change; never invent an input the contract lacks. Then how to use the config in code, onekey: valueblock per input, and the boot errors. It can be included in anAGENTS.mdor served as anllms.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:
| Column | Content |
|---|---|
path | Dotted for nested objects (database.host), with [] for list items (routes[].match). Rows are in path order, each object before its properties. |
type | string, integer, number, boolean, object, list of strings, list of objects, string or null, or see schema. |
required | Whether the property is in its parent's required. |
default, description, enum | From the schema. A secret input's rows have no default. |
constraints | Ranges, 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]| Flag | Meaning |
|---|---|
--format markdown | The reference for developers, such as CONFIG.md. The default. |
--format agents | The rules and per-input facts for AI agents, such as CONFIG.agents.md. |
--format model | The docs model itself, such as docs.json. |
-o file | Write to the file instead of standard output. |
--check file | Write nothing: exit 0 when the file is what would be generated, otherwise exit 1 with a diff. Cannot be combined with -o. |
-contract file | The 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.mdWhere each SDK takes them from
From each SDK's README:
| SDK | description | details |
|---|---|---|
| Go | The first paragraph of the field's doc comment, on one line, without its final period; a desc tag is the fallback | The 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) |
| Python | Field(description=...), else the first paragraph of the attribute docstring | The attribute docstring (the rest of it, without a description), or Field(json_schema_extra={"details": ...}) |
| Ruby | describe :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 it | The 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 |
| Rust | The first paragraph of the field's /// doc comment, or #[docuconf(description = "...")] | The rest of the doc comment, or #[docuconf(details = "...")] |
| Swift | The 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("...") |
| Elixir | The first paragraph of the @doc above env, or description: | The rest of the @doc, or details: |
| Gleam | The 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 entry | The rest of the PHPDoc comment, or details: |
| PHP (Symfony) | The description key of each variable in docuconf.yaml | The details key |
| COBOL | The first paragraph of the comment above the copybook field, or @desc | The rest of the comment, or @details |
The normative text is SPEC.md §14.