0.37.x

0.37.0

Released:

14.10.2026

The story of this release, in five arcs: derived fields, in lockstep with Sphinx-Needs 9.0.0 — a derive rule in ubproject.toml computes a field or a link type for every need — a copy, a roll-up, a transitive maximum, a hash or a fingerprint of the whole need — with the same values in both tools; dynamic functions you can rely on — [[…]] and <<…>> resolve in dependency order, filter= is evaluated at last, backlinks and dead-link flags are readable, results are type-checked as Sphinx-Needs checks them, and an edit re-resolves only what it moved; sphinx-test-reports in ubCode — [test_reports] and all six directives, from one need per JUnit report to result tables and the report template, in the editor, the preview, the site and the PDF; needs written in code — multi-line needs in source comments with a body in reStructuredText or Markdown, TypeScript and JavaScript sources, and Python docstrings; and a PDF that draws Mermaid and takes your classes — ubc build pdf (alpha) draws Mermaid in a browser you already have, sets type and colour by class, prints appendices and draws video posters. Also: choose / when / otherwise, Pharaoh’s Intent from Code (alpha), variant data in the shared [variants] table, one hue per token role in highlighted code, allowmixing PlantUML diagrams that keep their content, and -c overrides that compose. New warnings can fire on an unchanged project (see Breaking Changes).

This release is the ubCode half of a lockstep with Sphinx-Needs 9.0.0: declared derived fields (derive), including the fingerprint kind and rules per need type (derive_by_type), are read by both tools from this release and 9.0.0 onward, with the same values and findings on a shared conformance corpus. A Sphinx build with an older Sphinx-Needs ignores these keys without a word, and ubCode says so once per project (config.derive_sphinx_minimum, which names 9.0.0).

✨ Computed values

  • A field a rule computes, the same in ubCode and Sphinx-Needs 9.0.0. A derive table on a [needs.fields.<name>] entry computes the field for every need: copy a value from the need, another need or its link targets; sum, count, min or max (also transitive) over the needs it links or that link to it; any / all, collect and hash, narrowed with where. No author can set the field (needs.derive_authored), needs.json marks it readOnly, and a rule ubCode cannot read is config.derive_invalid while the configuration still loads. See Derived fields.

  • Derived link types. On a [needs.links.<name>] or [[needs.extra_links]] entry, derive = { kind = "links", where = "…" } links every need passing the filter and kind = "content_links" the needs the content references with :need:; include_parts = true offers need parts too, linked as <need>.<part>.

  • A fingerprint of the whole need, to notice an approved need changing. derive = { kind = "fingerprint" } holds a SHA-256 of the need’s id, type, title, status, tags, content, declared fields and links, so a link condition such as :links: REQ_1[fp == "…"] reports needs.link_condition_failed once REQ_1 changes; exclude leaves names out. See The fingerprint.

  • Rules per need type, derive_by_type — a table of need type to rule beside derive, overriding it on the types it names; a need of a type without a rule holds the field’s default (else null, or its empty value), and the field is closed on every type. types is no longer a reserved role.

  • Derived fields need Sphinx-Needs 9.0.0 on the Sphinx side. An older Sphinx-Needs leaves the field authorable and unset; the informational config.derive_sphinx_minimum says so once per project, and [lint] ignore silences it once the Sphinx build uses 9.0.0. See Derived fields and sphinx-needs and Derived fields need sphinx-needs 9.0.0.

  • Dynamic functions and variants resolve in dependency order. A [[…]] or <<…>> that reads a value another one computes — another need’s field, a calc_sum over such values, a link list a call computes, a variant condition naming one — now reads the computed value, whatever order the needs and documents are written in; until now it read the value stored before resolution. See When a call reads a value another call computes.

  • filter= is evaluated, and links_from_filter links what a filter passes. The filter argument of copy, calc_sum and links_from_content, and filter_string of check_linked_values, reported until now as not supported, run as Sphinx-Needs runs them, with [needs] filter_data. links_from_filter(filter, include_self=False, include_parts=False) links every need passing it, and id_parent / id_complete are readable by every filter.

  • Backlinks and dead-link flags are readable. A call or a variant condition reads a need’s backlinks as <link type>_back — copy('links_back'), <<['REQ_1' in links_back]: linked, unlinked>> — and has_dead_links / has_forbidden_dead_links are readable by calls, variant conditions, the :filter: of the view directives and needtable columns, as in Sphinx-Needs.

  • needextend applies by :extend_priority:. Extends apply lower priority first, default 500, as in Sphinx-Needs 9.0.0; an invalid value is reported and that extend skipped. Without the option the order is unchanged.

  • Variant data in the shared [variants] table. ubCode reads [variants] data and data_file, where Sphinx-Needs 9.0.0 and sphinx-mounts 0.3.0 read them; [needs] variant_data / variant_data_file are still read when [variants] sets neither. Earlier Sphinx-Needs releases ignore the table, so keep the [needs] keys until the Sphinx build uses one that reads it. See The [variants] table.

