Processing Pipeline

Build integration

The extension hooks into Antora’s event pipeline through two events:

  • contentClassified — fires once after content classification, before document conversion. The extension parses items, expands macros, substitutes links, and registers generated matrices and the per-version graph snapshot in the content catalog.

  • sitePublished — fires once after all pages are published. The extension writes the standalone traceability output directory (matrices, coverage, index).

The same RequirementsTraceabilityExtension class works standalone (via CLI) or within Antora — the event handlers are optional.

Pass structure within contentClassified

The extension runs four passes within the contentClassified event. The order matters — each pass depends on the result of earlier ones.

Pass 1: Parse and populate the graph

DocumentParser scans every page and partial for [item] blocks and inline relationship macros. Items and relationships are registered in the TraceabilityGraph. Both pages and partials are processed here. Files from components named by the excludeComponents extension option are filtered out before this pass, so excluded components never enter the graph and receive no macro expansion or generated attachments.

The graph must be complete before any subsequent pass can resolve cross-file references. This is why Pass 1 processes all files before moving on.

Pass 2: Expand relation macros (pages and partials)

tracer:outgoing[], tracer:incoming[], and tracer:links[] placeholders are replaced with formatted relationship lists. The expansion queries the graph to enumerate relationships for each item.

This pass is opt-in: only files whose resolved :traceability-links: attribute is true get macro expansion. The attribute can be set per page in the document header, or globally via the component’s antora.yml or the playbook asciidoc.attributes. Partials have the attribute forced on since they have no document attributes of their own. Files without the attribute have the macro text stripped.

Display style (list, table, inline), sort order (target-id, target-title, relation-type), and collapsible sections are configurable via document attributes.

Pass 2b: Expand graph macros (pages and partials)

tracer:graph[], tracer:graph-coverage[], and tracer:config-graph[] placeholders are replaced with rendered diagram image references. The graph’s toDot() and toVegaLite() methods generate the item and coverage diagram source; toConfigDot() renders the configuration model.

This pass is opt-in via :traceability-graph: true. It’s independent of Pass 2 — you can enable graph macros without link macros, and vice versa.

Inline relationship macros (``) are replaced with clickable xrefs to the target item’s source page. This is the mechanism that makes matrix cells clickable links to item definitions.

For page items: xref:page.html#ID. For partial items: link:URL#ID (since partials don’t produce HTML pages).

This pass also runs injectTitleIds() (prepends ID to title for visible display) and unindentItemMacros().

Artifact registration (per component version)

After Pass 3, the extension registers generated artifacts in the content catalog for each component version, under every module with AsciiDoc content. Matrices (HTML, CSV, JSON) are generated from the working graph. A traceability/graph.json snapshot serializes the same version’s working graph in the canonical { format, component, version, items, relationships } shape, excluding pubUrl. Both are gated by the generateMatrices option and refresh a committed copy in place rather than adding a duplicate.

Graph lifecycle

The extension maintains two graphs with distinct lifecycles:

  • Working graph — cleared and rebuilt per version during contentClassified (spanning components at the same version), so xrefs stay version-scoped (REQ-249).

  • Full graph — accumulated across all components and versions by merging each version’s working graph; the sitePublished generation passes (matrices, coverage, overview) read this graph.

The working graph’s lifecycle:

  1. Creation: The extension initializes both graphs synchronously in its constructor. Config/preset loading is a synchronous file read.

  2. Population (Pass 1, per version): The working graph is cleared, then items and relationships are added for that version. Validation runs on each addRelationship() call. After each version, the working graph is merged into the full graph.

  3. Quiescent (between Pass 1 and Pass 2, within a version): The working graph is complete for that version. Queries and macro expansion are safe.

  4. Macro expansion (Pass 2/2b/3): The working graph is read-only. Macros query it to generate output. No new items or relationships are added.

  5. Finalization: The standalone output directory (matrices, coverage, overview) is written at sitePublished from the full graph, so it spans every component and version. The graph.json snapshot is not part of this pass — it is registered during contentClassified from the working graph, so each published version keeps its own copy.

Component interaction

The extension follows a dependency-injection pattern:

  • ConfigLoader is created first (config/preset loading)

  • TraceabilityGraph receives ConfigLoader for validation

  • DocumentParser receives ConfigLoader for role validation

  • MatrixGenerator receives both TraceabilityGraph and ConfigLoader

  • Neo4jExporter receives TraceabilityGraph

Each component is independently testable without mocking frameworks. There is no service locator, DI container, or global state.

File state caching

The prepareFile() method computes file content, document attributes, and item block positions once per file, then feeds the same PreparedFile to all macro-expansion methods. This eliminates redundant buffer conversions and regex scans.