0.39.x

0.39.0

Released:

04.11.2026

The story of this release, in five arcs:

  • Numbering, as in Sphinx (beta) — sections, figures, tables, code blocks and equations are numbered from one model, [build.numbering], and :numref: and :eq: print the number, in the preview, the editor and both builds.

  • With Sphinx-Needs 9.2.0: where needs come from, and per-type models — a filter reads where a need comes from (c.source) and follows inherits (c.is_a); a need type grades its declarations, counts the links it receives and shapes its ids; summary link types.

  • Code coverage and semantic codelinks (alpha) — every marker names the code entity it documents, ubc codelinks coverage lists the functions that trace to no need, in the editor too, and ubc diff lists the needs whose code changed since it was linked.

  • Directives of your own (experimental), with sphinx-ub-directives 0.1.0 — directive presets and template directives, declared in ubproject.toml and read alike by sphinx-build.

  • Test reports, with sphinx-test-reports 4.0.0 and ub-test-reports 2.0.0 — a test-report type takes an entry of its own in [needs.types], and pytest’s lines are read from the right base.

Also in this release:

  • The language server counts columns in UTF-16, so navigation on a line with non-ASCII text lands on its target.

  • Every needs.json reader checks format_version first, and a need that is not an object no longer drops the file.

  • Directive option names match ignoring case, as in docutils.

  • Typos in field and link-type entries are reported.

  • ubc serve mcp exits promptly when its client goes away, and release binaries are about 7 % smaller on Linux.

Numbers appear and new warnings can fire on an unchanged project (see Breaking Changes).

This release is the ubCode half of a lockstep with Sphinx-Needs 9.2.0: c.source, c.is_a, summary, copy and collect on a link type, empty, where on copy, and severity, back_links and id_regex on a need type are read by both tools from this release and 9.2.0 onward, with the same answers on the shared conformance corpora. Marker attachment and generated ids follow sphinx-codelinks 2.1.0, test-report types and line_base sphinx-test-reports 4.0.0 and ub-test-reports 2.0.0, presets and templates sphinx-ub-directives 0.1.0.

✨ Numbering (beta)

  • Sections, figures, tables, code blocks and equations are numbered. One model, [build.numbering], decides which items carry a number, how it is written and what a numbered reference prints. With nothing configured, ubCode numbers what Sphinx numbers by default: the sections of the documents a :numbered: toctree lists, and every labelled display equation. Captions (Fig. 1.2, Table 1, Listing 1) are numbered once figure, table or code is enabled; sections.from = "all" numbers every chapter and equation.all every display equation. A table gives the ubCode key for each Sphinx setting. See Numbering.

  • Numbered references. :numref: ({numref}) and :eq: / :math:numref: ({eq}) print the target’s number and link to it. A missing label is std.numref or std.eq; a target with no number still links and says why (std.numref_not_numbered, std.eq_not_numbered); a document two numbered toctrees both number is toctree.secnum. In reStructuredText, hover shows the number and go to definition opens an equation’s math block; MCP resolve_reference answers with number. See In the editor and for agents.

  • The site and the PDF, each with its own switches. [build.html.numbering] and [build.pdf.numbering] inherit every key they leave unset and take suffix and show (on the site sidebar, prev_next and title are off by default). Pages listed in [build.pdf] appendices are lettered (A.1, Fig. A.1), and a :numref: to one reads Appendix A. Each build checks a reference against the numbers it prints, so one into an appendix is reported by ubc check and ubc build html, not by the PDF build. See Numbering sections, figures and equations, Numbering and Appendices.

  • docutils sectnum. .. sectnum:: (.. section-numbering::, MyST {sectnum}) numbers its page’s headings, with :depth:, :start:, :prefix: and :suffix:, and a .. contents:: on the page lists the same numbers. See Numbers for the page’s headings (sectnum).

