0.31.x¶
0.31.2b1¶
- Released:
12.08.2026
- pre-release:
Five arcs this release:
the HTML build goes public — ubc build html steps out of hiding as an alpha,
with a user guide, offline math, sphinx-design tabs and furo-parity page chrome;
PlantUML diagrams get a preprocessor — variables, macros, loops and a bundled C4
library expand before rendering, always on, with no filesystem or network access;
imports grow up — external needs get real links, imported need content renders as
sandboxed cards, and a re-ordered needimport can no longer silently lose its needs;
the index speaks Sphinx — dangling :ref: / :doc: / :term: references
now warn as sphinx-build does, sphinx.ext.autosectionlabel and
sphinx.ext.todo are ported, and every image, figure, table and diagram dimension is
validated;
and MCP grows agent-grade reference tools — search and resolution over everything
the project can cross-reference, structured answers on every tool, and one-command
registration with terminal Claude Code.
✨ The HTML build (alpha)¶
``ubc build html`` is now public (alpha)
The static-site builder is no longer hidden from
ubc build --help. It is marked(alpha): the command works and is under active development, but the emitted markup, the theming surface and the available options may still change between releases, so it is not yet recommended for production builds.A new guide — Building a static HTML site — covers running it: the quickstart and principal options, where the site lands and how it works from a
file://URL (search included), incremental rebuilds and--fresh, and how the build reports its warnings. The [build.html] configuration page remains the option reference.Math typesets, offline
The
mathdirective, the:math:role and MyST dollar math render through a bundled KaTeX — generated entirely at build time, no CDN, works offline — in the built site and the Rendered preview alike. A:label:gets an anchor and a permalink; raw LaTeX stays out of the search index; a page with no math ships none of it. See Math typesetting.sphinx-design tabs render, and the family follows your theme
tab-set/tab-itemrender in the oracle’s own markup and switch with pure CSS (JavaScript-disabled readers included), with:sync:groups, URL-hash deep-linking and persisted selection on top. The new[build.html]keysd_tabs_storage_prefixcontrols (or disables) the remembering (its default deliberately differs from sphinx-design’s). The whole sphinx-design family now takes its palette from the site’s--ubc-*tokens, so branding the shell brands the components — and the editor preview renders them styled too.furo-parity page chrome
Section headings and named table / code captions carry a hover
¶permalink; a followed fragment link tints its target; admonitions take furo’s tinted-title-bar model over the documented accent tokens; wide tables scroll inside their own box instead of stretching the page.Document topmatter becomes metadata
An RST leading field list or MyST YAML front matter is consumed into per-document metadata instead of rendering as visible content, and four keys now shape the page:
sd_hide_title,hide-toc,tocdepthandnosearch. Front pages that used to show their option block as text stop doing so.The build tells you what it could not render
A role with no renderer is aggregated once per name under
build.role_unhandled, and the diagram file forms —.. uml:: flow.puml,.. mermaid:: flow.mmd— now render the file, re-render the pages that use it when it changes, and report declines instead of silently showing a placeholder. Build warnings are durable across incremental rebuilds and name their page. See Unrendered directives, roles and diagram files.``objects.inv`` is complete
Markdown pages (and their
(target)=labels) now appear in the published inventory — previously a.mdpage was not intersphinx-linkable at all — and a:ref:to a target defined inside an.. include::-d fragment links the page that renders it, matching what the inventory advertises.The differences register covers both surfaces
Differences from a Sphinx build — the honest register of where ubCode’s rendering differs from a full
sphinx-build— is reframed around the two public surfaces (preview andubc build html), grouped into four thematic sections with a jump table, and explicit about what has and has not been audited against the build.
✨ Diagrams¶
PlantUML preprocessing, always on
A diagram’s body is preprocessed before it is translated, in the preview and in
ubc build html: variables (!$name),!define/!definelong,!function/!procedure, conditionals, loops and the string / list / arithmetic builtins all expand, so a diagram can be generated rather than written out in full. The C4 standard library is bundled —!include <C4/C4_Container.puml>resolves offline and thePerson()/Container()/Rel()macros expand — with no filesystem access and no network, ever: your own file includes, URLs and themes are recognised and skipped rather than fetched. This needs no configuration and cannot be turned off, matching the Sphinx plantuml extension. See Diagrams for the full supported / not-yet list.PlantUML fidelity: labels stop leaking their markup
Creole display text (
**bold**,//italic//, size / colour / font tags) renders instead of appearing literally; C4 sprite references become glyphs;left to right directionis honoured; a per-stereotypeskinparampaints only its stereotype; inline element colours reach the output — so the flagship C4 samples now look like PlantUML’s own rendering rather than markup soup.``needsequence`` renders as a Mermaid sequence diagram
The
needsequencedirective is now drawn in the Rendered preview and byubc build html, instead of showing a placeholder frame. From each:start:need the walk treats the needs it links to as the messages and each message need’s own links as the receivers, so an intermediate need becomes the arrow label — which is what makes the view good at showing a mediated relationship. Where Sphinx-Needs fails the build — a missing or unknown:start:, an unknown:link_types:name — ubCode warns and renders what it can.``:width:`` and ``:height:`` on ``needflow`` and ``needsequence``
The two diagram views can now be sized, with the same options and units the
mermaid/uml/plantumldirectives take. A value that is not a size raises a warning and the diagram renders without that dimension. Sphinx-Needs has no size option on any of its diagram directives, so these are a ubCode extension.:scale:on the two views now warns and points at:width:/:height:instead of being ignored in silence.A very large diagram no longer grows the page without limit: a rendered diagram is bounded to 60% of the viewport height and scrolls past that, in the preview and in
ubc build htmlalike. A diagram that already fits is unaffected, an explicit:height:lifts the bound, and printing releases it. (A 200-nodeneedflowpreviously produced a 45,000-pixel page.)
✨ Needs: imports and external needs¶
External needs are linked
An external need’s
external_urlis now computed when its source is read, from the source’sbase_urlandtarget_url, and stored on the need — as sphinx-needs does.needtableID and link cells,needlistitems and the:need:role all render real links to the site the need lives on, andexternal_urlbecomes an ordinary field: a:columns:entry, a:sort:key, a:filter:term, a Cypher property, and a key in theneeds.jsonexport. A source that cannot produce URLs now says so instead of leaving its needs silently unlinked.Imported need content renders as sandboxed cards
Each
needimportrenders one card per imported need, and imported needs become clickable everywhere a need renders. Content is sandboxed deny-by-default: images and file-reading directives inside imported content are refused with a visible diagnostic naming its config key, and a project can opt in per source ([[needs.import_sources]]withallow_assets/assets_root). Every need also gains a deriveddoctypecore field. See What works inside imported need content.Silent ``needimport`` outcomes now say so
A
:filter:that selects nothing reports its counts (needs.import.filter_empty, Info — it never failsubc check); an:ids:entry naming no need in the source warns; an ambiguousdoctypenames every tied parser and the winner; and unresolvable references inside imported content are diagnosed at the hosting directive.Python-dialect
:filter:expressions inneedtable,needlist,needflowand the:need_count:role can now read the incoming half of every link type as a<link>_backpseudo-field (implements_back, …), pinned to sphinx-needs’ own values.
✨ Sphinx parity in the index¶
Dangling references warn, as ``sphinx-build`` does
A
:ref:/{ref}to no label, a:doc:to no document and a:term:outside the glossary now warn — Sphinx’s exact message shapes under the codesstd.ref,std.docandstd.term— in captions, table cells, substitution definitions and sphinx-design titles included. Targets defined inside excluded.. only::blocks are exempt, so multi-format projects that are Sphinx-clean stay clean.``sphinx.ext.autosectionlabel`` and ``sphinx.ext.todo`` are ported
Both are opt-in via
[parse] extensions, with their upstream confvals (autosectionlabel_prefix_document,autosectionlabel_maxdepth;todo_include_todos,todo_link_only).todorenders as a first-class admonition andtodolistas a live cross-page aggregation. One deliberate divergence:todo_include_todosdefaults totrue.Every dimension is validated
:width:/:height:/:figwidth:on images, figures, tables, diagrams and videos are read by docutils’ own grammar family; a value docutils rejects raisesdirective.measure_invalid(ordirective.table_widths_invalidfor:widths:lists) and the dimension is dropped instead of leaking into the page’s CSS.Directive arguments and options parse their inline markup
Markup in a directive’s argument or an inline-typed option value — admonition and topic titles,
rubric, code captions, changeset text, card and tab titles and more — now renders, and references inside them resolve and warn when dangling. In RST and in MyST.A plain
:doc:now consults intersphinx inventories (Sphinx’s named-inventory phase), andstd:refis accepted as an alias ofstd:labelin:external:references.
✨ MCP and the editor¶
MCP: ``search_references`` and ``resolve_reference``
Two new tools let an AI agent see everything the project can cross-reference — local labels, glossary terms and documents plus every loaded intersphinx inventory.
search_referencesis ranked keyword / symbol search returning paste-ready role snippets;resolve_referenceanswers how the build would resolve a given role and target — local, external, or not-found with near-miss suggestions — and can list every usage site that resolves to the same target, so a rename’s blast radius is visible before it happens. See Model Context Protocol (MCP) server.MCP: structured content on every tool
Every tool now declares an
outputSchemaand carriesstructuredContentagainst it, so a client can rely on an answer’s shape instead of parsing prose.Terminal Claude Code, registered in one step
A new
ubcode.agent.idvalue — Claude Code (terminal),claude-code-cli— drives Pharaoh by handing the prompt to your own terminal session, so it works on a Claude subscription. The editor command ubCode: Register MCP server with Claude Code (orubc agent install --register-mcp) registers the ubCode MCP server for the project after an explicit confirmation dialog.``ubc intersphinx list``, and inventories in the editor
ubc intersphinx listrenders every configured[intersphinx.projects.<name>]inventory (inspectonly ever looked at one file), and the Std Referencing view gains anintersphinxnode — project →domain:objtype→ entries, fetched lazily, with click-to-open documentation. The:external:role family also completes segment by segment now, including after:external+(+is a new completion trigger).The Diagnostics view gains a Group By toggle — by diagnostic code (the default) or by file path — persisted per workspace.
🤖 Pharaoh (experimental)¶
Still experimental (ubc agent remains hidden); see the
Pharaoh documentation.
Drive runs on the new ``ubc agent prompt``: the verb prints, as plain text, the resolved instruction for the one next workflow action — the read-only twin of
ubc agent run— and the drive starter now hands it to the agent, so each Drive click resolves one arm; the direct prompt output works much better with weaker LLMs.Installed agent assets can be updated: a new ubCode: Update agent skills command runs
ubc agent update, and Drive offers the update once per project per session when the installed preset is behind the oneubcships (local edits are never replaced without a “Replace / Keep mine” confirmation). The drive contract’s teaching surfaces were also verified against the engine and trimmed by a third.Writing a review verdict now refreshes every view that folds review state — the Gaps overview and the need graph’s overlay included — without resetting the graph viewport.
👌 Improvements¶
The cache is byte-reproducible: two cold builds of an unchanged project leave identical cache files behind, making a cache directory comparable and checksummable. (A project with a remote
needimportstill writes one clock-derived stamp.)Link and backlink value lists render and export in sphinx-needs’ exact order and membership — deduplicated and naturally sorted — and an incoming chip no longer prints the target’s part id against the source’s id.
needtable: the sphinx-needs core/system fields (docname,lineno,is_external, …) are real:columns:entries and:filter:terms,:sort:orders by value rather than text (2, 9, 10, not10, 2, 9), and:colwidths:renders a real<colgroup>with docutils’ relative-share semantics.The PlantUML preprocessor’s include budgets are tightened to values measured against real diagrams (at most 64 included files and 8 MiB of included text, both far above what any legitimate diagram reaches). Diagram output is unchanged.
Site search stops matching on markup artefacts: state-directive chips, hyperlink target labels,
¶permalinks and raw LaTeX are all out of the index.An unknown directive option’s “did you mean” suggestion is capped by edit distance, so
:priority:is no longer “corrected” tolayout.[lint]code lists accept trailing-.*family wildcards everywhere codes are configured (ignore,lsp_select, per-file and message ignores) —"std.*"suppresses the whole standard-domain channel in one entry. A[parse] extensionsentry close to a known port name now gets a near-miss typo warning (config.unknown_extension) instead of silence.
‼️ Breaking Changes¶
One-time full re-index and re-render on upgrade
The cache format moved (6.40 → 6.99) and the build manifest with it (15 → 49). The first run after upgrading rebuilds the index and re-renders every page once; this is self-healing and needs no action.
New warnings may appear on an unchanged project
This release adds 28 new diagnostics, all reported by default — notably dangling standard references (
std.ref,std.doc,std.term— the largest new surface for projects that have never runsphinx-buildcleanly), duplicate labels and docnames (std.duplicate_label,std.duplicate_docname), dimension and option validation (directive.measure_invalid,directive.table_widths_invalid),needimportoutcomes (needs.import.*),needs.extend_missing_id, and theubc build htmlrender radars (build.role_unhandled,build.diagram_file_unresolved,build.diagram_unconvertible). Every one is suppressible by code through the ordinary lint configuration, and whole families in one entry with the new trailing-.*wildcards ("needs.import.*","std.*").A bare ``”config”`` ignore no longer silences everything
Six configuration checks that reported under the single
configcode now report under fine-grainedconfig.<subcode>codes. Alint.ignoreentry of"config"matches only the code-less configuration warnings from now on — replace it with"config.*"to keep suppressing the whole channel.``needs.json`` / Parquet / SQLite exports change shape
Exports gain
external_urlanddoctype(as schema properties, and as per-need keys when defaults are not elided), and link / backlink lists change order and membership (deduplicated, naturally sorted). A pipeline diffing exports byte-for-byte across the upgrade will see churn once.``ubcode.agent.cliPath`` defaults to the bundled binary
The default changed from
"ubc"(whatever is onPATH) to"", meaning the version-matchedubcbundled with the extension. Set it back toubcexplicitly if you rely on aPATHinstall for the agent commands.Previously-accepted dimension values are dropped
A
:width:/:height:/:figwidth:value docutils rejects (:height: 50%on an image,:width: auto) no longer reaches the page’s CSS — it warns and the dimension is dropped.MCP output shapes:
search_docs’s text block is now compact JSON (was pretty-printed), andfetch_doc_page’s structured half carries provenance only — read the page body from the text block.A failed cache write now stops the build on
ubc build html,ubc build targetsandubc check, which previously continued in silence.Warm builds converge to cold: a project where two imports pull the same need id may see a different copy win than its last incremental build produced — the winner is now a function of the sources, identical to
--no-cache.
🐛 Fixes¶
A ``needimport`` that was re-ordered or re-configured no longer silently loses its needs — one of two directives reading the same remote source could contribute nothing, with no warning, until the remote re-check window expired. Re-ordering or adding
needimportdirectives is also free now (no re-reads), and honours[needs.remote] cache_days.The project cache commits as one group. A build interrupted between index writes could leave the cache describing two generations at once — a page silently gone from the site, a need gone from
needs.json, a real warning suppressed for ever. Builds now write a commit record, verify it on load, and rebuild from scratch when it does not match.A deleted source no longer poisons the cache: the deleted page was purged in memory but resurrected from disk on every later build —
ubc checkwent green on a genuinely broken project andubc build htmlfailed permanently. Already-poisoned caches heal on the first build after upgrading.Warm rebuilds converge on the cold needs state: deleting an
includeno longer leaves ghost needs, re-indexing no longer duplicates a fragment’s needs, and an edit inside a partial no longer leaves the stale copy as the winner.needs.json read diagnostics say where the fault is — a
needs_schemaproperty missingfield_typeortypeno longer rejects the whole source: it is reported once, naming the JSON path and the accepted values, and the source’s needs are read. Read failures name the JSON path,needimportdiagnostics name the JSON source file, and Rust debug output no longer reaches these messages.A ``:need:`` to a need defined in an included fragment links the page that renders the card, not a dead chip or the fragment’s non-page.
A ``:name:``-d figure or image has an anchor at all — every
:ref:to one (and every publishedobjects.invURI) was a link to a fragment that existed nowhere; figure captions also gain the¶permalink.PlantUML sequence and state diagrams that previously rendered as nothing now render — four emitter defects produced Mermaid the renderer refuses; measured over a 646-diagram corpus, 20 diagrams that emitted invalid Mermaid now emit valid Mermaid, with none newly broken.
Three inputs that could crash the preview while parsing a PlantUML diagram — a malformed
skinparamscope, an extreme%darkenargument, and anote on linkwritten with the Kelvin sign — no longer do.A PlantUML
!returnvalue is now held to the same size budget an assigned value is; a deliberately hostile diagram could previously exhaust memory (measured 217 MiB → 10 MiB on the probe).A section heading inside a truthy
only/ifbody is a real section again — the page keeps its title, andubc format --previewno longer deletes the heading from the source file. In MyST, a heading inside a directive body renders as a rubric instead of emptying its section.Built pages stop rendering chips Sphinx renders nothing for: hyperlink-target definitions and the
role/default-role/highlight/default-domainstate directives (their labels are out of the search index too; the preview keeps its chips).ubc query filterresolves thedocnamefield, and aneedextendwhose argument names no need warns (needs.extend_missing_id) instead of applying nothing in silence.image.*wildcards select the builder-best file exactly as a Sphinx html build (glob + mimetype priority), a query / fragment suffix on an image URI no longer raises a falseimage.not_found, atodowhose body is a list no longer renders run together in thetodolist, and end-of-line completions work in CRLF documents.Need graph: link names scale with the graph instead of dwarfing it at low zoom, edges survive a theme change (labels lost their cutout and read as struck through), and rows leave room for the link name between them.
Diagnostic anchors tell the truth: positions are 1-based everywhere, a diagnostic raised inside an included fragment is anchored in that fragment rather than its host, and a circular-include message names the file project-relative instead of by its absolute path.
ubc formatno longer panics on a bare enumerator line, and a deeply-nested declining table no longer hangs the parser (directive.nesting_too_deepbounds it).
0.31.1b1¶
- Released:
30.07.2026
- pre-release:
Five arcs this release: openCypher becomes the needs query language
(CLI, MCP and the IDE filter boxes); Intersphinx brings cross-project
references to the IDE and preview; the preview renders the sphinx-needs
view directives (needlist / needtable / needflow) plus
substitutions, toctrees, tables and much more markup; ubc check
becomes the canonical project linter for CI; and
Pharaoh gains an in-editor Agent panel that
drives Claude Code, Mistral Vibe or OpenCode. Also: codelinks learns
preprocessor-aware C/C++, Go and Bash, and remote needs.json sources
are cached and served stale instead of vanishing on a failed fetch.
✨ openCypher queries¶
A real openCypher engine replaces the previous pseudo-cypher filter dialect. The project is queried as a property graph — each need a node labelled by its type, each typed link a relationship — supporting
MATCH/OPTIONAL MATCH,WHERE,WITH,UNWIND,RETURN,UNION,ORDER BY/SKIP/LIMIT, aggregation, variable-length paths,EXISTS/COUNTsubqueries and the openCypher function library (read-only, with query limits). It powers the newubc query cyphersubcommand, the Needs Index / Needs JSON filter boxes in VS Code (a bare expression overnstill works), and the ubCode-only:cypher:option on the view directives below. See the query guide — and the migration notes under Breaking Changes.MCP: ``query_cypher`` and ``get_graph_schema`` — AI agents query the needs graph with real openCypher and discover the schema (labels, link types, properties and available parameters, Neo4j-style), replacing the previous three needs tools (see Breaking Changes). Result tables are capped, with honest truncation notes and paging guidance.
Project context as query parameters — the reserved parameters
$var(variant data, e.g.$var.deploy.region),$build_tagsand$docnamebridge project context into queries, and every node carries a computeddocnameproperty. An unknown parameter errors, naming the available ones.Typo guards — a label, relationship type or property absent from the whole schema warns with a “did you mean” suggestion (on stderr in the CLI; as
note:lines in MCP); the newubc query cypher --strictflag turns these warnings into a failure.Python-dialect parity and friendlier errors — bare
True/Falseliterals (the upstream total-count idiom for:need_count:), literal-on-the-left comparisons ("spec" == type) and theis_need/is_partconstants now parse, so filters written for sphinx-needs stop failing whole views (18 → 4 view failures on the sphinx-needs docs corpus). Filter parse errors now name the offending position, unknown fields get “did you mean” suggestions, and the deliberately unsupported forms are documented with workarounds (see Differences from a Sphinx build).
✨ Intersphinx — cross-project references¶
:ref:and:term:references that miss locally now resolve against external Sphinx inventories configured in the new [intersphinx] section — in the IDE and in the preview, where they hydrate to real links carrying the target project’s titles and an ↗ marker. The explicit:external:role:/:external+inv:domain:role:family is ported too, for both reStructuredText and MyST.IDE support is offline-capable —
:ref:completion includes external labels, hover shows the resolved title and URL, and every externally resolved site becomes a ctrl+clickable link. Inventories are cached per user with conditional revalidation; fetch problems surface asintersphinx.*warnings and never fail a build;offline = truepins the cache for network-restricted CI.New
ubc intersphinxCLI —inspect <file-or-url>prints any inventory (Sphinx- or JSON-formatted, filterable), andcache dir | list | cleanmanages the cache.
✨ Preview¶
The sphinx-needs view directives render —
needlistandneedtable(sticky header, projected columns,:columns:/:sort:validated with suggestions) render live filtered views hydrated from the index,:need_count:counts inline (including thea ? bpercentage form), andneedflowdraws a real traceability graph — typed coloured nodes, per-link-type edges, legend — as a client-side Mermaid flowchart. Needs are selected via the shared Python filter surface or the ubCode-only:cypher:option, and a failed view renders an error block naming the reason. See the new needs tutorial.Substitutions expand —
|name|references resolve from yourreplace/unicode/datedefinitions plus the Sphinx specials|version|/|release|/|today|(new[project]keysrelease,today,today_fmt); document titles and:ref:labels use the expanded text everywhere (hover, Site Map,needs.json); undefined, circular and runaway definitions are diagnosed (substitution.*).Toctrees and the Site Map —
.. toctree::renders as a nested list of links (a:hidden:toctree as a collapsed block); the new Site Map view shows the project’s navigation tree from the configured[project] root_doc; newtoctree.*diagnostics report structural problems (missing documents, cycles, documents not included anywhere —:orphan:exempts); andubc build list-documentsgains--order <toctree|path>.Tables build —
list-table,csv-table(including:file:, a tracked dependency) andtablerender as real tables.literalincluderenders the included file as a highlighted code block with Sphinx’s filter pipeline (:lines:,:start-after:/:end-before:,:dedent:,:prepend:/:append:, …), as a tracked dependency, with honest diagnostics for the unsupported rest.The
rawdirective passes through, with trust controls —.. raw:: htmlemits its payload verbatim, in block position and inside substitution definitions (anrst_prolog-defined|br|finally works). The new[parse]keyraw_enabled = falsemakes every raw directive inert (directive.raw_disabledplus an escaped placeholder) for pipelines that build unreviewed contributions, and the preview escapes raw HTML in untrusted workspaces. See Raw content.The
onlydirective renders conditionally — its tag expression is evaluated againstbuild_tags; a falsy body shows as a collapsed greyed block in the preview and is excluded from the index (its needs and targets do not resolve, matching Sphinx).Markdown hosts splice files too — MyST
{include},{literalinclude},{csv-table}and{raw}with:file:now read their files on every surface, with cycle detection (which a Sphinx build lacks entirely).sphinx-design components and
:octicon:—grid,card,dropdown,divand the button directives render with sphinx-design’s own markup (structure only in the preview for now — styling follows),button-refand card:link:options participate in reference resolution, and:octicon:renders real inline icons.Built-in extension ports — the new
[parse]keyextensionsenables ported Sphinx extensions under theirconf.pynames:sphinxcontrib.video(videorenders a real HTML5 player; playback inside the VS Code webview is still limited),sphinx.ext.extlinks(link-shorthand roles declared in[parse.extlinks]), andsphinx_syntax_example— a newsyntax-exampledirective (its Sphinx twin published to PyPI assphinx-syntax-example) that shows markup as highlighted source plus rendered result, with opt-in per-document numbering (syntax_example_numbering). Ported directives join autocomplete and hover. See Parsing.Highlighting and pinning —
rst,mystandcyphercode blocks now highlight, plusjinja,powershell,bat,cmakeandlatex; and a new 📌 pin toggle holds the preview on its document instead of following the active editor.
✨ Indexing & needs data¶
Included content reaches cross-file features —
.. include::andrst_prolog/rst_epilogare now spliced on the index path too, so targets, needs and glossary terms defined there resolve project-wide, and references bound viadefault-roleor custom roles are collected like an explicit:ref:.Dangling hyperlink references are reported — an unresolved, duplicated or circular named hyperlink reference (
target_, and the backtick-quoted phrase form) is diagnosed (ref.*codes) in the editor andubc check.Remote needs sources: cached, revalidated, stale-served — the new [needs.remote] section (
cache_days,timeout,offline) governs everyneeds.jsonfetched over HTTP(S); a failed refetch now keeps the previously loaded needs and reports the failure (previously the needs silently vanished for 24 hours), revalidation is conditional (ETag / Last-Modified), andfile://URLs work.
✨ ubc check — the canonical project linter¶
Whole-project by default —
ubc check(no arguments) now indexes and lints the entire project, Markdown included, reporting everything the editor reports (needs, reference, toctree and schema diagnostics included);ubc check <paths>scopes the findings to the given paths;--per-filerestores the old isolated single-file behaviour. See linting on the command line.Severity-aware exit policy — findings are summarised by severity (
Found 1 error(s), 2 warning(s).), and the new--deny <info|warning|error>and--max-warnings <N>flags (on bothubc checkandubc build index) control what fails the run.--output-format json— machine-readable diagnostics on stdout (code, severity, message, source locations), with the human progress chatter demoted to stderr.
✨ Codelinks¶
Preprocessor-aware C/C++ extraction — an opt-in libclang engine (the preprocessor table, with
compile_commands,defines,includesandstd), so only markers in actually-compiled#ifbranches become needs. libclang is found viaLIBCLANG_PATHor the system, else downloaded once as a pinned, checksum-verified wheel (UBC_LIBCLANG_INDEX_URLsupports mirrors and air-gapped setups).Go and Bash sources —
comment_type = "go"andcomment_type = "bash"(.sh/.bash/.zsh/.ksh) join C/C++, Python, Rust, C# and YAML, with live editor diagnostics.
🤖 Pharaoh (experimental)¶
Still experimental (ubc agent remains hidden); see the
Pharaoh documentation.
The Pharaoh Agent panel — a new sidebar chat that runs an external coding agent in your workspace over the Agent Client Protocol: Claude Code, Mistral Vibe or OpenCode (GitHub Copilot keeps its native chat). The agent sees the project’s installed skills, asks inline before touching files or running commands (with a per-session auto-approve), and gets the ubc MCP tools. Provider API keys come from the environment — Claude Code needs an Anthropic Console
ANTHROPIC_API_KEY(subscription logins are not permitted over ACP); keyless setups degrade to copyable prompts.Harness-aware Drive and Run QA — the
ubcode.agent.idsetting now routes Drive / Capture intent to Copilot Chat, the Pharaoh panel, or plain clipboard copy (other), with always-available copy-prompt buttons; Run QA scores through the selected agent too (pin a different one withubcode.qualityAnalysis.agentIdto avoid self-review). Prompts name the bundled, version-matchedubc.Documentation grounding — new MCP tools
search_docsandfetch_doc_pageground any agent on the published ubCode and sphinx-needs documentation (symbol-aware: an exactneeds_typeshit outranks keyword matches), and the@pharaohparticipant now uses the same engine-side retrieval, degrading gracefully when the docs are unreachable. The Clear documentation cache command is gone (the cache is now session-scoped).Portable skills, better onboarding —
ubc agent installadditionally writes skills to the harness-neutral.agents/skills/location (read by Mistral Vibe and OpenCode), andinstall --detectnow derives per-type skills and a[quality]section, so Run QA works right after onboarding an existing project.
👌 Improvements¶
Directive diagnostics now underline the offending option name (or the directive name) instead of the whole directive block.
ubc build targetsderives everyobjects.invanchor and display name through the exact docutils / Sphinx rules, so external projects linking into yours land on anchors that exist.The PlantUML-to-Mermaid preview conversion decodes display-text escapes (
\nline breaks in notes, labels and member names) and renders multiple stereotypes.The Pharaoh chat transcript renders full Markdown (tables, task lists, nested lists) with bounded parsing, so a pathological reply degrades to plain text instead of freezing the panel.
The ubCode sidebar is reordered (everyday views first), and the Site Map gained a pinnable Project row showing the followed file and its
ubproject.toml.
‼️ Breaking Changes¶
MCP needs tools replaced —
get_data_for_single_need,get_schema_for_need_filterandquery_needsare removed in favour ofquery_cypherandget_graph_schema. Update saved prompts, agent configurations and allowlists.Cypher filter semantics — the VS Code needs-view filter boxes (and anywhere the previous pseudo-cypher dialect was used) now evaluate real openCypher: a missing field reads as
nullwith three-valued logic (NOT (n.priority > 7)now drops needs without apriority),=~is anchored full-string regex (was substring),upper()/lower()becometoUpper()/toLower(), and the implicit one-hopl/onamespaces are removed (the error suggests the explicitMATCH (n)-[l:type]->(o)rewrite). The Python dialect and free-text search are unchanged. See the migration notes.New warnings may appear — whole-project
ubc checksurfaces diagnostic families the old per-file mode never ran, and the new reference, toctree and substitution diagnostics also reach the editor — previously-clean CI runs may newly fail. Scope with--per-file, or suppress individual codes via the standard lint configuration. Info-only runs now exit0(use--deny infoto fail on any finding).View output is capped —
needlist/needtable/needflowdefault to:max_items: 100with an explicit truncation notice, and default ordering is id-sorted for build-to-build reproducibility (upstream: document order).ubc build list-documentsorders by toctree by default and fails when the index cannot be built;--order pathrestores the previous pure-discovery listing.One-time full re-index on upgrade — the cache format moved 6.15 → 6.40 across this release’s features and the config hash gained the new keys, so every project cold-rebuilds its index once. Self-healing, no action needed.
Driving Claude Code is ACP-only — selecting
claude-codeno longer opens a terminal with theclaudeCLI pre-typed; it runs in the Pharaoh Agent panel and requiresANTHROPIC_API_KEY. Declining the agent-skills install offer now genuinely cancels the action it gated (previously it carried on regardless).
🐛 Fixes¶
Crash fixes: a
needextendargument of a lone"aborted the CLI; a malformedlist-table/tablecould crash the language server and duplicate index entries; and pathological filter strings could overflow the stack — filter expressions are now bounded (16 KiB, 64 nesting levels).:term:case-insensitive fallback is now deterministic when two glossary terms case-fold to the same string — resolved links no longer flip between builds.Directives from enabled extension ports appear in autocomplete and hover (previously the parser knew
videobut completions did not).ubc agent doctorandconfig-validatenow reject a config that fails to load, in agreement withagent next, naming the offending line;doctornewly honours-c/--configandUBCODE_CONFIG_OVERRIDE.Workflow editor: clicking a stage without a review skill no longer crashes the inspector, the graph follows the panel size, uses a proper layered layout (no overlapping labels), and no longer opens blank.
Pharaoh Gaps: a project whose
ubproject.tomllives in a subfolder is no longer treated as greenfield, Set up project no longer tries to write to the filesystem root, and a need awaiting review reads “Missing review” instead of its own stage.Onboarding hand-off prompts name the bundled, version-matched
ubc(not whateverubcis on PATH), and the detect-answers file is written outside the project and cleaned up afterwards.Site Map rows no longer attract other extensions’ context buttons, and a collapsed hidden-toctree block in the preview can be expanded even with click-to-source enabled.
Codelinks: one-line markers terminated by a newline anchor to the comment start, so prose mentioning
@authormid-comment no longer produces a bogus need.Patched high- and moderate-severity advisories in the VS Code extension’s production dependencies (
brace-expansionDoS,dompurifyXSS,uuidbounds check).
0.31.0b1¶
- Released:
16.07.2026
- pre-release:
Three arcs this release: the Rendered preview hydrates cross-file links and need cards live from the index (and renders far more markup); the engine picks up saves, includes and imported content, with a wider diagnostic net; and Pharaoh — ubCode’s experimental agentic engineer — makes its debut.
✨ Preview¶
Cross-file hyperlinks (reference “hydration”)
:ref:/{ref}, the:need:role family (directional andtitle <target>forms included),:doc:and:term:now render as working links in the Rendered preview once the background index has resolved the target: click-through opens the target document at the exact line, and placeholder chips upgrade to links as indexing completes. Resolved links show their target’s real title (explicit link text wins, then the resolved title, then the bare target;:need:references render as “Title (ID)”, matching sphinx-needs), and relative:doc:targets resolve Sphinx-faithfully from subdirectories. Unresolved (or!disabled) references keep the placeholder chip, and typing latency is unaffected — the preview only looks up what the index has already published; it never resolves.Need cards
Need directives (every type from
needs.types) render as proper cards — type badge, title, ID, meta table and body — hydrated live from the index: field values includeneedextendmodifications, outgoing links are clickable, and incoming “links back” rows show data absent from the source file. Cards show authored and extended state only, following the sphinx-needsshow_in_layouttaxonomy (config-default padding is skipped, so cards stay clean on hundred-field projects), and upgrade as indexing completes; Structural mode keeps the placeholder... include::is expanded in placeNested includes, live unsaved edits of the included file, the docutils slicing options (
literal,start-line/end-line,start-after/end-before), section re-basing, and cycle / depth protection; a failed include shows an error chip carrying the docutils-shaped message. The included file becomes a tracked dependency of the index — editing it re-indexes every document that includes it — and missing files or failed slicing matches are diagnosed (directive.include_*codes). Not yet expanded: MyST{include}and the remaining docutils options (parser,code, …), each diagnosed as unsupported; targets and needs in an included file still attribute to the fragment file.Document state: default role, custom roles and highlight language
.. default-role::, doc-local.. role::definitions and.. highlight::now take effect instead of being inert: bare interpreted text (`` text) renders as docutils' ``title-reference— following an activedefault-role— instead of raising a bogus warning, custom roles resolve within the document, andhighlightsets the language of subsequent literal blocks (a muted chip marks each state change). New[parse]keys — Sphinx confval equivalents — seed the initial state:default_roleandhighlight_language(an unknown configured role is diagnosed asconfig.unknown_default_role), plusrst_prolog/rst_epilogto prepend / append source to every RST document — as in Sphinx, prologue state applies to the whole document, and an.. include::inside it works (substitution definitions do not yet expand). See Parsing.Code blocks render with syntax highlighting
The
code-block,sourcecodeandcodedirectives render as real literal blocks (previously generic placeholders) with:caption:,:name:and:class:support, coloured by bundled client-side highlighting that follows the editor’s light / dark theme. The language comes from the directive argument, falling back to the currenthighlightstate or[parse] highlight_language.Much wider markup coverage
Newly rendered: the docutils text roles (
:title-reference:and friends), deterministic:pep:/:rfc:link roles, the Sphinx UI and styled-literal roles (:kbd:,:menuselection:,:file:, …), the sphinx-needs:variant:role, version-change directives (versionadded/versionchanged/deprecated), structural block directives (topic,sidebar,rubric, …),centeredandhlist, diagram directives with file arguments, image / figure parity, admonitions includingseealsoand:collapsible:, and generic need-type rendering. Unhandled roles now at least visualise their name and content. The new Authoring reference documents exactly what renders — and where the preview deliberately differs from Sphinx.Diagram captions, alignment and sizing
The
mermaid,umlandplantumldirectives’:caption:and:align:options are now respected — a caption wraps the diagram in a figure — and:width:/:height:size it (a bare number means pixels; a small deliberate extension over upstream sphinxcontrib-mermaid so all three directives size consistently). Diagrams without these options render exactly as before.
✨ Indexing & diagnostics¶
Saving now refreshes cross-file features by default
[server] index_on_savenow defaults totrue, made safe by a new trailing debounce: burst saves coalesce into one indexing pass after a quiet window ([server] index_debounce_ms, default500;0= immediate). Diagnostics, preview hydration and need cards heal on save out of the box; setindex_on_save = falseto restore the old behaviour.ifdirective for conditional content (Sphinx-Needs 8.2 parity)The Sphinx-Needs
.. if::/{if}directive is now supported: its argument is a filter expression evaluated against the build’s variant data (thevar.*namespace fromneeds_variant_data). A truthy condition includes the body — needs inside are collected and indexed as if unwrapped; a falsy condition excludes it entirely — needs are not collected and targets do not resolve, matching Sphinx-Needs. An expression that cannot be evaluated is reported asif.invalid_expressionand the body is excluded.In the editor, a falsy
ifblock renders in the preview as a collapsed, greyed block labelled with its condition, and the inactive source region is faded (like disabled code behind an#ifin a C editor) — conditional content stays visible without cluttering the active view. See the if directive.Real-time validation of need option values as you edit
Need option values are now checked against their schema while you type, publishing editor squiggles for the open document — no full project build required — in both reStructuredText and MyST Markdown (previously these issues surfaced only after a full index build, in the diagnostics panel). Three codes are published, each suppressible via the lint configuration:
needs.invalid_field_value(enum / const, string length, numeric bounds, array constraints),needs.invalid_link_value(link-ID length), andneeds.dynamic_functions(a[[...]]value that does not look like a dynamic-function call). The checks are deliberately lightweight — regexpattern/formatvalidation and dynamic-function values are left to the full build — so they stay cheap on every keystroke.Imported need content is now parsed
The
contentof needs pulled in vianeedimportis markup-parsed in the importing document’s format: need parts become addressable,:need:references join the link graph (including backlinks), and markup problems surface as diagnostics anchored at theneedimportdirective, prefixed with the need’s ID. Imported content parses context-free — without the host document’s substitutions or relative paths — a deliberate, documented divergence from sphinx-needs’ accidental host-context leakage; nested generative directives are diagnosed (needs.import.nested_generative), never executed.Markdown (MyST) documents contribute to the project index
Markdown / MyST documents now contribute their structure (titles, headings, targets, footnote definitions) to the project index, as reStructuredText always has — cross-references into MyST documents resolve (including from reStructuredText, and in the preview links above), and Markdown titles surface in language-server features.
New markup diagnostics
All surfacing as editor squiggles (
ubcode-syntaxsource), inubc checkand in thebuild indexlog, governed by the standard lint configuration:invalid directive option values and choices, diagram content-plus-file conflicts, and non-consecutive Markdown heading levels;
directives missing a required argument (reStructuredText and MyST);
invalid
:variant:/:pep:/:rfc:role content;directive-name collisions between
needs.typesandextend_directivesin the configuration.
🤖 Pharaoh — the agentic engineer (experimental)¶
This release debuts Pharaoh, ubCode’s agentic engineer: describe your
engineering process once — the stages a requirement passes through, and the
evidence each stage needs — and from then on AI does the writing while the
engine does the judging: what is done, what is missing, what to do next.
Experimental (ubc agent is still hidden from ubc --help) and may
change between releases; see the
Pharaoh documentation for the full guide.
Describe your process; the engine judges
A new
[workflow]configuration section (Workflow) declares your process as stages that produce need types, depend on each other, and carry gates (trace links, code coverage, lifecycle, review). On top of it:ubc agent nextnames the single next thing to build (makefor your requirements),gapslists every unmet obligation,statusreports each stage done / ready / blocked, andrelease-checkis the combined final gate — all projections of one deterministic evaluation, so they can never disagree.Onboarding, greenfield and brownfield
ubc agent installscaffolds a workflow from a profile (default: a V-model) plus its AI-asset bundle of skills and agent definitions;install --detectonboards an existing project by deriving a drivable[workflow]from the observed link graph and schemas, raising only what it cannot infer as a few evidence-backed questions (--planpreviews,--answersapplies);ubc agent doctorpre-flights the setup.Quality Analysis — independent review of need substance
The graph judges structure; Quality Analysis judges substance — and the AI never grades its own homework. A new
[quality]section (Quality Analysis) defines criteria packs (per-axis rubrics as TOML data) and per-axis floors: an LLM measures each need, the engine judges pass / fail deterministically at read time, and the IDE explains the result. Verdicts live under.pharaoh/verdicts/and fingerprint both the need’s one-hop content and the criteria, reading as outdated when either drifts. Thereview-brief,verdict-submitandverdict-checkverbs gate substance fail-closed (also folded intorelease-check --with-verdicts); in VS Code, Run QA drives the review through the editor’s language-model access (ubCode never talks to an LLM provider itself), with a results panel and live CodeLens badges.The VS Code cockpit
Pharaoh Workflows and Gaps Overview panels, a workflow-aware needs graph with gap rings and a parent / child depth control, a read-only workflow-editor graph, per-need CodeLenses with gap diagnostics in the Problems panel, and the Drive / Accept / Tailor commands — hand the next step to your AI agent, commit only the authored artefacts, or derive a
[workflow]proposal from existing types and links. Without a[workflow]section none of this appears — the agent surface stays silent until you opt in.The chat participant is now
@pharaohRenamed from
@ubcode(kept as an alias; chat history survives), with high-level intents captured via/pharaoh-intent. The participant — and every driving skill — now runs the extension’s bundled, version-matchedubcbinary: no PATH setup, no mismatched installs.
👌 Improvements¶
Language-server request latency
Requests no longer deserialize the needs / domain / diagnostics indexes from disk — they reuse the in-memory snapshot the background indexer already published (roughly 66 ms → sub-millisecond per request at 10,000 needs), and the indexer reuses its own indexes across passes, so incremental
network_backvalidation now engages between background runs. External CLI builds are still adopted automatically.Faster schema validation: needs-network schema validators are now precompiled once per pass, with a hub-graph fallback for highly connected projects.
Faster index builds: the config-derived directive-specification table is built once per pass and shared, instead of rebuilt per file — roughly 0.6–0.8 s of cold-build wall time on a 5,000-file corpus.
Markdown (MyST) inline constructs now carry precise source ranges, improving diagnostic and navigation targets.
New “Authoring” documentation section: a reference for what the preview renders — directives, roles, needs and diagrams, Markdown — with an explicit differences-from-Sphinx page (see Authoring).
The editor-title preview icon now carries a small magnifier badge, so it is distinguishable from the built-in Markdown preview icon.
Hovering a
:variant:role value in reStructuredText now shows its resolved variant data, and the docutils generic text roles are offered in role-name completion.The PlantUML-to-Mermaid preview conversion handles multi-line and rectangle / hexagon sequence notes, activation-shortcut colours and group
endvariants, with hardened diagram detection.
♻️ Changes¶
Preview always renders via the unified AST; new Rendered / Structural modes
The preview now always lowers the parsed document to the unified AST and renders from it — the per-format concrete-syntax-tree path is retired, along with the internal
server.use_ast_previewflag (never part of a release). The old rich/plain toggle becomes two modes, switchable live from the preview toolbar:Rendered (the default) — every construct renders as its semantic HTML shape (headings, admonitions, images, diagrams, tables, …).
Structural — “show me what was parsed”: every directive and role renders as a uniform placeholder (name, arguments, options, body), identical for reStructuredText and MyST.
The VS Code setting
ubcode.preview.experimentalRendering(boolean) is renamed toubcode.preview.mode(rendered|structural), which seeds the panel’s initial mode.Need extraction is sourced from the unified document AST
Need directives (fields, links, parts, content links, diagnostics) are now extracted from the same unified AST the preview renders from, rather than a separate concrete-syntax-tree walk — an internal unification with no intended behaviour change, apart from the MyST body-role fix below.
‼️ Breaking Changes¶
One-time full re-index on upgrade
The cache format version moved 6.10 → 6.15 (imported-content parsing, glossary display text,
:doc:/:term:site collection, the state-directive diagnostics, include dependency edges) and the config hash gained new inputs ([server] index_on_save/index_debounce_ms,[parse] default_role/highlight_language/rst_prolog/rst_epilog), so every project cold-rebuilds its index once after upgrading. Self-healing — no action needed.New warnings may appear
ubc check/ubc build index(and the editor) now report the markup-semantics, required-argument and role-resolution diagnostics above, theneeds.import.*codes, the interpreted-text role codes below, andconfig.unknown_default_role— CI gating on warning counts may newly fail. Suppress individual codes via the standard lint configuration.New include warnings may appear
Projects with broken
.. include::paths — or jinja-templated ones (e.g.{{ build_root }}/…), which ubCode cannot expand yet — now getdirective.include_not_foundwarnings (~150 on one large real-world corpus, from templated paths alone). Suppress the code until jinja support lands.Diagnostic changes for interpreted text
The
inline.role_no_namewarning is retired — bare `` text `` is legal docutils — so suppressions keyed on it are dead configuration. In exchange,directive.unknown_role,directive.invalid_roleanddirective.role_redefinitionare diagnosed (docutils reports these as errors; on by default).Anchors unified to exact docutils rules
Rendered previews and the objects inventory derive every anchor through one docutils-faithful
make_id(differentially fuzzed against real docutils). Anchors that diverged fromsphinx-buildoutput move to the Sphinx-identical form — a parity upgrade, but external deep links to the old anchors need updating.needextendapplication order now matches sphinx-needsWhen several
needextenddirectives modify the same field of the same need, they apply sorted by(docname, lineno)— the sphinx-needs contract, verified against a real sphinx-needs 8.2.0 build — instead of file-path order. Projects whose path order and docname order differ (e.g.x.rstvsx-y.rst) may see different final values; the new values are the ones sphinx-needs produces.Background indexing now runs on save by default — set
[server] index_on_save = falseto restore the previous behaviour (see the feature entry above).Editor diagnostic source renamed:
ubcode-rst-lint→ubcode-syntax— the source now covers Markdown too. Any client-side filters keyed on the old source string must be updated.Needs inside a falsy
.. if::block are now excluded from the index: previously the (unknown) directive was a no-op, so such needs were still collected.Pharaoh state directory: agent state now lives in
.pharaoh/(a.ubc/from earlier development builds is ignored) — verdicts re-derive on the next review, the install manifest on the nextubc agent install.
🐛 Fixes¶
The CommonMark parser no longer panics on a no-break space (U+00A0) near heading / paragraph boundaries.
Image and figure
srcpaths now resolve correctly in the Markdown (MyST) preview.MyST need bodies now collect their
{need}and{need_part}rolesA
{need}/{need_part}role inside the body of a MyST need directive is now collected (as a content link / need part), matching reStructuredText need bodies. Previously such roles were silently ignored, so content links and parts were missing from the extracted need (and part-dependent diagnostics such as duplicate-part warnings could not fire).MyST diagram directives now render in the Markdown preview.
The MCP stdio server no longer drops connections intermittently on Linux.
Language-server edge-case fixes: float enum values in need-field validation, hover ordering, and schema key handling.