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

Appendix B — Frequently Asked Questions

A series of twenty-five or so short engagements with the questions that come up most often when someone looks at the DSL from the outside. None of these answers is a one-liner. The goal is to say enough that a reader can decide whether the pattern fits their project without having to read the entire series first — but also to point them at the canonical chapter whenever the real answer is too long to fit in this form.

This appendix is arranged roughly from "can I use it at all?" through "how does it scale?" to "how does it compare?". Read it in order if you are new to the series; use it as a lookup if you have already read the chapters.

A recurring note: every answer below assumes the package in question is @frenchexdev/requirements, extracted from the CV monorepo and described across the thirty-three chapters of this series. Questions about the earlier typed-specs/ package are answered separately, because that package models Feature without modelling Requirement — the whole point of this series is to close that gap.


1. Can I retrofit this on an existing project?

Yes, and the retrofit path is the path the package itself took. The canonical walkthrough is Chapter 01b: Historical Path — Feature First, Requirement Later, which reconstructs how an existing codebase with @FeatureTest/@Verifies decorators but no Requirement stratum can add Requirements incrementally without rewriting the Features that already exist.

The short version of that chapter is: Features do not need to change their shape. You keep your Feature subclasses, you keep your abstract AC methods, you keep your decorator chain. What you add is a new folder of Requirement classes and a single @Satisfies(...) decorator on each Feature that now points at the Requirements it meets. The compliance scanner treats Features with no @Satisfies as unlinked rather than invalid, so you can retrofit one Feature at a time without a big bang migration.

The honest caveat: retrofitting reveals the Requirements your project already had but never named. Some of those unnamed Requirements turn out to be contradictory once you write them down. That is a feature of the retrofit (the point of the DSL is to surface structural confusion), but it is not free — expect to spend time arguing with yourself or your team about which Requirement a given Feature actually satisfies. That argument is exactly the argument a proper requirements process would have had before the Feature existed; the retrofit just moves it to the middle of the project instead of the start.

If your project is small (fewer than ten Features), the retrofit typically takes an afternoon. At fifty Features, plan a week. Above that, do it chapter by chapter, matching the order of the series itself.


2. What if I don't use vitest? (Jest, Mocha, Playwright)

The package is test-runner-agnostic at the decorator layer. The decorators themselves — @FeatureTest, @Verifies, @Satisfies, @Refines — emit no describe/it and call no test runner APIs. They are plain TypeScript decorators that register metadata into a module-scope registry. See Chapter 07: Fifty-Four Tests, Only Decorators for the full argument and the code.

What ties the decorators to vitest in this project is not the decorators. It is two things: the tsx runner the requirements CLI uses to load the decorated files, and the coverage configuration the quality gates read from vitest.config.ts. Both are swappable. A Jest user can point the CLI at jest --testPathPattern=requirements and configure the compliance scanner to read Jest's coverage JSON instead of vitest's. A Mocha user does the same with nyc.

The Playwright case is more interesting. Playwright tests are functional end-to-end tests, and the @FeatureTest/@Verifies decorators map cleanly onto Playwright's test.describe/test shape if you stop thinking of them as describe/it replacements and start thinking of them as metadata adornments. The pattern is: a Playwright test(...) call gets a @Verifies('FEAT-X.acY') decorator on its containing class method, and the compliance scanner cross-references the Playwright test ID with the Feature registry. See Chapter 17: Cross-Package Adoption — Monorepo Sweep for the concrete wiring.

The honest limit: the dog-food rule (REQ-DOG-FOOD, zero describe/it) is a rule that the requirements package imposes on itself. It is not a rule the package imposes on your project. You can consume the decorators and keep your existing describe/it in parallel; the package does not care. What the compliance scanner cares about is whether your @Verifies annotations cover the ACs declared on your Features, not what the surrounding test harness looks like.


3. Why not just use Jira Epics / Linear / Asana?

Because those tools model work and this DSL models types. The distinction sounds abstract but it is concrete: a Jira Epic can be renamed, closed, or deleted without the compiler noticing. A Requirement class cannot. If you delete ReqDogFoodRequirement the project does not compile. If you rename it to ReqDogFoodingRequirement without updating the import in FeatureTraceExplorerTuiFeature, the project does not compile. The type system carries the traceability load — not a SaaS database.

The full treatment of this distinction is Chapter 19b: External Vocabulary — Jira, SysML, ISO 29148, BDD. The short version: Jira-class tools are excellent at the process layer (who is working on what, when is it due, what is blocked) and poor at the modelling layer (what are the entities, how do they compose, does the structure admit contradiction). The DSL does the reverse. The two layers are not in competition; most mature teams need both.

