0.32.x

0.32.0

Released:

20.08.2026

The first stable release since 0.30.3, consolidating the 0.31.x pre-release series together with everything landed since — the 0.31.x notes carry the full detail of each pre-release.

The story since 0.30.3, in six arcs: the rendered preview grew up — need cards, cross-file links and far wider markup, hydrated live from the index; the HTML build went publicubc build html builds your whole project as a static site (alpha), with furo-parity page chrome, offline math, search, an objects.inv and true incremental rebuilds; the needs view family is complete — all seven view directives now render, with needpie, needbar and needgantt drawing real deterministic charts, and need cards became a designable surface; openCypher became the needs query language, with intersphinx cross-project references beside it; warnings became gatesubc check is a whole-project, severity-aware CI linter, and the HTML build now fails on the same findings; and Pharaoh — ubCode’s experimental agentic engineer — made its debut, with an in-editor agent panel, MCP reference tools and documentation grounding.

✨ The HTML build (alpha)

  • ubc build html is public, as an alpha

    The static-site builder landed across the pre-release train: a user guide, offline KaTeX math, sphinx-design components, furo-parity page chrome (hover permalinks, tinted admonitions, scrolling tables), document topmatter as metadata, and coverage radars that say what could not be rendered. It is marked alpha: it works and is under active development, but markup, theming and options may still change between releases.

  • The furo theming surface fills in

    [build.html] gains furo’s own keys, semantics verbatim, so an existing html_theme_options block ports across by copy: announcement (a site-wide banner strip), footer_icons, hide_attribution, and the five source_repository family keys that put furo’s view-this-page / edit-this-page buttons in the content-icon row — with the common host URL shapes built in, and misconfigurations reported instead of emitting a dead button. extra_static publishes the files a stylesheet references (a directory’s contents are copied preserving structure into _static/, so the sheet’s own relative url(…) still resolves). See the [build.html] reference.

  • The theme toggle is drawn, and lives where furo puts it

    The light/dark/auto toggle drew its states as Unicode characters, which showed as an empty box on systems whose fonts lack them — reported in the field. All three states are now inline SVG, and the toggle moved into the content-icon row at desktop widths and the sticky mobile header at phone widths. The row floats over the content instead of reserving a strip, so pages are about 42 pixels shorter, and a project that configures nothing still gains the row at desktop widths.

  • ubc build html writes objects.inv

    A built site is now an intersphinx target: the output root carries a standard Sphinx inventory of the project’s documents, labels and glossary terms, written on every build exactly as sphinx-build does. Another project — via ubCode’s [intersphinx.projects] or Sphinx’s own intersphinx_mapping — can point at the published site and resolve :ref:, :doc: and :term: into it. Measured against sphinx-build on the sphinx-needs documentation, the two inventories are identical. Set [project] name and version so the inventory identifies itself.

  • needs.json can be built with the site

    The [needs] keys build_json, json_exclude_fields, json_remove_defaults, json_include_link_conditions and reproducible_json are now supported: ubc build html emits needs.json alongside the pages, shaped exactly as Sphinx-Needs shapes its own export.

✨ Need views & charts

The sphinx-needs view directives arrived across the train — needlist, needtable and needflow in 0.31.1b1, needsequence as a Mermaid sequence diagram in 0.31.2b1 — and the family is completed in this release:

  • needpie and needbar draw real charts

    Each renders an inline SVG plus the numbers as text, in the preview and the built site alike, counted through the same machinery :need_count: uses. Inline SVG buys what Sphinx-Needs cannot offer: byte-identical output for the same inputs, correctness on incremental builds, an accessible named-and-described chart, and rendering without JavaScript and in print. Fifteen authoring mistakes that abort a Sphinx-Needs build each become a warning and a deterministic degradation instead. The data table below the plot is shown when you set :legend:. See the differences register for the deliberate divergences.

  • needgantt renders — and solves its schedule

    A real gantt chart: inline SVG, an elbow connector per dependency, a progress track on every task that has one. ubCode computes a day offset per task from the constraint links, where Sphinx-Needs hands the text to PlantUML — so a chart with no :start_date: has a well-defined layout, a constraint cycle is a reported failure rather than a nonsense picture, and a dangling link is an inline notice rather than a build crash. Point the new [needs] keys duration_option / completion_option at fields you already have, or declare the two fields in [needs.fields].

  • needtable: :style_row: and :show_parts: are implemented

    :style_row: puts its value on each need’s table row as a CSS class — through the dynamic-function evaluator per need, so :style_row: needs_[[copy('status')]] colours each row by its own status — and every row now carries the Sphinx-Needs row classes, so a stylesheet written against a Sphinx build works unchanged. :show_parts: renders each need’s parts as extra rows beneath it, carrying the new [needs] part_prefix (default "→ ").

  • needreport renders its report

    .. needreport:: renders the project’s need types, link types, extra fields and usage counts — the same sections a Sphinx build produces — and unlike Sphinx-Needs the usage numbers are real rather than permanently zero. Your own Jinja template runs too: :template: on the directive, or project-wide with the new [needs] report_template key; a template fault is reported and the default report renders instead.

  • A project-wide cap for the views

    The new needs.views_max_items key sets the default :max_items: cap for needlist, needtable, needflow and needsequence (unset it stays at 100, as before; 0 removes the cap), and ubc build html now warns (build.need_view_truncated) whenever a cap drops items from a view.