✨ Test reports

  • sphinx-test-reports’ directives, in ubCode. With a [test_reports] table in ubproject.toml (an empty one will do) or sphinx_test_reports in [parse] extensions, test-file, test-suite and test-case read a JUnit report and create a need each, in reStructuredText and MyST, and :auto_suites: / :auto_cases: a need per suite and case. The section declares the three need types and their fields, so a converter’s needs.json imports bare, and each mistake is a positioned needs.test_report_* warning. See Test reports and Test reports: the directives.

  • Report tables and the report template. test-results and test-env draw a section and table per suite or environment, each row coloured by its result, and test-report (reStructuredText) fills sphinx-test-reports’ template, or [test_reports] report_template, with the report’s counts, tables and needs. All six render in ubc build html (alpha), ubc build pdf (alpha) and the preview, the need cards tinted by their result on the site and in the preview. See Results tables and A whole report: test-report.

✨ Needs written in code

  • Multi-line needs in source comments. With [codelinks.projects.<name>.analyse] get_multiline_needs = true (off by default), a comment block from @need to @endneed is a need whose options read like a need directive’s; a src-trace draws its body, parsed in the block’s declared markup, on its card in ubc build html, ubc build pdf and the preview, and the body’s labels, terms and citations register with that page, a name the page already holds being reported. Findings name the source line. See analyse.multiline_needs and Multi-line needs.

  • TypeScript and JavaScript. comment_type = "typescript" (or "ts") reads .ts, .tsx, .mts, .cts, .js, .jsx, .mjs and .cjs files, node_modules, dist, build, out and coverage excluded by default, and a JSDoc tag line is the jsdoc_tag warning rather than a need. The VS Code extension attaches to the four TypeScript and JavaScript language ids; if you override ubcode.server.documentSelector, add them. Python docstrings are now read too (see Breaking Changes). See analyse.

✨ PDF build (alpha)

  • Mermaid diagrams are drawn, in a browser you already have. ubc build pdf draws every authored mermaid diagram, directive or MyST fence, with the Mermaid the site uses, inside a Chrome, Edge, Chromium, Brave or chrome-headless-shell it finds or that [build.mermaid] browser names: headless, on a throwaway profile, every request refused, the pictures cached, and no node needed. It reports how many it drew and how long that took. Without a browser, or with [build.pdf.mermaid] enabled = false, a diagram stays a placeholder box. See Mermaid diagrams.

  • Type and colour by class. [build.pdf.theme.classes.<name>] sets the size, weight and italic of every element carrying that class, and its colors table repaints it in the roles [build.pdf.theme.colors] names — the paper twin of styling a class in the site’s extra_css. A table’s padding and row height scale with its type. sphinx-simplepdf’s ssp-tinier and ssp-tiny are built in at that extension’s values. See Type by class.

  • [build.pdf] appendices prints chosen pages after the body. Pages the root toctree does not reach, listed by name, follow the body in that order under one Appendices heading in the contents and the bookmarks; an entry that cannot be honoured is build.pdf_appendix_refused. See Appendices.

  • A video’s poster is its picture. A .. video:: whose :poster: image can be read is drawn as that picture, sized, aligned and captioned; one without a usable poster stays a box that says why.

✨ Conditional content

  • The choose, when and otherwise directives. A .. choose:: (MyST {choose}) includes its first when whose variant-data condition holds, else its final otherwise, as Sphinx-Needs does. A mistake — an unevaluable condition, a when without one, an otherwise with one or not last — is reported once under choose.* and skips the whole choose, so a misspelt key never shows the default in its place. The editor fades the branches not taken and the builders leave them out. See Choosing one of several branches with choose, when and otherwise.