✨ With Sphinx-Needs 9.2.0: where needs come from, and per-type models

  • Filter needs by where they come from: c.source. c.source.kind (page, import, external, code, report), c.source.path and c.source.line (the file the need is written in, an included file rather than the page), c.source.paths (every file it came through, innermost first) and c.source.document / c.source.sections, in every filter that selects needs; refused, naming the place, in a field’s predicates, a needimport :filter: and a variant condition. See Where a need comes from: c.source.

  • Filters and Cypher follow inherits. c.is_a('req') is true for a req and for any type inheriting from it; type == 'req' still compares the name. A c.is_a literal naming no need type is needs.filter_type, with a did-you-mean, so a misspelt type there no longer empties a view in silence. In Cypher a sys_req need is (:sys_req:req), and n.type stays its own type. See Special functions and Need types and inherits.

  • A need type grades, counts and shapes. severity = "violation" | "warning" | "info" on a type’s fields or links entry grades its failures (severity); [needs.types.<t>.back_links.<l>] schema = { minItems = 1 } counts the links a need of the type must receive (incoming links); id_regex on a type is matched beside [needs] id_regex and each ancestor’s (the ids of a type).

  • Summary link types, and a trace’s far ends as links. summary = true marks a link type whose links are shortcuts across the trace: a needflow without :link_types:, ubc agent’s orphan gate and impact walks and ubc diff’s impact chains (unless --allow-link names it) leave it out, and the Need Viewer tree shows it one hop deep. copy of id on the first rung and collect below give every need of a trace the requirements it reaches as links. See Summary link types and A chain as links: copy and collect on a link type.

  • Derived fields: empty and where on copy. empty = false on an all rule makes a requirement that nothing tests read false; a copy with over or join takes a where. See any and all and copy.

  • Imported and traced needs stand where their directive does: a need from needimport or src-trace has its directive’s sections and the need it is written inside as its parent_need. See Importing needs.

✨ Directives of your own (experimental)

  • Directive presets. [parse.directive_presets.<name>] declares a directive that is an existing one with preset options (options, append_options, force_options, argument); a preset named like its base gives that directive project-wide defaults. A use renders, is indexed and is checked as its base, and the editor completes it. See Directive presets.

  • Template directives. .. ub-template:: renders a Jinja template with data from [parse.template_contexts] and inserts the result in the page; generated needs are ordinary needs. [parse.directive_templates.<name>] declares a template directive of your own. Templates are sandboxed and bounded, and see the variant data as var. .. ub-template:: is switched on with [parse] ub_template_enabled = true; a declared template directive needs no switch. See Templates: generating content from data.

  • The same tables build with Sphinx: sphinx-ub-directives 0.1.0, a new Sphinx extension, reads them for sphinx-build, and both tools pass its two conformance corpora. See With Sphinx.

✨ Test reports, with sphinx-test-reports 4.0.0 and ub-test-reports 2.0.0

  • A test-report type takes an entry of its own. [needs.types.testcase] (or testfile, testsuite), bare or with a title and a prefix, narrows the type [test_reports] adds: its fields, links, inherits, back_links, id_regex and severity apply, so a link type or a rule can sit on the test cases themselves. On the Sphinx side this needs Sphinx-Needs 9.2.0 and sphinx-test-reports 4.0.0. See What the section adds.

  • pytest’s lines, read from the right base. case_line is the test’s own line for pytest’s JUnit reports, which count from 0; a ub_test_reports:line_base mark or [test_reports] line_base ("auto", 0, 1) decides the base. See Lines of pytest reports.