That said, the DSL does not forbid Jira. The compliance scanner can emit a JSON export that a Jira plugin could consume, and the reverse — generating Requirement class stubs from a Jira Epic query — is a roadmap item (see Chapter 22: Roadmap and Open Questions). The opinion baked into the DSL is that Jira should be a view on the authoritative type-level model, not the other way around. Teams that put Jira first and the code second end up with the typed-specs problem: the word Requirement appears everywhere in the tickets and nowhere in the code.

The honest trade-off: if your team already runs on Jira and your product managers are never going to read TypeScript, the DSL gives you nothing your PM can see. It gives the engineers something — a structural check the compiler enforces — but that value does not travel upward. Whether that is worth the adoption cost is a question this appendix cannot answer; it depends on whose pain you are trying to reduce.


4. Does @Refines compose transitively?

Yes. @Refines(Parent) establishes a parent-child edge in the Requirement graph; a Requirement can refine a parent that itself refines a grandparent, and the traceability graph walks the chain. The walk is implemented in src/lib/refinement-graph.ts and exercised by Chapter 12: Traceability Graphs — The Big Picture.

Transitive refinement means you can write a high-level Requirement like REQ-DISCOVERABLE-TRACEABILITY (a user browsing the graph must discover relations without prior knowledge of the vocabulary) and then decompose it into lower-level Requirements like REQ-HELP-OVERLAY (the browser exposes a ? keybinding that opens a help overlay) and REQ-BREADCRUMB-TRAIL (the browser displays the path from the current node back to the root). Each lower Requirement is @Refines(ReqDiscoverableTraceabilityRequirement). When a Feature @Satisfies(ReqHelpOverlayRequirement), the graph walks up the refinement chain and records that the Feature also transitively contributes to REQ-DISCOVERABLE-TRACEABILITY.

The caveat: transitivity is a graph property the DSL exposes, not a rule the DSL enforces. Nothing stops you from building a Requirement tree with fifteen levels of refinement; nothing stops you from building a flat tree with no refinement at all. Both shapes are valid TypeScript and both pass the compliance gate. The chapters argue (especially Chapter 04: Twenty-Two Requirements, Part One) that two or three levels is usually right — deeper than that and the refinement graph becomes harder to read than the flat list it was meant to organise.

Cycles are forbidden. A Requirement cannot @Refines itself transitively; the compliance scanner detects cycles at registry-build time and fails the build with a pointer to the offending chain. The detection is a classic DFS colouring algorithm, not a runtime surprise.


5. How do I migrate from typed-specs?

The migration is additive, not destructive. Your typed-specs Features — NavigationFeature, SearchFeature, etc. — keep their shape; what changes is that each of them acquires a @Satisfies(...) decorator pointing at one or more newly-declared Requirement classes. The step-by-step is Chapter 01: From Typed-Specs to Typed Requirements.

Concretely, the chapter lays out a three-step migration. Step one: create a requirements/ folder parallel to the existing features/ folder, and declare a Requirement class for each distinct thing your prose was already calling a requirement. This step is where most of the work lives; it forces you to name the things the earlier package named only rhetorically. Step two: on each Feature, add a @Satisfies(...) decorator listing the Requirements it meets. This step is mechanical once step one is done. Step three: enable the compliance gate in your build; it will flag any Feature without @Satisfies as unlinked and any Requirement with no @Satisfies pointing at it as orphan.

The honest pain point: step one takes longer than you expect. In the CV monorepo migration, the ratio was roughly one day per five Features — not because adding a class is hard, but because the act of naming each Requirement forced conversations the project had been avoiding. Several Features turned out to satisfy no Requirement at all; they were Feature-shaped code that had wandered into the codebase without a reason. Those Features became deletion candidates, which was uncomfortable and useful.

The chapter also documents the non-migration: typed-specs' Priority, ACResult, and Feature types are unchanged in @frenchexdev/requirements. Imports move from ../typed-specs to @frenchexdev/requirements, but the shapes are identical. This is a deliberate design decision — see Chapter 03: The Decorator Surface for why.


6. What's the upgrade path from typed-specs to requirements?

This is the same question as #5 with a different frame — "migrate" versus "upgrade" suggests two ways of thinking about the transition. The answer for both is: additive. But if your mental model is "upgrade" (replace package A with package B), a few extra notes.

The package rename is typed-specs@frenchexdev/requirements. The scoped name is deliberate; it signals that the package is part of a monorepo family (see Chapter 16: Cross-Package Adoption — typed-fsm). If you were importing from a local typed-specs/ folder in a monolith, the upgrade involves publishing-or-vendoring @frenchexdev/requirements and repointing imports. The shapes re-export under the same names, so Feature, Priority, and ACResult keep working without code changes.

