Contents Menu Expand Light mode Dark mode Auto light/dark, in light mode Auto light/dark, in dark mode Skip to content
ubCode 0.38.0 documentation
Light Logo Dark Logo

Start here

  • What is ubCode?
  • Installation
  • Quickstart
  • Your first traced project

Choose your path

  • Requirements & traceability
  • Publish HTML & PDF
  • Sphinx & Sphinx-Needs
  • Engineer with AI agents

How-to guides

  • Trace source code
  • Build variants
  • Write reports
  • Run ubc in CI
  • Use another editor
  • Build an HTML site
  • Build a PDF
    • Page classes at work
  • Migrate from Sphinx-Needs

AI agents

  • AI agents, checked by the engine
  • Pharaoh agentic engineer
    • Quick start
    • Configuring the workflow
    • Reviews and quality analysis
    • Recovering intent from code
    • Changing what is already specified
    • The ubc agent command
    • In VS Code
  • MCP server
  • Chat participant

Writing and configuration

  • Writing content
    • reStructuredText basics
      • The basics in MyST
    • reStructuredText tables
      • Tables in MyST
    • References and links
      • References and links in MyST
    • Roles
      • Roles in MyST
    • Markdown (MyST)
      • MyST core syntax
      • MyST extensions
    • Needs
      • Writing needs
        • Writing needs in MyST
      • Need roles
        • Need roles in MyST
      • Need views
        • Need views in MyST
      • Importing and extending needs
        • Importing and extending needs in MyST
      • Dynamic functions and filters
        • Dynamic functions and filters in MyST
    • Variants
      • Variants in MyST
    • Test reports: the directives
      • Test reports in MyST
    • Traced source code
    • Directives
      • Admonitions
        • Admonitions in MyST
      • Code and includes
        • Code and includes in MyST
      • Images, figures and video
        • Images, figures and video in MyST
      • Version markers
        • Version markers in MyST
      • Glossary, index, confval and envvar
        • Glossary, index, confval and envvar in MyST
      • Structure and layout
        • Structure and layout in MyST
      • Conditional content
        • Conditional content in MyST
      • Page state
        • Page state in MyST
      • Math
        • Math in MyST
      • Todos
        • Todos in MyST
      • Directives in MyST
    • Layout components
      • Layout components in MyST
    • Diagrams
      • Diagrams in MyST
    • Unsupported syntax
    • Differences from a Sphinx build
    • Writing content in MyST
  • Configuration
    • Needs
    • Card designs with [needs.card_layouts]
    • Field links with [needs.string_links]
    • Schema validation
    • Derived fields
    • Deprecated needs options
    • Codelinks (source code tracing)
    • Test reports
    • Intersphinx
    • Link checking
    • Parsing
    • Linting
    • Formatting
    • Server
    • Project, source & scripts
    • HTML site
    • PDF document
    • Workflow
    • Quality Analysis

Reference

  • Feature matrix
  • Command line (ubc)
  • Configuration keys
    • [agent]
    • [build.highlight]
    • [build.html]
    • [build.mermaid]
    • [build.pdf]
    • [build.plantuml]
    • [codelinks]
    • [format_rst]
    • [intersphinx]
    • [linkcheck]
    • [lint]
    • [needs]
    • [needs_json]
    • [parse]
    • [project]
    • [quality]
    • [reports]
    • [rst_lint]
    • [scripts]
    • [server]
    • [source]
    • [test_reports]
    • [variants]
    • [workflow]
  • VS Code
  • MCP tools
  • Diagnostic codes
  • VS Code features
    • Home view
    • Linting
    • RST preview
    • Realtime index
    • Needs filtering
    • Needs graph view
    • Navigation
    • Site Map
    • Commands
    • Diff & impact analysis
    • needs.json view
  • Query language
  • Languages
  • Supported toolchain
  • What works in imported needs

Concepts

  • Key concepts
    • Glossary
  • Maturity levels

ubCode for teams

  • ubCode for teams
  • Request a license
  • Troubleshooting
  • Report an issue