👌 Improvements

  • A diagram that falls back to its placeholder keeps its number, so later figure numbers and a :numref: to it match the PDF.

  • One number per equation. KaTeX and the PDF’s typesetter no longer number an unlabelled align or an unstarred equation on their own, as with Sphinx’s MathJax; an explicit \tag{…} is still drawn. See Numbered equations.

  • needs.json readers check format_version first, refusing a later format as Sphinx-Needs’ reader does; a need that is not an object is skipped and reported, where it dropped the file; a need with no id has its key as id; and a needs.json older than Sphinx-Needs 4.0 imports its needs’ description as content. See External needs.

  • Configuration typos are reported: an unknown key in a field or link-type entry, or an unknown schema keyword, is a config warning, where it was dropped in silence, and a name close to a declared one is config.type_near_name. See Fields.

  • Hover on a link option shows its target_types, and ubc query filter reads what a view reads (back-link fields, the dead-link flags, var.*, [needs.filter_data]). See Backlink fields.

  • A variants data-file override swaps the file, never the table: -c "variants.data_file = '…'" and -c "needs.variant_data_file = '…'" are one override, as -D needs_variant_data_file is in Sphinx-Needs. See A data-file override swaps the file, never the table.

  • The editor’s agent views follow ubcode.server.configOverride (alpha), Tailor workflow splices [workflow] as ubc agent install --detect does, and ubc agent next says which open streams can continue (in_graph, actionable) and names its arm, the one action run and prompt take (author, review, refix, fix_diagnostics or none). See Settings and Ask for the next step.

  • ubc agent reverse status --summary (alpha) prints a compact answer that stays small on very large projects: single-line JSON, each recovery unit’s counts once, and dropped lists replaced by counts. See ubc agent reverse status.

  • ubc serve mcp --tools NAMES serves only the tools it names (--tools query_cypher,get_graph_schema); the others are neither listed nor answered, and an unknown name stops the server at startup. See Serving fewer tools.

  • ubc serve mcp exits within about six seconds when its client goes away, where it waited for every abandoned tool call.

  • Smaller release binaries (about 7 % on Linux), from whole-program link-time optimisation and compressed PDF fonts.

  • Dependencies with security advisories are updated: DOMPurify 3.4.17 (GHSA-cg2g-xh64-v4q7, critical, and two more) and KaTeX (GHSA-238p-pmpm-9mq7) in the Mermaid bundle; zstd 0.14 (RUSTSEC-2026-0391, -0398, -0403, -0410), which decompresses the embedded assets; ctutils 0.4.5 (RUSTSEC-2026-0385), under the licence check; and tokio 1.54.1 (RUSTSEC-2026-0386), under the MCP server.

♻️ Changes

  • Directive option names match ignoring case, as in docutils and Sphinx-Needs: :Status: open sets the status, on need directives, needextend and the test-report directives, in reStructuredText and MyST; of two MyST options matching one name the exact spelling wins. Declared names that differ only in case are a config warning. See Components and markup.

  • [needs] id_regex is applied as Sphinx-Needs applies it: from the start of the id (Python’s re.match), in Python’s syntax; a pattern ubCode cannot read is reported. ubc schema validate tags a violation sn_schema_violation (was sn_schema_error).

  • A map iterates in the order it was written, as a Python dict does, on every Jinja surface: ubc report templates and insights, needreport, needuml and needarch, need templates, an external source’s target_url, string links and ubc agent’s prompt templates. Output that iterated a map may reorder; |dictsort restores the sorted order. Templates also gain {% break %}, {% continue %} and urlencode.