✨ Need cards

Need cards debuted in 0.31.0b1; this series makes them a designable surface:

  • [needs.card_layouts]: design your own need cards

    :layout: is now honoured, and a card design is a declarative spec: five regions (header, meta, content, footer, a side column) over an element vocabulary (id, title, type, field:<name>, image:<field>, …), with extends inheriting every key you omit and per-element options (height / width on image, label on field). ubCode ships ten designs interpreted by the same code, and an unknown :layout: renders the default design with a warning — Sphinx-Needs raises an exception that ends the whole build. See Card designs with [needs.card_layouts].

  • :style: and a need type’s colour reach the card

    Each :style: token becomes a class on the card, with styling shipped for every name Sphinx-Needs documents (colour fills, *_border frames, *_bar edge stripes and their aliases), and a [[needs.types]] color outlines the type badge and paints the card’s leading edge. Both resolve off the index, so they work however the value arrived — authored, extended, defaulted, variant-produced or imported.

  • A hidden need collapses instead of disappearing

    A need whose hide resolves true renders its whole card inside a closed disclosure. Nothing is withheld: everything nested still renders, every anchor still exists, and the need’s own anchor sits outside the disclosure, so a :need: reference to a hidden need needs no expansion.

  • [needs.string_links]: matching field values render as links

    A rule is a pattern plus two templates; values of the fields it names render as links on the card, in a needtable, in the built site and in the preview. Matching semantics are pinned to a measured Sphinx-Needs oracle; a bad rule costs one warning naming it, where Sphinx-Needs raises out of the first need it renders. See Field links with [needs.string_links].

✨ Querying & cross-project references

  • openCypher and intersphinx (0.31.1b1)

    A real openCypher engine replaces the pseudo-cypher filter dialect — in the CLI, the MCP tools (query_cypher, get_graph_schema) and the IDE filter boxes, with typo guards and Python-dialect parity. Intersphinx brings cross-project references to the IDE and preview, offline-capable, with inventories inspectable in the editor.

  • New ubc intersphinx diff

    Compare two objects.inv inventories — each a local path or a URL — and get the rows added, removed, or changed (with the changed column named). -f json emits the same content as one object, and the exit code (0 identical, 1 differences, 2 no comparison) makes it usable as a CI gate; --exit-zero reports without failing.

✨ Sphinx parity

The index learned Sphinx’s own rules across the train — dangling :ref: / :doc: / :term: references warn with Sphinx’s message shapes, sphinx.ext.autosectionlabel and sphinx.ext.todo are ported, and every image, figure, table and diagram dimension is validated. New in this release:

  • Glossaries render, in both flavours

    .. glossary:: renders the <dl class="glossary"> a Sphinx build renders — :sorted: applied, hover permalinks, definition-less entries kept — instead of falling through to the generic directive path. A MyST {glossary} now reads and registers too: terms land in the standard domain and in objects.inv, so :term: and {term} resolve against either flavour, and the false term not in glossary warnings on Markdown glossaries are gone.

  • contents renders a real page-local table of contents

    .. contents:: and {contents} render the page’s own TOC in Sphinx’s shape, with :local:, :depth:, :class:, :backlinks: and a title argument all honoured.

  • Built-in extension ports ship on by default

    [parse].extensions is now an override map rather than an opt-in list: every markup port ships on — sphinx_design, sphinxcontrib.mermaid, sphinxcontrib.plantuml, sphinxcontrib.video, sphinx.ext.todo and more — so their markup renders without configuration (sphinx.ext.autosectionlabel stays opt-in). A switched-off port’s markup is known but gated: parsed, completed and hovered, rendered as a labelled placeholder with a directive.extension_disabled warning naming the fix.