Development

  • Roadmap
  • Changelog
    • 0.38.x
    • 0.37.x
    • 0.36.x
    • 0.35.x
    • 0.34.x
    • 0.33.x
    • 0.32.x
    • 0.31.x
    • 0.30.x
    • 0.29.x
    • 0.28.x
    • 0.27.x
    • 0.26.x
    • 0.25.x
    • 0.24.x
    • 0.23.x
    • 0.22.x
    • 0.21.x
    • 0.20.x
    • 0.19.x
    • 0.18.x
    • 0.17.x
    • 0.16.x
    • 0.15.x
    • 0.14.x
    • 0.13.x
    • 0.12.x
    • 0.11.x
    • 0.10.x
    • 0.9.x
    • 0.8.x
    • 0.7.x
    • 0.6.x
    • 0.5.x
    • 0.4.x
    • 0.3.x
    • 0.2.x
Back to top
View this page
Edit this page

Tracing source code with Codelinks¶

VS Code CLI MCP Beta

Added in version 0.29.0.

This guide walks you through embedding traceability markers in source code and bringing those markers into your documentation as Sphinx-Needs objects. For the full configuration reference, see Codelinks (source code tracing).

What is source code tracing?¶

In a Docs-as-Code workflow, requirements, specifications, and test cases usually live in reStructuredText files. But the code that implements those requirements lives somewhere else — in .py, .cpp, .rs, or other source files. When you need to prove that a requirement is covered by code (or that a piece of code relates to a specific requirement), you have to maintain that mapping by hand.

Source code tracing solves this by letting you embed traceability markers directly in code comments. ubCode’s Rust engine scans those comments, extracts the markers, and converts them into proper need objects — complete with IDs, types, links, and metadata. You get a live, bidirectional link between your documentation and your code without any manual bookkeeping.

Marker types¶

Codelinks supports three kinds of markers. Each one serves a different purpose.

One-line need definitions¶

A one-line marker creates a new need from a single comment line. Fields are positional, separated by the field_split_char (comma by default).

Source code (Python)
# @Function Bar, IMPL_4, impl, [SPEC_1]
Resulting need (equivalent RST)
.. impl:: Function Bar
   :id: IMPL_4
   :links: SPEC_1

With the default configuration this produces a need with:

  • Title: Function Bar

  • ID: IMPL_4

  • Type: impl

  • Links: SPEC_1

The field order, separator, and start/end sequences are all configurable via [codelinks.projects.<name>.analyse.oneline_comment_style] — see analyse.oneline_comment_style for the full reference.

Defaults and optional fields: Fields with a default value can be omitted from the right-hand side. For example, if type defaults to "impl" and links defaults to [], then:

# @Function Bar, IMPL_4

is enough to create the same impl need, just without links.

Escaping: If a field value contains the split character (,) or square brackets, escape them with a backslash:

# @Title\, with comma, IMPL_5, impl, []

This produces a need titled Title, with comma.

Need ID references¶

A need-ID reference links a code location to existing needs without creating new ones. It records that this spot in the code is related to the listed requirements.

Source code (C++)
// @need-ids: REQ_001, REQ_002
Effect

The code location is linked to needs REQ_001 and REQ_002. No new needs are created.

The marker keyword (@need-ids: by default) is configurable in [codelinks.projects.<name>.analyse.need_id_refs] — see analyse.need_id_refs.

The resolved code URL is written onto each referenced need under the field named by ref_url_field (default code_url) — see ref_url_field.

Supported languages¶

Codelinks recognises the comment syntax of several programming languages. Set the comment_type in [codelinks.projects.<name>.source_discover] to tell ubCode which language to expect.

Language

Value

Comment syntax

Discovered file extensions

C / C++

"cpp"

// (single-line), /* */ (multi-line)

.cpp, .hpp, .h, .cc, .hh, .c++, .h++

Python

"python"

# (single-line), """ """ (docstrings: the text between the quotes)

.py

Rust

"rust"