The CLI is new. typed-specs had no CLI; it shipped a compliance scanner you invoked as a Node script. @frenchexdev/requirements ships a requirements binary with subcommands — requirements scan, requirements trace, requirements compliance, and the forthcoming requirements explore. See Chapter 13: Quality Gates and Compliance for the full subcommand list. If your CI was calling node scripts/compliance.js, it now calls npx requirements compliance.

The decorators are a superset. @FeatureTest and @Verifies keep their signatures. @Satisfies and @Refines are new. @Style(...) is new. Your existing typed-specs code compiles against the new package; your new code can use the new decorators; the two styles coexist. The chapter on coexistence is Chapter 03b: Newcomer Primer — Abstract Classes, Decorators, Registries.


7. Can features have parents?

No — and this is a deliberate asymmetry with Requirements. Requirements compose via @Refines; Features do not. The design argument is in Chapter 20: Requirements Meet DDD, Revisited.

The argument is essentially: Requirements are about the world (they describe properties the system must have), and properties compose into more-specific properties — a general Requirement like "the user can browse the graph" refines into "the user can use arrow keys" and "the user can use Vim keys" without contradiction. Features are about the code (they describe implementable units of behaviour), and implementation units compose via satisfaction, not refinement — a Feature that handles arrow keys and a Feature that handles Vim keys are two Features, not one Feature that refines another.

If you find yourself wanting Feature parents, the DSL's opinion is that you have either (a) discovered a missing Requirement (the "parent" you want is really a Requirement that both Features satisfy) or (b) a God-Feature you should split. Chapter 06: Twenty-Five Features Mapped gives the splitting heuristic: if a Feature has more than seven abstract AC methods or spans more than one Requirement that is not itself part of a refinement chain, split it.

