What it reifies
@frenchexdev/requirements-trace is the visualisation tier of the compliance pipeline. Where requirements-compliance (Part 07) renders the traceability matrix as a table, requirements-trace renders it as a graph. Same four-tier chain — Requirement → Feature → AC → Test — different consumer surface: tables are for the CI log, graphs are for the documentation site, the PR review, and the editor pop-up.
The package's package.json description: "Traceability query engine + REQ→FEAT→AC→TEST graph rendering (Mermaid/DOT)." Two capabilities, paired on purpose: the query engine answers structured questions about the chain, the graph renderer turns the answers into a picture.
The public surface
The src/index.ts re-exports two modules:
// @frenchexdev/requirements-trace — Tier-1 analyzer: REQ → FEAT → AC → TEST
// query engine + Mermaid/DOT graph rendering.
export * from './trace-core';
export * from './trace-graph';// @frenchexdev/requirements-trace — Tier-1 analyzer: REQ → FEAT → AC → TEST
// query engine + Mermaid/DOT graph rendering.
export * from './trace-core';
export * from './trace-graph';trace-core is the query engine. It takes the kernel registries (Feature, Requirement, Satisfaction, Refinement) and the scanner manifest, joins them into a single in-memory graph, and exposes structured queries: "what tests verify the Acceptance Criteria of this Feature?", "what Features satisfy this Requirement?", "what Requirements are not satisfied by any Feature?", "what is the shortest path from this Requirement to a failing test?". The queries return typed result types — never strings.
trace-graph is the renderer. It takes a TraceNode[] from the core and emits either a Mermaid graph source or a DOT (digraph) source. The output is text; the consumer decides whether to embed it in Markdown, pipe it to dot, or hand it to a viewer.
Where it sits
Tier 1, third deepest in the family. Depends on requirements-scanner (for the manifest), requirements-compliance (for the cross-reference primitives that the trace engine reuses), and requirements-requirements (for the vocabulary). The dependency on compliance is the one notable horizontal Tier-1 ↔ Tier-1 edge in the family; the alternative would have been to duplicate the cross-reference logic, which would have been a DRY violation visible to any reader.
Three things the package must not do:
- Run a graph layout engine. Mermaid and DOT are text formats — the package emits the text and stops. Layout is the consumer's problem (Mermaid's runtime, Graphviz's
dotbinary, or whichever rendering tool the site builds with). - Cache. The query engine is stateless; a caller that wants memoisation wraps the function themselves. Caching inside the package would couple the cache invalidation strategy to the package boundary, and there is no good default invalidation strategy without knowing what the caller is doing.
- Open files. The query engine takes typed inputs (registries, manifest); the renderer takes typed inputs (nodes); neither package touches
fs. The CLI is the only place that loads the inputs and writes the outputs.
A concrete call-site
The CLI's trace subcommand and the CV site's build pipeline are the two main consumers. The build-pipeline shape, simplified:
import {
buildTraceGraph,
queryByRequirement,
type TraceNode,
} from '@frenchexdev/requirements-trace/trace-core';
import { renderMermaid, renderDot } from '@frenchexdev/requirements-trace/trace-graph';
import {
getFeatureRegistry,
getRequirementRegistry,
getSatisfactionLinks,
} from '@frenchexdev/requirements-shared-kernel';
import { scanTestBindings } from '@frenchexdev/requirements-scanner';
import { fs } from '@frenchexdev/requirements-core/ports';
const manifest = await scanTestBindings(fs, { testDir: 'test', srcDir: 'src' });
const nodes: TraceNode[] = buildTraceGraph({
features: getFeatureRegistry(),
requirements: getRequirementRegistry(),
satisfactions: getSatisfactionLinks(),
bindings: manifest,
});
const subgraph = queryByRequirement(nodes, 'REQ-DOG-FOOD');
const mermaid = renderMermaid(subgraph, { layout: 'TD' });
// the build pipeline writes mermaid into a generated .md file
await fs.writeFile('docs/traceability/req-dog-food.md', `\`\`\`mermaid\n${mermaid}\n\`\`\`\n`);import {
buildTraceGraph,
queryByRequirement,
type TraceNode,
} from '@frenchexdev/requirements-trace/trace-core';
import { renderMermaid, renderDot } from '@frenchexdev/requirements-trace/trace-graph';
import {
getFeatureRegistry,
getRequirementRegistry,
getSatisfactionLinks,
} from '@frenchexdev/requirements-shared-kernel';
import { scanTestBindings } from '@frenchexdev/requirements-scanner';
import { fs } from '@frenchexdev/requirements-core/ports';
const manifest = await scanTestBindings(fs, { testDir: 'test', srcDir: 'src' });
const nodes: TraceNode[] = buildTraceGraph({
features: getFeatureRegistry(),
requirements: getRequirementRegistry(),
satisfactions: getSatisfactionLinks(),
bindings: manifest,
});
const subgraph = queryByRequirement(nodes, 'REQ-DOG-FOOD');
const mermaid = renderMermaid(subgraph, { layout: 'TD' });
// the build pipeline writes mermaid into a generated .md file
await fs.writeFile('docs/traceability/req-dog-food.md', `\`\`\`mermaid\n${mermaid}\n\`\`\`\n`);The call-site reads like prose because every layer is named for what it does. buildTraceGraph joins; queryByRequirement filters; renderMermaid formats. The CLI's trace subcommand wires the same three calls with --format mermaid|dot|json and --query feature=X|requirement=Y|ac=Z options.
Why it is its own package
Two arguments.
First, the renderer's output formats are open-ended. Today Mermaid and DOT; tomorrow SVG (via a layout pass), HTML (via a templated table), JSON-LD (for downstream RDF tooling), Excalidraw, mind-map, whatever the site needs. Each new format is a new exported function. Keeping the package narrow — "traceability rendering" — means each format addition is local; bundling the renderer into requirements-compliance would have meant every format change forced re-testing the matrix renderer.
Second, the query engine has its own consumers. The CV site's blog pipeline calls queryByRequirement to generate a per-Requirement traceability box at the top of every blog article that links to a @Satisfies chain. The build pipeline calls queryByFeature to validate that every state machine in data/state-machines.json has at least one bound test. Neither consumer wants a Mermaid graph; both want typed query results. Splitting trace-core (queries) from trace-graph (rendering) inside the same package, but allowing consumers to import either subpath through ./trace-core / ./trace-graph exports, means the build pipeline does not pay for the rendering machinery.
The package also unlocks a feature the pre-split state could not: per-package trace graphs. Each Tier-1 and Tier-2 package can render its own slice of the trace graph as part of its generated docs, scoped to just the Features it owns. Before the split, the rendering code lived in requirements-lib and consumers had to filter the whole-workspace graph by hand; now, each package can call buildTraceGraph with its own registry slice and get exactly the picture it needs.
The next page covers the package that serialises the spec to JSON and back: requirements-spec-io.