// (single-line), /* */ (multi-line), /// and //! (doc comments)

.rs

C#

"csharp"

// (single-line), /* */ (multi-line), /// (XML doc comments)

.cs

YAML

"yaml"

# (single-line)

.yaml, .yml

Go

"go"

// (single-line), /* */ (multi-line)

.go

Bash / Shell

"bash"

# (single-line)

.sh, .bash, .zsh, .ksh

TypeScript / JavaScript

"typescript" (or "ts")

// (single-line), /* */ (multi-line), /** */ (JSDoc), <!-- (legacy single-line, JavaScript)

.ts, .tsx, .mts, .cts, .js, .jsx, .mjs, .cjs

Note

For TypeScript / JavaScript, the default one-line start_sequence = "@" is JSDoc’s own sigil (@param, @returns, @deprecated): a JSDoc tag line the one-line parser would read as a marker or warn about (@param {number} b First, second — a comma, say) is a jsdoc_tag warning rather than a need, and one it would ignore (@param a the augend) stays silent. Set a start_sequence JSDoc does not use, such as "@@" or "[[" (not "@need": with get_multiline_needs on, that is the multi-line open word, and the configuration is refused). Under "[[", a JSDoc line naming ECMAScript’s internal-slot notation ([[Prototype]]) with a comma is read as a marker; for C/C++, "@@" is the choice, since commented-out attribute syntax (// [[nodiscard]] int add(int a, int b);) would be too. Discovery also excludes common generated-output and dependency directories (node_modules, dist, build, …) by default for this language — see source_discover.

The .. src-trace:: directive¶

Once a codelinks project is configured, include the traced needs in your documentation using the .. src-trace:: directive:

.. src-trace::
   :project: my_app

This inserts all needs discovered by the my_app codelinks project.

Scope the results to a single file or a subdirectory:

.. src-trace::
   :project: my_app
   :file: main.cpp

.. src-trace::
   :project: my_app
   :directory: core/

Multiple .. src-trace:: directives can appear in the same document to combine needs from different projects or scopes.

Note

The :file: and :directory: options are mutually exclusive — use one or the other, not both.

What the directive renders¶

The directive draws one need card per traced need, at its own position, in ascending id order. A one-line need has no body — a one-line marker carries none — so its card is its type, its title and its id in the header, plus a metadata row for every other field the marker set (a :links: marker field shows as that link type’s own row, links outgoing, not under the field name) and for the source URLs if you enabled them.

Traced needs are ordinary needs from that point on:

  • :need:`IMPL_HELLO` links to the card, on whichever page the .. src-trace:: that traced it sits.

  • A traced need appears as a link in other needs’ links incoming and links outgoing rows, like any need you wrote by hand.

  • The cards are indexed for search, because a traced need’s card at its trace site is the only card the search index records for it. (A needextract can card it too, but an extract card carries no anchor of its own, so it is deliberately left out of the index.)

Three situations render the container with no cards inside it:

  • A scope that could not be read — a :file: naming a file that is not there, for instance. The read failure is reported as a needs.src_trace diagnostic, so the empty container is not the only thing you have to go on.

  • A scope whose needs are all claimed by another site. If two .. src-trace:: directives cover the same marker, only one of them draws its card — the one on the earliest page by path, and earliest on that page — because publishing the same need id twice in one site would break the page’s own anchors. The duplicate is reported as needs.duplicate, naming both directives.

  • A need of the same id written, imported or declared external elsewhere. A hand-written, imported or external need always wins the id over a traced one, so the traced registration is demoted and draws no card. A site all of whose markers collide this way renders empty, and the collisions are reported as needs.duplicate.

In the editor preview, a site shows at most needs.import_preview_max cards (50 by default) and says so when it truncates, so a directive tracing a large source tree stays responsive while you type. ubc build html always renders every card.

Multi-line needs¶

Added in version 0.37.0.

With get_multiline_needs on (analyse), a need can be written across the lines of a comment, and the directive draws its card with its body, among the one-line needs. A traced file such as

// @Login handler, IMPL_LOGIN, impl
int login();

/**
 * @need req: Login must be rate limited
 * :id: REQ_LOGIN_1
 * :links: IMPL_LOGIN
 * :status: open
 *
 * After **five** failed attempts the account is locked,
 * see :need:`IMPL_LOGIN`.
 * @endneed
 */
int rate_limit();

traced with

.. src-trace::
   :project: my_app
   :file: auth.cpp

draws two cards: IMPL_LOGIN, and REQ_LOGIN_1 with its status and links rows and the body below them — “five” in bold, the :need: a link to IMPL_LOGIN’s card.

  • The options are a need directive’s. A key is read case-insensitively (:Status: is status), links and tags are split, a typed field is converted, and a block without :id: gets the id a .. req:: with that title would get — or, with no title, the one hashed from its body, as any untitled need’s is. An empty :id: refuses the block; delete, title_from_content, docname, lineno, parts and arch are warned about and ignored, the need kept; a key given twice (in any case) keeps its first value. The title is the open line’s, as a one-line need’s is: title_from_content does not apply to a block.

  • The body is parsed in the block’s markup, whatever the page’s: @need[md] routes it to your Markdown parser and the default to reStructuredText, through [parse.parsers] as a file of the markups suffix would be. A markup no configured parser claims is parsed as the page’s markup, with one warning per markup per directive (also when no [parse.parsers] is configured and the markup is not reStructuredText). A heading in a body follows a need directive’s body: a reStructuredText section title is reported (block.title_disallowed) and left out, and a Markdown ## heading is shown as a rubric.

  • The need is located at the directive, as a one-line need is, and its doctype is the block’s markup. Its local-url / remote-url name the block’s open line.

  • A body is restricted: it cannot read files, its images and other assets are not resolved, a need directive (or a needimport, list2need, nested src-trace) in it is reported and not executed, and a needuml in it is shown as its source rather than drawn. Nor does it see the page’s substitutions or rst_prolog.

  • A label in a body is a target of the page that renders the card. An explicit target (.. _label:), a :name: on a directive in the body, an inline target and a MyST (label)= register with that page, so a :ref: from the page, from another page or from a MyST page reaches it, objects.inv lists it, and go-to-definition jumps to its line in the source file. The page’s own ids come first: a body name the page already holds — the same name, or a name whose anchor is already one of the page’s labels or sections — is reported as std.duplicate_target at the body line and does not register, and so is the second of two blocks on one page defining one name: the first in reading order keeps it — the src-trace directives in document order, the cards by need id within a directive. A body label is a :ref: / {ref} / #label target; a docutils named reference (label_) from the page to it is still reported as unknown. The same name in blocks on two pages is reported at both source lines. A body’s footnotes are numbered on their own, from [1], without moving the page’s. A hidden block’s labels register too (its card is drawn, collapsed).

  • A glossary term, a std object or a citation in a body registers the same way. A term, a confval or envvar and a citation in a body register with the page that renders the card, under the same page-first and first-block rules, so :term:, :confval:, :envvar: and [X]_ (and {term}, {confval}, {ref} in MyST) reach it from any page, and objects.inv lists it. A name the page already holds is reported with its own code (std.duplicate_term, std.duplicate_confval or std.duplicate_envvar, std.duplicate_citation), and so is a name whose anchor a page label, section, term or object already takes (std.duplicate_target). option and program are not modelled, in a body or on a page.

  • Every finding is at the source line — an option at its own line, an unknown type at the open line, a refused construct at its body line — with the src-trace directive as a second location. A dangling :need: or :ref: inside a body is reported at the block’s open line.

  • A hidden block (:hide:) draws its card collapsed, as a hidden need does anywhere.

See The .. src-trace:: directive for the full directive reference.

How it works under the hood¶

When ubCode encounters a .. src-trace:: directive, it runs the codelinks pipeline:

  1. Discover — walk the configured src_dir, applying include/exclude patterns and .gitignore rules, to find source files.

  2. Parse — read each file and extract markers (one-line needs, need-ID references) according to the language’s comment syntax.

  3. Cache — store results per file. On subsequent runs, only changed files are re-parsed (based on modification time), so incremental updates are fast.

  4. Attach URLs — if set_local_url or set_remote_url is enabled, compute file URLs using the configured patterns.

  5. Convert — transform extracted markers into proper need items and merge them into the needs index.

The entire pipeline runs in Rust and executes in parallel, so even large codebases are traced in milliseconds.

ubCode vs. Sphinx-Codelinks¶

The codelinks marker format was introduced by Sphinx-Codelinks, a Sphinx extension. ubCode reimplements the same analysis pipeline in Rust and integrates it directly into its engine.

Aspect

Sphinx-Codelinks

ubCode codelinks

Installation

pip install sphinx-codelinks

Built into ubCode (VS Code extension, ubc CLI, MCP)

Configuration

codelinks.toml referenced from conf.py

[codelinks] section in ubproject.toml

When it runs

During sphinx-build

In real time as you edit (VS Code), on demand (CLI), or via MCP

Performance

Python-based, runs during Sphinx build

Rust-based, incremental, parallel; millisecond-scale updates

Marker format

Same (one-line needs, need-ID refs)

Same (one-line needs, need-ID refs)

Both can coexist. Use ubCode for fast feedback while editing, and Sphinx-Codelinks for the final Sphinx build. The marker format is identical, so source files do not need any changes.

Getting started¶

Follow these steps to set up codelinks in your project.

  1. Define a codelinks project in your ubproject.toml:

    [codelinks.projects.my_app]
    remote_url_pattern = "https://github.com/org/repo/blob/{commit}/{path}#L{line}"
    
  2. Configure source discovery so ubCode knows where to look and what language to expect:

    [codelinks.projects.my_app.source_discover]
    src_dir = "../src"
    comment_type = "python"
    
  3. Configure the analysis to select the marker types you want:

    [codelinks.projects.my_app.analyse]
    get_oneline_needs = true
    get_need_id_refs = true
    
  4. Add markers in your source code:

    # @Validate user input, IMPL_AUTH, impl, [REQ_AUTH]
    
  5. Include traced needs in your documentation:

    .. src-trace::
       :project: my_app
    
  6. Enable remote URLs (optional) so each traced need links back to GitHub/GitLab:

    [codelinks]
    set_remote_url = true
    

See Codelinks (source code tracing) for the full configuration reference and Complete example for a complete example.

Real-time diagnostics¶

Added in version 0.30.0.

When you open or edit a source file that belongs to a codelinks project, ubCode validates one-line markers (and, with get_multiline_needs on, multi-line needs) in real time and reports problems as warnings directly in the editor. Diagnostics appear inline and in the Problems panel, just like RST lint rules.

The following warning codes are reported:

Code

Description

too_few_fields

The marker has fewer fields than the configured field layout requires.

too_many_fields

The marker has more fields than expected.

missing_square_brackets

A list field (e.g. links) is missing its surrounding [ ].

not_start_or_end_with_square_brackets

A list field has brackets on only one side.

newline_in_field

A field value contains a line break, which is not allowed in one-line markers.

docstring_tag

In a Python docstring, the start sequence is directly followed by a docstring tag in the Epydoc field shape — :, or spaces or tabs, one word without spaces, tabs or colons, and : (@return: text, @param a: text) — so the line is documentation and yields no need; # comments, other docstring lines and other languages are parsed as one-line markers as usual.

jsdoc_tag

In a TypeScript or JavaScript JSDoc comment (opened by exactly /**), under the start sequence @, nothing alphanumeric precedes it, a JSDoc tag such as param, returns or link directly follows it and ends at a space, a tab, {, } or the line’s end (@param{number} a, {@link Foo}), and the one-line parser would otherwise have read the line as a marker or warned about it, so the line is documentation and yields no need; a tag line the parser would have ignored (@param a the augend) stays silent. Under any other start sequence the check does not run; choose one JSDoc does not use, such as @@ or [[.

multiline_need_header

A multi-line need’s open line does not match @need[<markup>] <type>: <title> (or its open word sits behind a * leader the block comment does not strip); the block is skipped, its lines up to the close are still not read for one-line markers.

multiline_need_unterminated

The comment ends before a close line; the block is skipped.

multiline_need_markup

The open line names a markup that markups does not know; the project default is used.

multiline_need_oneline_form

The open and close words are on one line (a one-line need is written with the one-line marker); the block is skipped.

multiline_need_duplicate_option

An option key repeats an earlier one, or names a key the need sets itself (type, title, content, doctype); the first value is kept.

multiline_need_nested_open

The open word starts a line of a block’s body; it stays body text (one need per block).

All diagnostics use the source label ubcode-codelinks.

A build (ubc build html, ubc check) reports the same warnings for the files a src-trace reads, at their source file and line with the directive as the second location, each under codelinks.<code> (codelinks.too_many_fields, codelinks.multiline_need_unterminated, …). A multi-line need’s own findings keep their own codes: block.directive_unknown_option (an option a need does not take), directive.invalid_option_value (an empty :id:), codelinks.multiline_need_duplicate_option (a key repeated in another case), needs.unknown_type, needs.build_error (a value that does not convert), needs.create_need (a field its type requires has no value), needs.import.denied_ingest and needs.import.nested_generative (a refused construct in a body), codelinks.multiline_need_unclaimed_doctype and codelinks.multiline_need_ambiguous_doctype (a markup no parser, or two parsers, claim), and needs.link_ref / std.ref for a dangling reference in a body. A label in a body that the page already holds is std.duplicate_target, at the body line with the directive beside it, reported with the project’s diagnostics (ubc check, ubc build) rather than on the file open in the editor: Duplicate local target name "<label>", same as on line <N> of <file> when the page (or an earlier block) has the name, and Target "<label>" would take the anchor "<anchor>", which <file> already uses when the page already uses its anchor. needs.src_trace, at the directive, keeps what none of these codes covers: a src-trace whose files could not be discovered or read, a one-line marker that does not become a need (an unknown type, a field that cannot be set) or whose predicates warn, and a codelinks project whose configuration does not validate. lint.ignore = ["needs.src_trace"] silences none of the codes above — list the ones you mean.

Note

Diagnostics require the VS Code extension’s document selector to include the language of the source file. The default selector covers all languages listed in Supported languages above. If you have overridden ubcode.server.documentSelector in your settings, make sure the relevant language IDs are included.

Source code outside the docs folder¶

In most projects, source code lives in a different directory tree than the documentation. ubCode normally discovers configuration by walking up parent directories until it finds a ubproject.toml. For files outside that tree (such as ../src/main.py), the walk fails because no ubproject.toml exists above them.

To handle this, place a ubproject.redirect.toml in the source directory:

# src/ubproject.redirect.toml
path = "../docs"

This tells ubCode to look for the configuration in the docs/ directory. See Configuration redirect with ubproject.redirect.toml for details.

Further reading¶

  • Codelinks (source code tracing) — the full [codelinks] configuration reference.

  • Complete example — a complete, annotated ubproject.toml example.

  • Key concepts — source code tracing concepts alongside needs, links, and RST.

  • Codelinks documentation — the standalone Sphinx-Codelinks reference, covering the Sphinx extension, CLI, and advanced topics.

Next
Variant builds
Previous
Engineer with AI agents
Copyright © 2026, team useblocks
Made with Sphinx and @pradyunsg's Furo
On this page
  • Tracing source code with Codelinks
    • What is source code tracing?
    • Marker types
      • One-line need definitions
      • Need ID references
    • Supported languages
    • The .. src-trace:: directive
      • What the directive renders
      • Multi-line needs
    • How it works under the hood
    • ubCode vs. Sphinx-Codelinks
    • Getting started
    • Real-time diagnostics
    • Source code outside the docs folder
    • Further reading