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 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.

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)

traceability:outgoing[], traceability:incoming[], and traceability: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 with :traceability-links: true get macro expansion. 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)

traceability:graph[], , and 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().

Graph lifecycle

The traceability graph has a defined lifecycle — understanding which operations are valid in each state explains why pass ordering matters:

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

  2. Population (Pass 1): Items and relationships are added. Validation runs on each addRelationship() call — source exists, target exists, relation allowed by config.

  3. Quiescent (between Pass 1 and Pass 2): The graph is complete. All items and relationships from all files are loaded. Queries and macro expansion are safe.

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

  5. Finalization: Matrices are generated from each version’s graph during contentClassified and registered in the content catalog as attachments; the standalone output directory is written at sitePublished.

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.