✨ Needs data

  • Imports and external needs grew up (0.31.2b1)

    An external need’s external_url is computed and stored as sphinx-needs does, so external needs are linked everywhere a need renders; imported need content renders as sandboxed cards (deny-by-default asset access); silent needimport outcomes now say so; and remote needs.json sources are cached, revalidated and served stale instead of vanishing on a failed fetch.

  • A needs.json with no needs_schema is read, not rejected

    ubc build validate-json and the needs.json panel accepted only exports carrying a needs_schema section (sphinx-needs 3.0.0+), so every older export was rejected. Such a source is now read best-effort — fields inferred from the values, with one warning stating exactly what was guessed.

✨ Pharaoh, MCP & the editor

Across the train: Pharaoh — ubCode’s experimental agentic engineer — debuted, with heartbeat / gap / workflow verbs, onboarding for greenfield and brownfield projects, and independent quality analysis of need substance; the Agent panel drives Claude Code, Mistral Vibe or OpenCode in-editor; and MCP gained agent-grade tools — query_cypher / get_graph_schema, search_references / resolve_reference over everything the project can cross-reference, and search_docs / fetch_doc_page grounding answers on the official documentation — with structured content on every tool and one-command registration with terminal Claude Code. New in this release, the ubc agent command itself is public, as an alpha: it is listed by ubc --help, and its subcommand set, options and JSON output shapes may still change between releases.

👌 Improvements

  • Warm HTML builds are much faster, and reach a true no-op. Three per-page terms (two quadratic, one linear) are now computed once per build: on a 2,000-page project a warm build goes from 4.2 s to 1.9 s, emitted bytes unchanged. And a page carrying a view directive whose filter fails no longer re-renders forever — an unchanged project now reports 0 rendered, with the warning still reported.

  • Build warnings say where they came from. Render-phase warnings carry a real source location, printed through the same block as the indexing warnings; indexing warnings print before build warnings, and a failing quality gate names each stream’s contribution. ubc build html -v prints a per-phase timing breakdown.

  • --deny gains a none level on every command that has it (“report findings but never fail”), and ubc build html gains --lint-extend-ignore, matching ubc build index.

  • An external source whose target_url template fails now reports one warning per distinct failure naming how many needs it cost, rather than one warning per need.

  • [[needs.types]] entries keep the order you declare them in wherever ubCode presents the type list.

  • The cache is byte-reproducible (0.31.2b1): two cold builds of an unchanged project leave byte-identical caches.

  • Codelinks reads preprocessor-aware C/C++ (opt-in libclang engine), plus Go and Bash comment markers (0.31.1b1).

  • The changelog is now split into one page per minor release series, under a hub at the same URL. Existing deep links such as development/changelog.html#release-0-30-3 continue to work — the hub forwards them to the series page with the fragment intact.

‼️ Breaking Changes