✨ Pharaoh (alpha)

  • Intent from Code: recover requirements, architecture and user stories from existing code. The new Pharaoh Intent from Code view in VS Code marks each source file not recovered, recovered or changed since; pick files, a stage and the evidence, and Reverse sends the work to your agent session. ubc agent reverse does the same from the command line, keeping a ledger under .pharaoh/rev-eng/ to commit with your documents; [agent.reverse] names the stages and write_code_markers = false stops the codelinks marker it writes into recovered files. ubc agent install --profile vmodel adds six recovery skills. See Recovering intent from code and the verbs.

👌 Improvements

  • Edits re-resolve far less, and schema validation is faster. An edit re-resolves only the calls and variants that read what moved, and a whole-project calc_sum column is summed once per pass: on a 20 000-need project, resolution after a one-need edit fell from about 5.5 s to about a tenth of a second. Schema validation is a quarter to a third faster on large projects, and up to half again where a select reads only the type. Results are unchanged.

  • An imported or external need whose "id" is not its key is reported. It is still read under its key, now with one needs.import / needs.external finding, as Sphinx-Needs 9.0.0 reports it.

  • Configuration errors name what a key accepts. A key that takes two shapes says which at the value itself, instead of “data did not match any variant of untagged enum”, and a key containing a dot is shown quoted (var."a.b").

  • Codelinks markers are read as Sphinx-Codelinks reads them. Ids after @need-ids: may be separated by spaces, every marker on a line yields its references, and three warnings use Sphinx-Codelinks’ wording.

  • One hue per token role in highlighted code, at AA contrast. The palette ubc build html, ubc build pdf and the preview share gives keywords, types, keys, names, numbers, strings, built-ins and comments a hue each, every ink at least 4.5:1 on the code background; keys and values in JSON, YAML, INI and TOML now differ, and an INI section, a diff hunk header, a format placeholder and an HTML entity are coloured.

  • The ubc build html sidebar reads like furo’s (alpha). The whole row is the click target and takes a hover tint, nested entries are muted, the current page’s trail is bold and scrolled into view, links show a focus ring, and three new tokens, --ubc-nav-fg-top, --ubc-nav-fg and --ubc-nav-hover-bg, theme it.

  • allowmixing and bold-link PlantUML diagrams keep their content in the preview and ubc build html. A mixed diagram keeps its classes, enums, objects, actors, use cases and labels, a container its members, o-- and *-- their glyph and direction, and A => B and -[bold]-> draw thick. A needuml or needarch that still draws with lines missing is reported as needs.uml_partially_converted, naming them.

  • Dependencies with security advisories are updated: DOMPurify 3.4.16 (GHSA-55q2-fjhq-7xh7) in the Mermaid the builders and the preview draw with, and brace-expansion 5.0.12 (GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p) in the VS Code extension.

♻️ Changes

  • Highlighted code changes colour on upgrade. Every default --ubc-hl-* colour of the site, every hl-* role of ubc build pdf and the preview’s palette take the new values; no class or token name changes. To keep an old colour, redeclare the --ubc-hl-* property in your extra_css or set the hl-* role under [build.pdf.theme.colors]. See Custom properties.

  • A directive name has one owner. [parse] extensions switches sphinx-test-reports on by name, or off with "sphinx_test_reports" = false while keeping a [test_reports] section; a need type wins over a [parse.extend_directives] entry of the same name; a [needs.fields.<name>] entry named after a link type is reported and ignored, keeping the link type (a field named links used to shadow it), while parent_needs still replaces the built-in. Built-in directive names: see Breaking Changes. See Extensions that carry configuration.

  • ubc agent (alpha) refuses an unknown [agent] key in every file of the extend chain, and the language server runs the same check on each configuration load and shows its configuration-error notification, while the editor keeps using the configuration. ubc agent config-validate names the key’s file and line.

