Appearance
Migration Guide: UDE v1.0 to v2.0
This guide is the complete checklist for moving a working v1.0 setup to v2.0. Every command, field name, error message and output sample below was verified against the shipped engine on 2026-08-02 — where v2.0 raises a hard error on a v1.0 input, the exact message is quoted so you can match it against your logs.
Read the Hard stop sections first: they are the changes that make a previously working configuration fail outright.
Hard stop 1: every document directory now requires sidebar.toml
This is the single most likely reason a v1.0 project stops building on v2.0.
sidebar.toml replaced the per-renderer toc_*.json files as the source of navigation truth. Delete any toc_*.json you carried over — they are no longer read by anything.
The file is mandatory and must declare at least one [[sidebar]] entry. The pipeline refuses a document directory otherwise rather than falling back to defaults, because navigation that renders successfully but wrongly is far harder to notice than a failed build. Three separate failures, all fatal:
text
Required sidebar.toml not found in <dir>. Every document directory must contain a sidebar.toml file.
sidebar.toml in <dir> declares no [[sidebar]] entries. There is no default navigation to fall back on ...
Failed to parse required sidebar.toml '<path>': <parser detail>Place one in each <project>/ directory that has a ude_doc_config.json, including the Hugo variants. A minimal file is three lines:
toml
[[sidebar]]
type = "api_reference"
label = "API Reference (C++)"
url = "index.html"Configuration resolves through a three-tier deep merge (global → SDK → document), so shared values live at the outer tiers and each document directory carries only its overrides.
The folder taxonomy that used to live in template JSON now sits in a [groups] table:
toml
[groups]
namespace_level = ["Classes", "Structures", "Enums"]
class_level = ["Methods", "Properties"]The table's contents are strictly validated (extra="forbid"), so a misspelled key inside [groups] is a hard error rather than a silently empty taxonomy. [groups] has one extra tier below the usual three — the engine's own default TOML seeds it — and a [groups] table found in the document directory merges at the highest priority of all.
[[sidebar]] and [groups] behave differently, deliberately:
| Table | If you omit it |
|---|---|
[[sidebar]] | Build fails. There is no default navigation at any tier. |
[groups] | Engine default applies. Most projects never declare it. |
The asymmetry is intentional. A taxonomy default produces a sensible, uniform folder layout; a navigation default would produce a sidebar you never wrote, which renders successfully and gives you nothing to notice. So an emptysidebar.toml is not valid — it fails exactly like a missing one:
text
sidebar.toml in <dir> declares no [[sidebar]] entries. There is no default
navigation to fall back on — every document directory must define its own
[[sidebar]] table.An explicit sidebar = [] is rejected for the same reason.
Hard stop 2: removed and renamed configuration keys
sidebar_structures_dir was removed from ude_global_config.json. It is trapped explicitly rather than ignored, so a stale key fails loudly:
text
sidebar_structures_dir is removed as of [GAP-32-Debt-2]; folder taxonomy is now sourced from sidebar.toml [groups].Remove the key and move its content into sidebar.toml's [groups] table.
renderer.type must be a renderer family key, not a renderer class name. Configs naming a concrete class (CppHtmlODARenderer, PyHugoDefaultRenderer, …) fail with:
text
Unsupported format '<value>' in target config.The renderer's own factory selects the language-specific subclass from collector.language, so the family key is all you supply:
renderer.type | Output |
|---|---|
html, static_html | Standalone static HTML |
oda_html | ODA-conventions HTML |
hugo_markdown, markdown, hugo | Hugo Markdown |
oda_hugo_markdown, oda_markdown, oda_hugo | ODA-conventions Hugo Markdown |
Unknown keys elsewhere in ude_global_config.json are still ignored silently (extra="ignore"), and every field has a default — a config missing a field will not raise.
Typed entity models
The untyped ClassEntity has been replaced by strict Pydantic models. The change you are most likely to feel is in class fields:
- v1.0:
fields: ["int count", "string name"] - v2.0:
fields: List[VariableModel], each with.name,.typeand.docstring
If you maintain a custom renderer or any code that reads the IR, replace string parsing with attribute access. Seven typed models ship in total, covering variables, constants, enums, type aliases, parameters, overloads and methods.
Existing imports keep working. ClassEntity, NamespaceEntity and MethodEntity remain as module-level aliases of the new model classes and are explicitly maintained for external consumers — you do not have to rename imports as part of this migration.
ProjectCatalog metadata
ProjectCatalog gained project_name and version. Both are optional, defaulting to an empty string — no action is required, and neither belongs in ude_global_config.json. They are IR-level metadata populated from your document config; set project_name in ude_doc_config.json if you want it carried into the IR.
Decoupled CLI
The v1.0 flat interface is unchanged and still supported — ude --doc-config … behaves exactly as before, and ude compile is its explicit equivalent. v2.0 adds subcommands that split parsing from rendering, which is what you want if you plan to cache or archive the IR between CI stages.
Note that the IR is gzip-compressed; use a .json.gz extension.
Generate IR:
bash
ude parse --doc-config path/to/ude_doc_config.json --output-ir catalog.json.gzRender from IR:
bash
ude render --input-ir catalog.json.gz --output ./public --format html--output-ir is required for parse; --input-ir and --output are required for render. Both accept --global-config / -g and --doc-config / -d.
Coverage auditing and ude audit
Two new GlobalConfig fields drive the documentation coverage gate:
coverage_mode—allow-undocumented(default, reports only) orreject-undocumented(fails the build).coverage_threshold— a fraction between0.0and1.0, default1.0.
Unit mismatch, worth pinning to memory. The config field is a fraction (
0.98), while the CLI flag is a human-friendly percentage (--threshold 98, valid range 0–100). Passing--threshold 0.98is accepted but means 0.98 percent, which will pass almost anything. The CLI divides by 100 before handing the value to the gate.
--mode and --threshold override the config values when given explicitly.
Exit codes
| Code | Meaning |
|---|---|
0 | Audit completed; gate passed, or mode is allow-undocumented |
2 | Gate rejected the target: reject-undocumented and coverage below threshold |
Actual output format
ude audit writes a Markdown report to stdout: a summary header followed by one row per entity — not per module.
markdown
# Documentation Coverage Audit
**Total Entities:** 4948
**Documented Entities:** 969
**Overall Coverage:** 19.58%
| Type | Entity Name | Documented |
| :--- | :--- | :--- |
| Class | `ODA::Publish::PdfPublish::OdPdfPublish_Od2dGeometryBlock` | PASS |
| Method | `ODA::Publish::PdfPublish::OdPdfPublish_Od2dGeometryBlock.Format` | FAIL |On a large SDK this is thousands of rows — redirect it to a file rather than into a CI log, and use the exit code for the pass/fail decision.
GitHub Actions integration
yaml
- name: UDE Coverage Audit
shell: bash -euo pipefail {0}
env:
PYTHONPATH: engine
run: |
python -m ude.cli audit \
--doc-config path/to/ude_doc_config.json \
--threshold 98 \
--mode reject-undocumentedWhen embedding the engine as a library instead, apply_coverage_gate() raises UdeException rather than calling sys.exit(), so a host application is never killed by a coverage failure.
Also new in v2.0 (no migration action required)
These arrive automatically and need no configuration change:
- Unified logging — one
uderoot logger driven bylog_level/log_file. Importingude.configwithout callinglogging_setup()produces no output, per the standard library contract, so embedding the engine stays quiet by default. - L2 render cache — unchanged entities skip re-rendering on repeat builds.
- Three-tier Doxyfile merge — key-level merge across global, SDK and document tiers, resolved via
global_templates_dir. - Public library API —
UdeOrchestratorexposesparse,renderandrunfor embedding without going through the CLI.
Step-by-step migration checklist
- [ ] Add a
sidebar.tomldeclaring at least one[[sidebar]]entry to every directory containing aude_doc_config.json, Hugo variants included. The build fails on a missing, empty or malformed file — there is no default navigation. - [ ] Delete leftover
toc_*.jsonfiles; move any folder taxonomy intosidebar.toml's[groups]table. - [ ] Remove
sidebar_structures_dirfromude_global_config.jsonif present. - [ ] Change any
renderer.typenaming a concrete renderer class to the corresponding family key from the table above. - [ ] Update custom renderers and IR consumers to read
VariableModelattributes (.name,.type,.docstring) instead of parsing strings. LegacyClassEntity/NamespaceEntity/MethodEntityimports need no change. - [ ] Optional: adopt
ude parse/ude renderif you want the IR as a separate CI artifact.ude compileand the v1.0 flat flags keep working. - [ ] Optional: set
coverage_modeandcoverage_threshold, and addude auditto CI — remembering the fraction-vs-percentage distinction.
Verifying the migration
bash
# 1. A v1.0 config still compiles through the flat interface
ude --doc-config path/to/ude_doc_config.json
# 2. A previously saved v1.0 IR still loads
python -c "from ude.storage import load_compressed_ir; load_compressed_ir('old_catalog.json.gz')"
# 3. Split pipeline produces the same output as a single-pass compile
ude parse --doc-config path/to/ude_doc_config.json --output-ir /tmp/ir.json.gz
ude render --input-ir /tmp/ir.json.gz --output /tmp/split --format html
ude compile --doc-config path/to/ude_doc_config.json --output /tmp/single --format htmlRelated Docs: (Paths below are repository-root-relative.)
user-docs/docs/cli-reference.md— full reference for the subcommands introduced hereuser-docs/docs/changelog.md— condensed v1.0 -> v2.0 release notesuser-docs/docs/global-settings.md—ude_global_config.jsonfield referenceuser-docs/docs/target-settings.md—ude_doc_config.jsonfield reference
