Architecture
This document describes the architecture of the Antora Tracer extension using selected arc42 sections. Each section is a traceable [item] that references the requirements it addresses.
Introduction and Goals
Project scope and architectural goals
The Antora Tracer is an Antora extension that adds role-based requirements traceability to AsciiDoc documentation. It replaces fixed macro types with a single configurable [item] macro.
Architectural goals:
-
Enable users to define their own traceability domain model (roles, relations, matrices) through YAML configuration
-
Maintain a clean separation between parsing, graph storage, validation, matrix generation, and export
-
Ship with built-in presets for common domains without requiring users to write configuration
-
Integrate with Antora’s event pipeline without imposing runtime dependencies on users who don’t need specific features
-
Provide a standalone CLI for use outside Antora
Building Block View
The extension is composed of eight modules organized in a layered architecture:
| Component | Responsibility |
|---|---|
|
Antora entry point. Registers event handlers, creates |
|
Orchestrator. Owns the graph, delegates to parser, matrix generator, and Neo4j exporter. Public API for programmatic use. |
|
Regex-based AsciiDoc parser. Two-pass: extracts |
|
In-memory directed graph. Items stored as |
|
Config-driven matrix generation. Reads matrix definitions from configuration, queries the graph, computes coverage, produces CSV output. Delegates HTML to TemplateRenderer. |
|
Mustache wrapper. Loads templates from |
|
Path resolution for matrix-to-item deep links. Strips `pages/\` prefix and `.adoc\` extension from sourceFile values, generates `../../component/page.html#ID\` links using a configurable `relativePathPrefix\`. |
|
Graph-to-Neo4j export. Produces |
|
YAML configuration loader. Loads and validates config, merges with presets, provides role/relation validation queries to the graph. |
Runtime View
The following sequence shows how an AsciiDoc file flows through the system during an Antora build:
Key timing characteristics:
-
Fire-and-forget async init:
AntoraTraceabilityExtensionconstructor startsinitializeAsync()without awaiting it. Event handlers are registered after config loads. In practice, the Antora build pipeline provides sufficient latency. -
Single-pass processing:
process()call does both parsing and graph population in one call. No separate relationship processing pass needed. -
Lazy rendering: Templates are loaded and compiled on first use, cached thereafter.
Architecture Decisions
This section captures the design decisions from openspec/changes/unified-item-architecture/design.md.
| Decision | Rationale | Alternatives Considered |
|---|---|---|
Single |
Simplifies mental model, enables user-defined roles without code changes, reduces parser duplication |
Keep separate macros (two ways to do things), role as positional parameter (less readable) |
Configuration-based roles and relations (YAML) |
Separates concerns, enables validation, allows presets, version-controllable |
Hardcoded roles (inflexible), JSON (less readable), JS config (more complex) |
Role-based relation validation at processing time |
Catches errors early, prevents invalid graphs, provides clear messages |
No validation (allows invalid graphs), warning only (users miss issues), runtime validation (too late) |
Mustache templates for HTML |
Separates presentation from logic, enables user customization, already working from prior work |
Embedded HTML generation (code duplication), other template engines (Mustache was already in place) |
Neo4j export (not custom query engine) |
Mature graph query capabilities, Cypher is industry standard, no need to build query language |
Custom DSL (high effort), GraphQL (not graph-optimized), Gremlin (less popular) |
Built-in presets for common domains |
Gives starting points, reduces config burden, demonstrates best practices |
No presets (must define from scratch), default roles (conflicts with flexibility goal) |
No default roles |
Forces explicit configuration, prevents domain model assumptions |
Default roles (simpler but less flexible), fallback behavior (complex transition logic) |
Unknown roles = warnings (not errors) |
Graceful degradation, partial config allowed, incremental role adoption |
Error on unknown (too strict), silent ignore (users miss config issues) |
Manual regex parsing over Asciidoctor.js extensions |
Simpler, version-independent, easier to test, works consistently across Asciidoctor.js versions |
Asciidoctor.js block processors (complex, version-dependent API) |
Config-driven matrix generation |
Users define which roles go on rows and columns, which relations count as coverage |
Hardcoded matrix types (req-impl, req-test, full) — inflexible for domain models with different roles |
In-memory source substitution for clickable links |
After processing, replace `` with Asciidoctor xrefs in the content catalog buffer. Two-pass approach ensures cross-file targets are resolved. No disk writes. |
HTML post-processing (PDF-incompatible), Asciidoctor inline macro extension (API dependency) |
Dedicated LinkResolver component |
Separates path resolution from matrix generation, makes link generation configurable and testable in isolation. Robust |
Link generation in MatrixGenerator (mixes concerns), template-level logic (Mustache cannot do complex path manipulation) |