The honest limit: this is an opinionated stance. Other requirements DSLs (notably SysML — see #9) do allow feature-to-feature containment. The opinion in this DSL is that feature-to-feature composition is a source of confusion more often than a source of clarity, and that the @Satisfies many-to-many relation already carries the grouping work you were trying to use parenthood for.


8. Can I plug my own Style?

Yes. Style is an extension point, not a fixed enum. The registry machinery is in src/lib/style-registry.ts, and the canonical walkthrough of how to register a new Style is Chapter 08: Styles — A Plural Rhetoric.

A Style is a three-part contract: a name (string literal type), a renderer that turns a Requirement's typed fields into prose in that Style's idiom, and optionally a validator that rejects Requirements whose fields are structurally incompatible with the Style. The five Styles the package ships — default, industrial (SIL-flavoured), lean, agile, kanban — are reference implementations; nothing in the type system privileges them. A user who wants a ears Style (Easy Approach to Requirements Syntax) or a catala Style (French tax law idiom) declares a Style class, registers it, and decorates their Requirements with @Style('ears') or @Style('catala').

The three Style deep-dive chapters (Chapter 09: Industrial/SIL, Chapter 10: Lean, Chapter 11: EARS/Agile/Kanban) double as tutorials on how to build a Style: each one walks through the validator, the renderer, and the tests that verify the Style meets its own REQ-STYLE-FIDELITY Requirement. You can copy any of them as a starting point.

The honest limit: Style pluralism works at the rhetorical layer (how a Requirement reads), not at the ontological layer (what a Requirement is). Your custom Style cannot add new fields to the Requirement base class; it can only rearrange, filter, or re-phrase the fields that are already there. If you need a genuinely new Requirement type (say, a safety-case Requirement with a fault-tree field), that is a fork of the DSL, not a Style plugin. The boundary is drawn carefully in Chapter 08.


9. How does this relate to SysML?

SysML is a much larger and more ambitious modelling language, of which the requirements DSL is a small, opinionated subset. The full comparison is Chapter 19b: External Vocabulary — Jira, SysML, ISO 29148, BDD.

SysML has a Requirement diagram, a satisfy relation, a derive (refines) relation, a verify relation (corresponds to @Verifies here), and a trace relation. Those five relations map almost one-to-one onto this DSL's @Satisfies, @Refines, @Verifies, and implicit traceability graph. What SysML adds over this DSL is roughly: cross-cutting concerns (ports, flows, constraints as first-class), a full behavioural layer (state machines, activity diagrams, sequence diagrams), and a tool ecosystem (Cameo, MagicDraw, Capella) that treats the model as a first-class artefact separate from the code.

What this DSL adds over SysML is that the model is the code. A SysML Requirement lives in a model file, possibly XMI, usually edited in a specialised tool. A DSL Requirement is a TypeScript class, edited in whatever editor the developer already has open, compiled by the same compiler that compiles the rest of the project. The trade-off is expressivity-for-accessibility: SysML can say things the DSL cannot (quantitative safety margins, physical units, probabilistic assertions), and the DSL can be adopted without learning a new tool.

For a team that has SysML expertise, the DSL is a lightweight complement — use SysML for the system architecture, use the DSL for the software layer. For a team that does not have SysML expertise, the DSL is a bridge: the vocabulary (Requirement, Satisfies, Refines, Verifies) is SysML's vocabulary, so a developer who grows into systems engineering will find their idiom already shaped. Chapter 19 argues this explicitly.


10. What about BDD / Cucumber / Given-When-Then?

BDD and the DSL aim at similar goals (a shared language between stakeholders and developers, a check that the implementation meets the stated intent) via different means. The comparison chapter is Chapter 19b: External Vocabulary.

BDD's innovation is that scenarios are written in Gherkin (Given/When/Then), a controlled natural language that non-developers can read and edit. The scenarios are then mechanically matched to step definitions written in code. The DSL does not have Gherkin; its Requirements are written as TypeScript class properties with string literals for human-readable prose. A non-developer cannot edit a DSL Requirement without running a TypeScript compiler.

That trade-off is deliberate. BDD optimises for a world where the product owner writes scenarios and the developer writes step definitions — a world where the boundary between intent and implementation runs through the scenario file. The DSL optimises for a world where the developer is responsible for modelling intent, because the product owner has abandoned the attempt to write Gherkin (which, in practice, is most BDD adoptions after eighteen months). See Chapter 02: Why Dog-Food a Requirements DSL for the half-life argument.

The two approaches can coexist. A Gherkin scenario can map to an abstract AC method via a @Verifies annotation on the step definition; a Requirement can be the "higher-level intent" a set of Gherkin scenarios collectively satisfies. Chapter 17 sketches the wiring. The honest opinion baked into the DSL is that Given-When-Then is one Style among many; it is not the universal form of a requirement. The @Style('ears') example in the custom-Style chapter shows how to embed a Given-When-Then-adjacent idiom without buying the whole Cucumber toolchain.


11. Does dog-food slow me down?

The answer is "yes, briefly, then no." The full telemetry is in Chapter 13b: Developer Experience — TUI Wizard, Live Feedback and Chapter 19: Lessons and Anti-Patterns.

The "yes, briefly": for the first two weeks after adopting the DSL on a codebase, every new Feature takes longer to write than it would without the DSL, because you have to name its Requirement(s) and argue about which Style they belong to. The measured overhead on the CV monorepo was roughly 30–45 minutes per Feature during the adoption phase.

The "then no": once the Requirement vocabulary stabilises (typically around the third or fourth week, or when you hit twenty-ish Requirements), new Features are faster to write than they would have been without the DSL. The reason is that the Requirement graph is, by that point, carrying the design-decision load. Instead of "what should this Feature do? let me think" you get "this Feature satisfies REQ-DISCOVERABLE-TRACEABILITY and refines REQ-BROWSE-GRAPH; the ACs write themselves from the Requirement's structural fields." The DSL pays for itself somewhere around the tenth Feature in a new codebase.

The honest caveat: this accounting assumes the developer is the same person before and after. For a team, the learning curve is longer, because each new team member pays the two-week cost individually. Teams of more than four or five people often find the cost accumulating faster than the DSL can amortise it, and the requirements wizard TUI (see Chapter 13b) was built specifically to compress the onboarding window. If your team rotates members faster than every three months, the DSL is probably not worth the adoption cost.


12. What happens when I have 200+ REQs?

At 200+ Requirements the traceability graph becomes too large to hold in your head, and the tooling matters more than the decorators. The scale chapter is Chapter 14: AST Extraction and Registry with cross-references to the forthcoming requirements explore TUI.

What changes at scale: the requirements trace CLI, which at small scale prints a readable tree, becomes unhelpful — the tree is too big to fit on one terminal page. The mitigations the package ships are (a) the requirements explore TUI (Chapter 15 — not yet shipped as of 2026-04-14, but specified as FEATURE-TRACE-EXPLORER-TUI in the running-example chapter), (b) the Mermaid export, which produces a diagram that can be panned and zoomed in the browser, and (c) scoped trace queries — requirements trace --from REQ-DOG-FOOD --depth 2 which walks only a subgraph.

What does not change at scale: the compliance scanner's runtime is O(V+E) where V is the number of Requirements plus Features plus Tests and E is the number of decorator edges. On a 200-Requirement project with 300 Features and 800 Tests, the scanner runs in under two seconds. The bottleneck at that scale is human comprehension, not scanner performance.

The honest limit: the DSL has not been exercised on a codebase with more than the CV monorepo's fifty-ish Requirements. The scale claims above are extrapolations from the scanner's asymptotic behaviour and from a synthetic benchmark described in Chapter 22: Roadmap and Open Questions. A real 1000-Requirement codebase might surface problems the DSL does not yet know about. If you are that first real adopter, the author would like to hear from you.


13. Can I use this without TypeScript?

No, not really. The DSL is fundamentally TypeScript-shaped: it uses abstract classes, decorators, keyof T indexed access, class-valued decorator arguments, and module-scope registries. Every one of those shapes has a cousin in other languages, but the cousins are not drop-in. The philosophical argument is in Chapter 03: The Decorator Surface.

The closest port would be to C# — specifically, a .NET 8+ codebase with source generators, which can replicate the decorator-chain pattern using attributes plus IIncrementalGenerator. The CV monorepo's C# CMF is heading in that direction and will probably ship a C# sibling to @frenchexdev/requirements in 2026. Java with annotation processors is possible but awkward (annotation processors do not see class-valued annotation arguments the way TypeScript decorators see class references). Python is possible with metaclasses but loses the compile-time check that is the whole point of the DSL. Rust is possible with proc-macros but the ergonomics are poor.

If you are in a language without decorators or source generators, the closest you can get is a schema-file-plus-scanner pattern: declare your Requirements in a YAML or JSON file, declare your Features in code, and run a scanner that cross-references the two. You lose the compile-time guarantee (typos in Requirement references become runtime errors rather than compile errors), but you keep the traceability graph. Chapter 19b discusses this fallback.

The honest opinion: if you are in a language without a decent decorator story, you are probably better off with SysML plus a model-to-code generator than with a homemade schema-file DSL. The DSL's value proposition is the compile-time guarantee; without it, you have a documentation tool, not a modelling tool.


14. What's the runtime cost?

Zero at production runtime, small at test/build time. The full accounting is in Chapter 14: AST Extraction and Registry.

The decorators register metadata into a module-scope Map when the decorated module is first loaded. In production, you almost never load the features/ and requirements/ folders — they are development-time artefacts consumed by the compliance scanner, not by the running application. The Feature classes are abstract; you cannot instantiate them. A tree-shaking bundler (Vite, esbuild, webpack with mode:production) drops them entirely from the production bundle.

At test time, loading the registry adds a few milliseconds to test startup — the registry is populated once per test process via a single import '../requirements/index' statement. The measured cost on the CV monorepo is under 50ms for fifty Features and thirty Requirements. That cost is paid once per test run, not once per test.

At build time, the compliance scanner runs the TypeScript compiler's AST walker over the features/ and requirements/ folders to extract decorator annotations without executing the code. The AST walk is the dominant cost. On the CV monorepo, requirements compliance takes roughly 1.5 seconds, most of which is the tsc --noEmit front-end. This cost scales linearly with codebase size; at 1000 Requirements, expect 15–20 seconds.

The honest caveat: if you decide to run the compliance scanner inside your application — for example, as a startup self-check in a regulated deployment — the cost is no longer zero at runtime. Chapter 13c: When Compliance Fails — Diagnostics and Recovery discusses when this is a good idea (regulated contexts, see #15) and when it is not (most consumer apps).


15. How do I handle regulated industries (FDA, IEC 61508)?

The DSL's Style pluralism was designed partly with regulated contexts in mind. The canonical treatment is Chapter 09: Style Deep Dive — Industrial/SIL, which walks through the industrial Style and its correspondence with IEC 61508's SIL-level requirement shapes.

The short version: regulated standards (FDA 21 CFR Part 11, IEC 61508, DO-178C for avionics, IEC 62304 for medical device software) require a traceability matrix — a document that cross-references every requirement to every design element to every test. The @frenchexdev/requirements package produces exactly that matrix as a generated artefact, via requirements compliance --format=matrix. The matrix is machine-readable (JSON) and human-readable (Markdown and HTML variants).

What the DSL does not do for regulated contexts: it does not produce the surrounding quality-management-system documentation (design history file, hazard analysis, risk management file). It does not sign artefacts cryptographically (you need an external signing step). It does not enforce reviewer approval workflows (those live in your ALM tool — Polarion, DOORS, Helix). The DSL produces the artefact; the regulatory process wraps it.

The honest caveat: no regulated project has yet shipped with @frenchexdev/requirements in its pedigree. The industrial Style is modelled after SIL-requirement shapes, and the compliance scanner produces the matrix a notified body would expect to see, but the DSL has not been audited against a specific standard by a specific body. If you are pursuing regulatory approval, treat the DSL as a development-time aid and plan on wrapping it in whatever your standard's audit trail requires. Chapter 13: Quality Gates and Compliance is explicit about this.


16. What if my team won't dog-food?

Then don't dog-food. The REQ-DOG-FOOD rule (zero describe/it, the package validates itself with itself) is a rule the requirements package imposes on its own source tree, not a rule the package imposes on consumers. The discussion is in Chapter 02: Why Dog-Food a Requirements DSL.

A consumer can use @Satisfies, @Refines, @Verifies, @FeatureTest in a project that still has plenty of describe/it. The decorators and the runner are orthogonal. The compliance scanner will happily accept a codebase where half the tests are describe/it-shaped and half are decorator-shaped, as long as the decorator-shaped ones cover the ACs declared on the Features. See Chapter 07: Fifty-Four Tests, Only Decorators for the gradient.

The argument for pushing dog-food anyway: the describe/it shape encourages tests whose names drift from the Features they are meant to cover. A test labelled describe('navigation', () => { it('handles arrow keys', ...) }) has no structural connection to NavigationFeature.handlesArrowKeys — the compiler cannot tell if the test has drifted to cover something else. The @Verifies('NavigationFeature.handlesArrowKeys') decorator closes that gap, and once you have the decorator, the describe/it wrapper is redundant. Teams that adopt the decorators usually drop describe/it within a quarter, not because anyone forced them to, but because the wrapper became visibly redundant.

The argument against: some developers find describe/it a useful scaffolding for their own thought, even if the output is redundant from the DSL's point of view. Forcing them to drop the scaffolding buys you cleanliness at the cost of cognitive friction. The DSL's opinion is that this is a team-local decision, not a correctness question. The package itself dog-foods because the package is small enough that the cognitive friction of dog-food is smaller than the cognitive friction of maintaining two parallel test shapes.


17. Can I gradually adopt this?

Yes. Gradual adoption is the default, not the exception. The canonical gradient chapter is Chapter 22b: A Day in the Life of a New Requirement, which follows a single Requirement from sketch to merge in a codebase that is only partially DSL-adopted.

The gradient has four levels. Level one: add the @FeatureTest/@Verifies decorators to your existing tests without adding any Requirements. This gets you Feature-to-test traceability without the Requirement stratum. Level two: add @Satisfies(...) on a few high-value Features pointing at a small set of Requirements. This gets you a partial Requirement graph with the rest of the codebase treated as unlinked. Level three: enable the compliance gate at "warning" severity; unlinked Features emit warnings but do not fail the build. Level four: promote the gate to "error"; unlinked Features fail the build.

Most teams sit at level two or three indefinitely. Level four is the terminal state the requirements package itself operates at — every Feature has a @Satisfies, every Requirement has at least one Feature satisfying it, and the compliance gate is a hard failure. Getting there takes months on a real codebase.

The honest note: level three is the load-bearing level, not level four. Level three is where the Requirement vocabulary becomes visible enough to shape design decisions without being punitive about historical gaps. Chapter 19: Lessons and Anti-Patterns argues that teams which try to jump to level four too fast often roll back to level one; teams that sit at level three patiently find themselves at level four six months later without having planned the transition.


18. Does this work in a monorepo?

Yes — and the package is itself extracted from a monorepo. The canonical monorepo chapter is Chapter 17: Cross-Package Adoption — Monorepo Sweep, which walks through how the CV monorepo's fifteen packages share a single Requirement registry.

The key design decision: each package has its own requirements/ folder with its own Requirement classes, but the compliance scanner walks all packages and builds a unified registry. A Requirement declared in @frenchexdev/typed-fsm can be satisfied by a Feature in @frenchexdev/requirements (and vice versa), because both packages export their Requirement classes and import each other's. The registry treats cross-package @Satisfies identically to same-package @Satisfies.

The pain point the monorepo sweep chapter documents: circular imports. If package A's Features satisfy package B's Requirements, and package B's Features satisfy package A's Requirements, you have a bidirectional dependency that TypeScript will refuse to compile. The fix is to extract the shared Requirements into a third package (the "shared kernel" pattern from DDD) that both A and B depend on. Chapter 20: Requirements Meet DDD, Revisited gives the full pattern.

The honest limit: the monorepo support assumes a single tsconfig.json root that can see all packages' source. If your monorepo uses composite builds with stricter isolation, the compliance scanner needs to be invoked per package and the results merged. The merging is not yet automated; it is a 2026-Q3 roadmap item (see Chapter 22).


19. How do I test UI features?

Same way you test non-UI features, with the caveat that UI tests often live in a Playwright harness that calls into a browser. The Playwright pattern is covered in answer #2 above and in Chapter 17: Cross-Package Adoption — Monorepo Sweep.

The decorator surface does not care whether the test runs in Node or in a browser. What matters is that the test class has @FeatureTest(SomeUIFeature) and each test method has @Verifies('SomeUIFeature.someAC'). The test body can use Playwright's page.click, page.waitForSelector, expect(page.getByRole(...)), etc. The compliance scanner matches the decorator annotations against the Feature's abstract methods; it does not look at the test body.

The pattern the CV monorepo uses: a *.e2e.test.ts file per UI Feature, each file containing one @FeatureTest-decorated class with one test method per AC. A tour-widget Feature with four ACs has a tour-widget.e2e.test.ts file with four @Verifies methods. The Playwright runner sees four top-level test cases; the compliance scanner sees four AC covers. Both are happy.

The honest caveat: visual-regression tests (screenshot diffs, a11y scans, perf budgets) are harder to fit into the AC shape because they are not naturally pass/fail predicates — they return a numeric delta or a score. The pattern the CV monorepo uses is to declare the AC as a boolean ("the page meets WCAG 2.1 AA") and let the test body do the numeric work, failing if the score is below threshold. This is ugly but works. Chapter 22 discusses proper numeric ACs as a roadmap item.


20. Can I export to Jira / Linear?

Not yet directly, and the roadmap item is deliberately low-priority. The exchange-format discussion is in Chapter 22: Roadmap and Open Questions.

What the package does ship: a JSON export from requirements compliance --format=json that contains every Requirement, every Feature, every AC, every test, and every @Satisfies/@Refines/@Verifies edge. The JSON is stable across versions (see the schema in Chapter 14: AST Extraction and Registry). A small script can translate the JSON into Jira's REST API shape or Linear's GraphQL shape; the translation is about 100 lines in each case.

What the package does not ship: the translation itself. The reason is that Jira and Linear both have opinions about the hierarchy (Epic → Story → Task; Project → Issue) that do not map cleanly onto the Requirement → Feature → AC hierarchy. The Epic-is-Requirement / Story-is-Feature / Task-is-AC mapping works for some teams; the Epic-is-Refinement-root / Story-is-Requirement / Task-is-Feature mapping works for others. A canonical translation would have to pick one, and the package's opinion is that this is a team-local decision not worth encoding in the core.

The honest option for teams that want Jira sync: write the 100-line translator once, commit it to your project, and run it as a CI step that updates Jira from the JSON export. The reverse direction (Jira as source of truth, DSL generated from Jira) is harder because Jira does not enforce the structural rules (@Refines acyclicity, @Satisfies many-to-many) that the DSL relies on. That direction is not on the roadmap.


21. What about multiple teams sharing REQs?

Multi-team adoption is the hard case. The DSL supports it structurally (monorepo answer #18) but not socially (the Requirement vocabulary has to be agreed on). The discussion is in Chapter 20: Requirements Meet DDD, Revisited and Chapter 21: Requirements Meet the Human Side, Revisited.

Structurally: each team owns a package, each package has a requirements/ folder, Requirements are exported and imported across package boundaries, the compliance scanner walks the union. This is the same pattern as answer #18 applied to team boundaries instead of module boundaries.

Socially: the hard part is that different teams speak different dialects of the product vocabulary. Team A's REQ-FAST-CHECKOUT is Team B's REQ-LOW-LATENCY-PAYMENT. The DSL does not merge those Requirements — it takes them literally as two distinct classes — and the duplication shows up in the traceability graph as two nodes where there should be one. The fix is coordination, not tooling: teams have to agree on the Requirement vocabulary the same way they have to agree on API contracts. The DSL makes the coordination visible (the duplication shows up in the graph) but does not automate it.

The bounded-context pattern the chapter argues for: each team defines an internal Requirement graph that uses the team's local vocabulary, plus a small shared Requirement kernel that every team imports. Cross-team @Satisfies only points at shared kernel Requirements. Internal Requirements are opaque to other teams. This is the same bounded-context pattern DDD uses for entities, applied to Requirements. It scales to roughly five teams; above that, the shared kernel itself fragments and needs its own governance.

The honest limit: the CV monorepo is a one-person project. Multi-team Requirements are a theoretical argument, not a tested one. If you are running a multi-team DSL adoption, the author would like to hear what breaks.


22. Is this open source?

Yes, MIT-licensed, published under the @frenchexdev/ scope on npm and mirrored on GitHub. The source tree lives in packages/requirements/ in the CV monorepo. Contribution guidelines are in the package README and summarised in Chapter 22: Roadmap and Open Questions.

The package is open to issues, PRs, and forks. The contribution bar is that every PR must pass the dog-food gate — your contribution must be modelled as a Requirement if it changes behaviour, satisfied by a Feature, and verified by a test. PRs that add code without adding the corresponding Requirement-Feature-Test triple will be asked to do so before merge. This is the same rule the package imposes on itself.

The roadmap is public and lives in Chapter 22. Issues that are in scope include: better error messages, the requirements explore TUI, the multi-team coordination patterns, the regulated-industry audit trail, the Jira/Linear bridge. Issues that are out of scope include: alternative language ports (if you want a Python version, fork it), graphical editors (the DSL is text-first on principle), and proprietary integrations with specific ALM tools.

The honest note: the package is new. The version is 0.x. Semantic versioning applies but breaking changes should be expected until 1.0, which is targeted for late 2026 once the remaining roadmap items have shipped and stabilised.


23. How does this compare to Catala?

Catala (the domain-specific language for legal rules, developed at Inria and used notably for French tax code) is a cousin, not a sibling. The comparison is drawn in Chapter 19b: External Vocabulary and more speculatively in the author's Appareil et compilateur project notes.

What Catala and @frenchexdev/requirements share: a commitment to making a specification executable rather than descriptive. Catala compiles legal rules into verified code; the DSL compiles Requirements into type-checked traceability edges. Both refuse the natural-language-plus-mechanical-translation pattern where the spec lives in prose and the code lives somewhere else.

What they differ on: Catala's domain is formal legal text, which has a specific shape (scope, definition, condition, exception) that Catala's syntax mirrors. The DSL's domain is software requirements, which has a much looser shape; the Style pluralism exists precisely because no single shape fits all. Catala is a standalone compiler with its own IDE support; the DSL piggybacks on TypeScript's compiler and ecosystem. Catala targets lawyers-plus-developers pair-programming; the DSL targets developers who want to model their own intent without requiring a domain expert in the room.

The author's working hypothesis is that Catala and the DSL are two points on a larger spectrum of compiling-specification languages — the "Appareil et compilateur" essay argues that this spectrum is wider than either project realises, and that the DSL would benefit from adopting more of Catala's formal-verification machinery as it matures. But that is a 2027 conversation, not a 2026 one.


24. Can AI write my REQs?

Partially, with human review. The AI-assist chapter is Chapter 13b: Developer Experience — TUI Wizard, Live Feedback and the deeper stance is in Chapter 21: Requirements Meet the Human Side, Revisited.

What AI does well: drafting the initial Requirement class from a prose description. If you say "a user browsing the graph must be able to discover relations without prior knowledge of the vocabulary," an LLM can produce a ReqDiscoverableTraceabilityRequirement class with the right fields populated, in the right Style, with a plausible @Refines parent. That draft is correct maybe 70% of the time and needs editing the other 30%. The requirements wizard TUI bakes this in as an opt-in step.

What AI does poorly: deciding which Requirements your project needs. The act of naming a Requirement is an act of commitment to a specific modelling decision; an LLM without the full project context will produce plausible-sounding Requirements that are not load-bearing. The ratio the CV monorepo observed was roughly one in three AI-drafted Requirements survived review; the other two were either too vague or duplicated an existing Requirement. This is consistent with the broader pattern that LLMs are good at filling in a specification shape and poor at choosing the shape.

The author's stance, written out fully in Chapter 21: AI is a drafting tool, not an authority. A Requirement that the author did not understand before the AI drafted it is a Requirement the author does not yet own, and pushing an unowned Requirement into the codebase is a failure mode the DSL is specifically designed to catch — because an unowned Requirement tends to be named-but-not-modelled, which is the exact gap this entire series exists to close. The LLM has to feed the author's understanding, not replace it.


25. What if my REQ changes?

You change the Requirement class, run the compliance scanner, and fix whatever breaks. The change-management chapter is Chapter 13c: When Compliance Fails — Diagnostics and Recovery.

What changes well: adding fields, rewording prose, changing Style, adding a @Refines edge. These changes are backward-compatible from the compiler's point of view; existing @Satisfies annotations keep working because they reference the class by identity, not by content.

What changes badly: renaming a Requirement class, splitting one Requirement into two, merging two Requirements into one, deleting a Requirement. Renaming is a find-and-replace exercise that the TypeScript language server handles well. Splitting requires you to walk every @Satisfies(RenamedClass) and decide which of the two new classes each Feature now satisfies; the compliance scanner surfaces the Features that need the decision. Merging is the reverse. Deleting requires you to either reassign the Features to another Requirement or accept that they become unlinked — the compliance scanner flags the unlinked Features at the next build.

The pattern the CV monorepo uses: when a Requirement changes meaningfully (beyond a reword), bump its identifier. REQ-FOO becomes REQ-FOO-V2; the old REQ-FOO is left in place with a deprecation comment for one release cycle and then removed. This gives downstream consumers a release to migrate. It also produces a visible audit trail in git history — the diff shows exactly what changed and when.

The honest caveat: this manual migration pattern is adequate for a one-person project. On a large team it would benefit from more tooling — automatic codemod generation, migration-guide boilerplate, deprecation-warning integration with the CLI. Those are roadmap items in Chapter 22. Until they ship, plan on each Requirement rename or split costing roughly half an hour of coordinated team time.


⬇ Download