Upgrading from 0.30.3 you get every change below; the 0.31.x pre-releases already carried most of them, and each entry says when it landed. The single most effective migration step: if your ubproject.toml sits above the directory that actually holds your sources, say so with [project] srcdir — on ubCode’s own documentation taken at 0.30.3, that one line removed 65 of 92 new warnings (every toctree.not_included, std.doc and root-document complaint at once).

  • Warnings now fail your builds by default

    ubc check and ubc build index fail on any warning-or-worse finding (0.30.3 failed only on errors; since 0.31.1b1), and ubc build html now applies the same quality gate (new in this release) — the site is still written in full before the gate is judged, so only the exit code changes. Relax the bar with --deny error / --deny none / --max-warnings N, set it project-wide with deny under [build.html], or suppress individual codes through the lint configuration — a narrowed code never reaches the gate.

  • New warnings may appear on an unchanged project

    The train added whole diagnostic families, all on by default: dangling standard references (std.ref / std.doc / std.term), toctree and duplicate-label checks, fine-grained config.* codes, needimport outcomes and dimension validation. This release adds the needreport execution warnings on unchanged sources (needs.report_no_sections, needs.report_template_not_found — sphinx-needs warns on the bare form too), build.need_view_truncated, build.need_layout_unknown and build.need_style_invalid on the HTML build. Every one is suppressible by code, whole families in one entry with trailing-.* wildcards.

  • Options that were previously ignored now apply

    :layout: is a closed set of card designs: a name defined under sphinx-needs’ needs_layouts is not a card design (ubproject.toml currently swallows a [needs.layouts] table without comment) — port it to [needs.card_layouts], drop the option, or suppress build.need_layout_unknown. Six names that were aliases (clean_l, clean_r, clean_lp, clean_rp, focus_l, focus_r) are now real side-column designs, so those cards change. :style: is comma-separated and validated: a space-separated sphinx-needs value like blue border is one invalid token — write blue, border. [needs.string_links] rules — previously inert — now render matching values as links. And with sphinx.ext.todo on by default, a todolist lists the project’s todos instead of a placeholder (switch the port off with "sphinx.ext.todo" = false under [parse.extensions] to keep the previous output — not todo_include_todos = false, which hides more).

  • Suppressions to re-check

    :export_id: now warns as needs.option_deprecated (was needs.filter_unsupported), on all five views that handle it. The :style_col: message text was rewritten, so a message-ignores pattern matching the old wording stops matching — as do patterns that quoted a build warning’s position, which now lives in the location line rather than the message. A bare "config" ignore matches only code-less configuration warnings (use "config.*"), and block.directive_unknown no longer fires for a disabled port’s markup (use directive.extension_disabled). In the other direction, [lint] per-file-ignores now applies to build warnings too.

  • One-time full re-index and re-render on upgrade

    The cache format moved (6.4 → 6.116 since 0.30.3; 6.99 → 6.116 since 0.31.2b1) and the build manifest with it. 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, nothing to delete by hand.

  • openCypher replaces the pseudo-cypher dialect (0.31.1b1)

    Missing fields read as null with three-valued logic, =~ anchors as full-string regex, upper() / lower() become toUpper() / toLower(), and the MCP tools get_data_for_single_need / get_schema_for_need_filter / query_needs are replaced by query_cypher and get_graph_schema. See the migration notes.

  • View output is capped (0.31.1b1)

    needlist / needtable / needflow / needsequence default to :max_items: 100 with an explicit truncation notice, and default ordering is id-sorted for reproducibility. Raise or remove the cap project-wide with the new [needs] views_max_items.

  • Exports change shape once

    needs.json / Parquet / SQLite exports gain external_url and doctype, link and backlink lists are deduplicated and naturally sorted (0.31.2b1), and the default needs.json now mirrors Sphinx-Needs in dropping its collapse and hide fields (json_exclude_fields = [] restores them). A needs.json at the root of a ubCode-owned output directory is builder-managed and is pruned when the build does not produce it. A pipeline diffing exports byte-for-byte across the upgrade sees churn once.

  • VS Code agent defaults (0.31.x)

    ubcode.agent.cliPath defaults to the version-matched bundled binary (was: whatever ubc is on PATH), and driving Claude Code is ACP-only via the Pharaoh Agent panel (requires ANTHROPIC_API_KEY).

  • Smaller behaviour changes carried by the train: a :width: / :height: / :figwidth: value docutils rejects warns and is dropped instead of reaching the page’s CSS; ubc build list-documents orders by toctree (--order path restores discovery order); a failed cache write stops the build instead of continuing silently; and warm builds converge to the same needs state as a cold build.

🐛 Fixes

  • Warm builds no longer serve stale imported or generated content. A reference or view written inside an imported need’s content — or a needreport template — was invisible to the incremental fingerprint, so a page carrying one could go stale on the first warm build after an edit somewhere else. Both now record what their content resolves to, so a warm rebuild is byte-identical to --fresh.

  • ubc build html reports and gates on the re-indexed state after a mid-build source drift, rather than printing warnings from the pre-drift index.

  • A bare :ref: shows the implicit title, not the label name. A reference without an explicit title takes its text from the section title, rubric, caption or first term it points at, the way Sphinx does — whether the label sits above the block or is the directive’s own :name:, in both reStructuredText and MyST. objects.inv display names follow the same data.

  • A :name: on a diagram or view directive now emits its anchor. Nine directives registered the target, resolved :ref: to it and published it into objects.inv without ever putting the id on the page, so the deep link scrolled to the top.

  • An extra field named after one of the hidden core names ([needs.fields.arch], sections, …) now shows on cards — a project’s own declaration wins visibility.

  • needbar no longer warns about the always-empty corner cell of a FROM_DATA grid — the documented upstream authoring shape — and no longer renders an empty label as a literal 0; data-cell complaints now name the coordinates the author wrote.

  • PlantUML visibility directives no longer delete edges. In a class, component or state diagram, hide / show / remove dropped every relationship whose endpoint was created implicitly or declared with an unrecognised node kind — hide empty members alone could empty a diagram.

  • A view directive whose filter fails no longer dumps a stack backtrace into the build warning when RUST_BACKTRACE is set.

  • Crash fixes across the train (0.31.1b1): a needextend argument of a lone " aborted the CLI, a malformed table could crash the language server, and pathological filter strings could overflow the stack — filters are now bounded.

  • Patched advisories in the VS Code extension’s bundled production dependencies across the window: brace-expansion (DoS), dompurify (XSS), uuid (bounds check) in 0.31.1b1, and smol-toml (GHSA-7w5x-hrqm-74c2) in this release.