‼️ Breaking Changes

  • One-time full re-index and re-render on upgrade. The cache format moved (6.256 → 6.287) and the build manifest with it (147 → 154): the first run after upgrading re-indexes every project and ubc build html re-renders every page; nothing is lost and nothing needs doing. The link-check result cache and fetched intersphinx inventories are not discarded.

  • New warnings can fire on an unchanged project, and fail the default gate of ubc check and the builders until ignored or fixed: std.numref, std.numref_not_numbered, std.eq, std.eq_not_numbered, toctree.secnum, build.numbering_mismatch, build.pdf_numbering_mismatch and build.pdf_equation_number_kept; needs.filter_type; config for an unknown entry key or schema keyword, an enum on a boolean, or names that differ only in case; config.type_near_name, deliberate plurals included; and template.switched_off for a .. ub-template:: a page already held. The likeliest is a :numref: to a figure while figure numbering is off, the default, as Sphinx warns: [build.numbering.figure] enabled = true. See Configuration diagnostics (config and config.*).

  • Numbers appear on an unchanged project: a :numbered: toctree numbers its sections, a labelled display equation prints (N) and a .. sectnum:: numbers its page, in the site and the PDF. An unlabelled equation loses the number its engine drew: label it, or set [build.numbering.equation] all = true. [build.numbering] enabled = false prints none (in a builder table, for one output; show drops one surface).

  • Cypher: a need’s labels are its type lineage, so MATCH (n:req) also matches the types inheriting req; WHERE n.type = 'req' keeps the old answer.

  • A failed per-type declaration at violation is an error, where needs.invalid_field_value and needs.invalid_link_value were warnings: --deny error now fails on them, and --max-warnings no longer counts them. severity = "warning" restores the old grade.

  • Test-report directives without :id: fail the default gate: their generated ids, in lower case, fail the default [needs] id_regex (needs.id_regex), as a Sphinx build refuses those needs. Give them an :id:, set an id_regex they match (a test-report type’s own id_regex checks them too), or ignore the code.

  • Test-report fields belong to the test-report types: a need of another type that sets case_name or result gets needs.undeclared_for_type; declare the field on that type or under [needs.fields]. case_parameter, case_file and case_line are null where the report gives none (case_line is None, not == '').

  • pytest test cases move one line down: case_line from a plain pytest --junitxml report is one more. [test_reports] line_base = 1 reads unmarked reports as before.

  • Some [test_reports] extra_options names are refused, as sphinx-test-reports refuses them (config.test_reports_extra_option_reserved), and a directive writing one makes no need: remove the name. See the refused names.

  • Codelinks markers without an id become needs (see Fixes): two with one title are needs.duplicate; give them distinct titles or ids. A marker is no longer attached across code to a later entity.

  • A need’s parent_needs is its nesting: an imported record’s stated parent_needs is no longer kept; carry the relation in a link type of your own.

  • A needs.json older than Sphinx-Needs 4.0 imports content, which can raise needs.import.denied_asset or needs.link_ref: allow the assets with allow_assets = true on the source (assets_root scopes them), or fix the references.

  • Answers change on an unchanged project: an option that was ignored for its case now sets its field; two data-file overrides naming different files are refused (pass one); docname in a field’s or link type’s default predicate names the page for a src-trace need; docname in a link condition names the target’s page; a [needs] id_regex that matched anywhere may report ids; json_remove_defaults leaves out links equal to a link type’s default; a template that iterated a map may reorder; and the commands ubc agent (alpha) writes out for an agent carry more flags, and its editor views refuse a malformed [workflow] override.

  • An unchanged project’s PDF comes out differently (beta): numbered headings, captions and equations, and lettered appendices; the [build.pdf.numbering] keys switch each off.

  • Lockstep with Sphinx-Needs 9.2.0: a project adopting c.source, c.is_a, summary, copy or collect on a link type, empty, where on copy, back_links, id_regex on a type or severity needs Sphinx-Needs 9.2.0 on the Sphinx side, and a [needs.types.testcase] entry sphinx-test-reports 4.0.0 too.

🐛 Fixes

  • A codelinks marker written without an id is a need, with the id sphinx-codelinks 2.1.0 gives it, where it was dropped without a word; a marker whose id slot is empty makes no need. See generated ids.

  • Google Test’s value_param and type_param reach case_parameter, where it was null.

  • Navigation on lines with non-ASCII text: go to definition, references, hover and completion count columns in UTF-16 units, as the editor does, and initialize declares positionEncoding: "utf-16"; diagnostic columns are not converted yet. See Use ubCode from any editor.

  • Navigation to an imported need in an included file lands in that file, not the host page, and a need there whose id fails needs.id_regex is reported on its own :id:.

  • An external source’s target_url that renders an absolute URL is used as given, and version = "" reads its current_version; ubc build validate-json and ubc diff report a need whose "id" is not its key.

  • ubc agent (alpha): config-validate and doctor read need and link types as a build does; the commands it writes out for an agent carry the call’s -p, -c and --config-name, quoted for the shell; install --detect keeps the tables after its [workflow] block; and change requests no longer crowd the needs graph view (its CR button shows them) or the stream lists.

  • Run QA scores the answer, not the reasoning before it (alpha): a model’s reasoning object before its answer no longer hides the scores, and a response with unknown or repeated axis IDs or conflicting scored objects is refused rather than mis-attributed, the existing verdict kept. See Running a review.

  • Deeply nested documents no longer crash ubc on Windows: every input-driven thread runs on an 8 MiB stack.

  • A template that calls '…'.count('') (an empty substring) now gives a template error instead of hanging the render forever, on every Jinja surface: template directives (in the editor too), ubc report, string links, needreport, needuml and needarch, need templates and ubc agent’s prompt templates.

  • An imported or external need whose string field holds a JSON number is kept, the value as text (3 becomes "3"), where the need was dropped with a warning; ubc build validate-json stays strict.

  • ubc codelinks coverage no longer crashes on a non-ASCII identifier (über, straße) (alpha).