‼️ Breaking Changes

  • One-time full re-index and re-render on upgrade. The cache format moved (6.199 → 6.240) and the build manifest with it (131 → 145). The first run after upgrading rebuilds the index and re-renders every page once, about the cost of a cold build, then incremental behaviour resumes. Self-healing. Diagrams drawn by a configured PlantUML executable are rendered once more too, as their store lives in the same versioned cache. The link-check result cache and fetched intersphinx inventories are not discarded.

  • An external source’s id_prefix is added as written. id_prefix = "p_" now names a need p_X; it was upper-cased to P_X. Sphinx-Needs does the same from 9.0.0 (8.x upper-cases it). If your pages reference the upper-cased ids, write the prefix upper-case in [[needs.external_needs]] — which gives the same ids in both tools and in every Sphinx-Needs release — or update the references.

  • Codelinks’ removed get_rst key stops its project. The never-implemented marked-RST surface is gone: get_rst = true or an [analyse.marked_rst] table is reported and that codelinks project is skipped, its src-trace directives creating nothing, and even get_rst = false is the warning config.codelinks_removed_key. Projects scaffolded by ubc agent carry the line twice, and ubc agent update does not rewrite ubproject.toml: delete the lines.

  • Codelinks reads Python docstrings, and source positions move. Unchanged Python sources can gain needs and references from their docstrings, and a docstring line whose start sequence is followed by an Epydoc field (@param a: text) is the docstring_tag warning; choose a start sequence your docstrings do not use, such as [[. Of two one-line needs with one id on one row the leftmost is created, a need referenced from several places gets the URL of its first reference by path, row and column, and columns count characters, not bytes.

  • Codelinks warnings in the build have their own codes. A malformed marker in a file a src-trace reads was reported as needs.src_trace at the directive; it is now reported at the marker’s source line under codelinks.<code> (codelinks.too_many_fields, …). ignore = ["needs.src_trace"] no longer silences them; ignore = ["codelinks.*"] does. See Real-time diagnostics.

  • A dynamic function’s result must fit its field. A number, boolean or list returned into a string field, a list into an integer, number or boolean field, an item of another type into an array field and nothing into a field that may not be empty are needs.dynamic_function warnings that leave the field empty, as in Sphinx-Needs; ubCode used to store "7.0" or "True". Declare the field with the result’s type (schema = { type = "number" }) to keep the value.

  • Values on a cycle of reads, or read out of order, are warnings. needs.derive_cycle reports a value that reads itself through what it reads — among them a variant reading the field it fills, such as :status: <<is_open: active, inactive>> with is_open = "status == 'open'", which earlier documentation showed: put the variant on another field. needs.derive_scope reports a read the resolution cannot order, such as a link field reading a computed value or a backlink, or a needextend filter naming a computed field. Both leave the value empty.

  • An if with no variant data configured excludes its body. Every .. if::, .. if:: True included, now reports if.invalid_expression and leaves its body out when the project configures no variant data, as Sphinx-Needs does: configure [variants] data (or data_file), or remove the if. Errors inside a falsy if body are no longer reported, and a default-role, role, highlight, default-domain, content-less rst-class or substitution definition inside one no longer affects the rest of the document.

  • A need type named like a built-in directive takes the name. A [[needs.types]] entry whose directive is note, todo, needtable, src-trace or another built-in name is now that need on every layer — parsed, indexed and drawn, its title kept — and reported as config.conflicting_directive_registration; with a [test_reports] section, test-file, test-suite, test-case, test-results and test-env belong to sphinx-test-reports instead. Give the type another directive.

  • Repeated -c overrides no longer join into one document. Each -c is its own TOML document: -c "[needs]" -c "variant_data_file = 'v.json'" no longer opens the table for the second, whose key is then top-level and ignored — write it in the same -c or dotted (-c "needs.variant_data_file = 'v.json'"). A key repeated across -c takes the last value instead of failing, and a failure names its snippet. ubc serve lsp and ubc serve mcp read UBCODE_CONFIG_OVERRIDE before the -c values, so a -c wins.

  • An unchanged project’s PDF comes out differently (ubc build pdf, alpha). Mermaid diagrams are drawn wherever a browser is found ([build.pdf.mermaid] enabled = false keeps the boxes); highlighted code takes the new palette; an element classed ssp-tiny or ssp-tinier is set at half or three quarters of its size (size = 1.0 under its [build.pdf.theme.classes.<name>] turns that off); a generic admonition has a title band, and one classed with a kind’s name is drawn as that kind; a video with a readable poster shows it; and DejaVu’s own private-use glyphs are no longer drawn — list an icon font under [build.pdf] fonts for them.

  • New warnings may appear on an unchanged project. Beside the codes above, each failing ubc check and both builders on the default gate unless listed under [lint] ignore: needs.filter (a filter= once skipped fails on some needs); config.variant_data_location ([variants] and [needs] variant_data* both set, the latter then ignored); config.variants_unknown_key (another key in [variants]); config (a field named after a link type); needs.uml_partially_converted (see Improvements); and in ubc build pdf, build.mermaid_render_failed (a diagram Mermaid rejects) and build.pdf_glyph_unavailable (a character missing inside a picture, or a private-use one). Variant-data findings move to config.variant_data_invalid, beyond a bare "config" ignore, and values the schema never allowed, such as a date-time in [parse.extensions], are refused.

🐛 Fixes

  • A link type’s own default and predicates are read. On [needs.links.<name>] or [[needs.extra_links]] they were ignored without a word; they now fill the link as in Sphinx-Needs, as [needs.global_options] already did.

  • A :delete: need is gone, body and all. A need whose :delete: is truthy no longer draws an empty card holding its body in ubc build html and ubc build pdf, nothing written inside it takes effect, its links are not checked and its images not copied, and the editor fades the whole directive.

  • A warm build gives a clean build’s answers. After an edit, [[copy('lineno')]] and lineno conditions on a needimport or src-trace need, a modifications count after a second matching needextend and a backlink read through need.links_back are recomputed; a whole-project calc_sum adds in need-id order, so a decimal total no longer differs between builds; and a need’s references keep one order.

  • links_from_content() and linked sums. An empty links_from_content() gives the empty list without a finding and a need_id naming no need is reported by name; calc_sum(…, links_only=True) and check_linked_values skip a dead link, which the dead-link finding already reports, instead of failing.

  • Variant data and render context are read as written. A -c "needs.variant_data.<key> = …" override, or an inline table in a file that extends another, merges key by key instead of replacing the table, an invalid value in the data file is reported at its key, and a TOML date-time in the variant data or in [needs.render_context] is reported instead of being read as a nested table.

  • network_back schema messages list incoming needs in id order, so a message no longer changes order between runs of an unchanged project.

  • Children of an orphan page are not “not included”. toctree.not_included no longer reports the pages a toctree lists on a page no toctree reaches — the children of an :orphan: hub — in ubc check, ubc build html and ubc build pdf, as in Sphinx; ubc build pdf warns build.pdf_documents_excluded only for a page that is neither listed, :orphan: nor included, and names the others on a Left out: line.

  • A contents inside a need’s card no longer moves the page’s anchors. A .. contents:: in the content a needextract, needimport, needreport template or src-trace card shows numbered its entries in the page’s own id1, id2 … sequence, so a :ref: to a later target on that page could land on another element; its entries now take ids of the card’s own.

  • A class and a tab set see through if and choose. A content-less rst-class just before an if or choose lands on the first element the branch taken shows, instead of reporting directive.class_unsupported_target, and a tab-set holding them renders the tab-item directives they show instead of reporting directive.tab_structure_invalid, as in Sphinx-Needs.

  • In a PDF, a character is drawn from the face prose would use, and a missing one is reported, in pictures and code too. An inline literal, a listing or a :kbd: draws a character the monospaced face lacks from the sans face, keeping its chip; prose falls back to the monospaced face; a font listed under [build.pdf] fonts draws its private-use icons; and a character no face has inside a chart, a gantt, a drawn diagram or an SVG image is reported as build.pdf_glyph_unavailable instead of drawn as a box with no finding.

  • In a PDF, references and titles read as on the site. A named reference, an embedded <#label> fragment and a target pointing at #REQ_1 are links to what they name; a source line break inside a reference title, a need title, an :abbr: explanation or a :math: span is a space, and a need card’s title is one line.

  • In a PDF, a generic admonition draws its title band and icon, and .. admonition:: Title with :class: warning (or another kind’s name) is drawn as that kind, as on the site. Lines next to an inline literal, a :kbd: or inline maths are evenly spaced, and a character from a fallback font no longer moves its line or grows a literal’s box.

  • VS Code no longer offers ubCode as the formatter for source files. C/C++, Python, Rust, C#, Go, YAML, shell, Markdown and TypeScript files were offered a formatter that changed nothing; the language server now registers formatting for reStructuredText documents only, which format as before.