Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

Part 02: DDD/Hexagonal Tiers

The mega-split organises the @frenchexdev/requirements-* family into four tiers plus a cross-cutting band. Lower-numbered tiers do not know that higher-numbered tiers exist; dependency arrows point inward only. That single rule turns the dependency graph from a fragile barrel into an acyclic DAG that the workspace tool can verify mechanically.

The shape

Diagram

Figure 1 — Dependency graph of the @frenchexdev/requirements-* family. Arrows point from consumer to dependency. Tiers are colour-coded: blue (Tier 0 shared kernel + vocabulary), green (Tier 1 analyzers), amber (Tier 2 use cases), pink (Tier 3 CLI), purple (cross-cutting styles). The graph is acyclic by construction; no arrow ever points to a higher tier.

Two packages sit at Tier 0. requirements-shared-kernel owns the infrastructure: base classes, decorators, the RequirementStyle abstraction, branded IDs, the FileSystem port. requirements-requirements owns the vocabulary: the concrete Requirement<S> and Feature classes, plus the self-hosted req-*.ts files that specify the whole ecosystem. Both have zero non-workspace runtime dependencies; the vocabulary package depends on the kernel and on nothing else.

Tier 1 hosts the analyzers — seven of them. Each is a stateless function-pack over the domain: scan an AST, compute coverage, build a trace graph, classify a mutation verdict. Tier-1 packages may consume other Tier-1 packages where strictly necessary (requirements-compliance consumes requirements-scanner; requirements-trace consumes both) but never reach into Tier 2.

Tier 2 hosts the application services — three today. Each represents a coherent use case the CLI exposes: scaffold a test suite (requirements-scaffolders), sync a spec to disk (requirements-sync), drive an interactive wizard (requirements-wizards). Tier-2 packages compose Tier-1 analyzers and Tier-0 primitives. They own no domain types of their own.

Tier 3 hosts a single package: @frenchexdev/requirements, the CLI. It is a commander + @clack/prompts + picocolors shell. Its only job is to translate command-line input into Tier-2 use-case invocations and render the result. Phase 4 of the roadmap will shrink it further by removing the duplicate domain types it still carries from the pre-split era.

Cross-cutting, outside the tier stack: requirements-styles ships five RequirementStyle presets (Default, Industrial, Lean, Agile, Kanban) plus a registry. requirements-styles-demo is a scenario-driven CLI player that demonstrates each preset through a three-FSM orchestrator. Both depend on Tier 0 only and are optional for every other package.

The DDD mapping

The shape is not borrowed from Hexagonal Architecture by accident. Each tier corresponds to a DDD concept the corpus needs.

DDD concept Where it lives
Bounded Context The whole requirements-* family is one bounded context (the Requirements Traceability subdomain).
Ubiquitous Language The four-tier Requirement → Feature → AC → Test chain plus Styles. Identical vocabulary across every package.
Shared Kernel Tier 0 — the tightly-controlled subset shared by everything. Zero deps, narrow surface, every change reviewed.
Domain Services Tier 1 — stateless operations over the domain. Scanning, computing coverage, building traces.
Application Services Tier 2 — orchestrated use cases. Scaffold, sync, run a wizard.
Inbound Adapter Tier 3 — translates CLI input into use-case invocations.
Anti-Corruption Layer The CLI itself acts as the ACL for foreign bounded contexts (the ddd-* family, downstream consumers).

The Shared Kernel mapping is the one most worth defending. In DDD a Shared Kernel is the smallest subset of the domain that two or more teams agree to share, controlled by all participants. Tier 0 in this family is exactly that — the Feature, Requirement<S>, six decorators, and the style abstraction. Everything outside Tier 0 is a capability that consumes the kernel without forcing the kernel to know about it.

The SOLID enforcement

Layering turns SOLID from a coding guideline into a workspace constraint.

SRP — one package, one responsibility. The npm boundary makes the violation impossible to hide: a package.json with two unrelated concerns is visibly wrong in code review.

OCP — registries (StyleRegistry, ScaffolderRegistry) and decorator hooks (@Satisfies, @FeatureTest) extend the family without modifying the kernel. New scaffolders, new styles, new Features arrive as additive imports.

LSPFeature and Requirement<S> are already substitutable. Every package in requirements-requirements/src/ extends one or the other and is consumed polymorphically by the scanner.

ISPFileSystem is a single narrow port; future Tier-1 packages will declare their own narrow ports rather than depend on the lib's previous grab-bag.

DIP — Tier 2 depends on Tier-1 abstractions (or could, via additional ports); the CLI depends on Tier-2 abstractions, not on the deprecated requirements-lib barrel.

The two transitional packages

Two packages still in the workspace do not appear in the diagram as canonical members. They are real today, slated for removal, and useful to mention so the reader can navigate the repo without confusion.

requirements-core is the old name for the shared kernel — requirements-core and requirements-shared-kernel coexist today, with the latter being the target. requirements-core additionally hosts a handful of cores (audit-hooks, toggling, explore-core, rename-core, watch-core) that have not yet been re-tiered. Phase 5 of the roadmap will rename requirements-core to requirements-sharedkernel workspace-wide (a 1,263-file codemod) and redistribute the leftover cores to their correct tiers.

requirements-lib is the old barrel — the pre-split lib. During the migration it becomes a re-export shim — export * from '@frenchexdev/requirements-scanner', and so on for the other twelve capability packages — keeping the old import paths valid. Phase 6 of the roadmap deprecates and then deletes it. New code should not import from requirements-lib.

Both transitional packages are covered again in Part 20, which walks through the phased migration plan in detail.

What the layering buys

The acyclic-by-construction graph is the prize. Workspace tooling (npx pnpm) can verify that no tier ever depends on a higher tier; a future lint rule can encode the tier number in the package.json description and fail any PR that introduces an upward arrow. The barrel's six concerns can grow independently — independent versions, independent coverage thresholds, independent release cadences — and a consumer who needs only the scanner pays for only the scanner.

The next page makes the keystone of all this concrete: how requirements-requirements dog-foods the entire family by exporting its own requirements as importable TypeScript.

⬇ Download