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

What it reifies

@frenchexdev/requirements-styles is the cross-cutting package that ships concrete instances of the RequirementStyle abstraction the kernel defines. The kernel ships only the shape — the abstract RequirementStyle, StyleVocabulary, StyleValidators, StyleTemplates, RequirementReporter types. This package ships five concrete styles, each typed-against and validated by that shape, so that a project can pick one and inherit a complete vocabulary plus validation rules plus templating plus reporting.

The package.json description: "Built-in RequirementStyle presets (Default/Industrial/Lean/Agile/Kanban) + StyleRegistry for @frenchexdev/requirements." The five styles are pitched in docs/pitches/ inside the package; the maintainer's own CLAUDE.md summarises each:

  • DefaultStyle — ISO/IEC/IEEE 29148 + Volere + EARS. The general-purpose default, applicable to most software projects.
  • IndustrialStyle — IEC 61508/61511/62443/61131-3/61850, ISA-88/95/18.2, 21 CFR Part 11, GAMP 5. A thirteen-state heavy-gate lifecycle, SIL 1–4 risk classification, designed for regulated industrial software.
  • LeanStyle — Toyota / A3 / PDCA / Gemba / VSM / Kaizen. Lightweight, continuous-improvement-oriented vocabulary; minimal status workflow.
  • AgileStyle — Scrum / XP / SAFe / BDD. Three-state workflow, INVEST validator on every Requirement, fit criteria slanted toward acceptance scenarios.
  • KanbanStyle — Anderson's Kanban Method. Classes of Service (expedite, fixed-date, standard, intangible), flow-based status workflow, WIP-limit-aware reporting.

Each style is a single TypeScript file in src/, each exports one const of type RequirementStyle narrowed to a project-specific union. The as const discipline (feedback_as_const_satisfies_for_union_runtime_lists.md) is mandatory: the union of valid status, kind, and verificationMethod values is computed at the type level from the as const definition, so a downstream Requirement's status: 'Approved' is type-checked against the specific style's status workflow, not against a permissive string.

The public surface

The exports map is one per preset plus the registry:

"exports": {
  ".":           "./dist/index.js",
  "./default":   "./dist/default.js",
  "./industrial":"./dist/industrial.js",
  "./lean":      "./dist/lean.js",
  "./agile":     "./dist/agile.js",
  "./kanban":    "./dist/kanban.js",
  "./registry":  "./dist/registry.js"
}

The default . export re-exports all five. Consumers who care about install size pick the subpath: import { INDUSTRIAL_STYLE } from '@frenchexdev/requirements-styles/industrial' pulls in only the Industrial preset and skips the other four.

The registry exposes a discoverable list:

import type { RequirementStyle } from '@frenchexdev/requirements-shared-kernel';

export interface StyleRegistry {
  byName(name: string): RequirementStyle | undefined;
  list(): readonly { name: string; description: string }[];
}

export const BUILT_IN_STYLES: StyleRegistry;
export const DEFAULT_STYLE_REGISTRY: StyleRegistry;
export function createStyleRegistry(
  overrides?: Record<string, RequirementStyle>,
): StyleRegistry;

createStyleRegistry accepts overrides. A project that wants its own custom style — say, a MedicalDeviceStyle that combines Industrial's IEC 62304 with FDA Class II controls — registers it once and uses it everywhere via --style medical-device on the CLI.

Where it sits

Cross-cutting. Not in the tier stack — depends only on @frenchexdev/requirements-core (transitional kernel), so on the shared kernel transitively. No Tier-1, no Tier-2, no Tier-3 deps. Optional dep of the CLI (a user who only wants the default style does not install this package at all; the kernel ships an embedded minimum-viable default).

Three things the package must not do:

  • Modify the kernel. Styles instantiate the kernel's abstract types; they do not extend them. A style that needed a new RequirementStyle field would force a kernel breaking change, which is wrong.
  • Bake in project-specific defaults. Every style is intended to be usable by multiple projects. Project-specific overrides happen via createStyleRegistry in the project's repo.
  • Ship a runtime. A style is data plus pure validators. No I/O, no side effects, no codegen.

A concrete call-site

The CLI consumes the registry to resolve --style:

import { BUILT_IN_STYLES } from '@frenchexdev/requirements-styles/registry';
import { orchestrateSpecWizard } from '@frenchexdev/requirements-wizards';
import { clackPromptAdapter } from './clack-prompt-adapter';

const styleName = options.style ?? 'default';
const style = BUILT_IN_STYLES.byName(styleName);
if (!style) {
  console.error(`Unknown style: ${styleName}`);
  console.error('Known:', BUILT_IN_STYLES.list().map(s => s.name).join(', '));
  process.exit(1);
}

const result = await orchestrateSpecWizard(clackPromptAdapter, { style });

A Requirement authored under the Industrial style cannot use a status: 'Approved' value because Industrial's status workflow does not include that state — the as const narrowing turns the typo into a compile error if the spec is in TypeScript, and into an ajv schema violation if it is in JSON. The wizard's prompts narrow to the style's vocabulary too: the EARS pattern selector for Default offers five patterns plus natural, while for Industrial it offers only the patterns the IEC standards endorse.

Why it is its own package

Three arguments.

First, the styles are an extension point. The Open/Closed Principle, applied to the kernel: new styles must arrive without modifying the kernel. Packaging the presets separately makes the extension surface physical — anyone wanting to add a sixth preset (or a custom in-house style) registers it via createStyleRegistry without touching @frenchexdev/requirements-shared-kernel. The five built-ins are reference implementations a future custom style mimics. The maintainer's discipline (feedback_dsl_open_closed_via_generators.md) — "a DSL is parameterised by an ontology produced by a Source Generator, never blocked on a pre-built library" — applies here: the styles ship as TypeScript data; new ontologies parameterise the same kernel.

Second, the package's optional-dep status protects the kernel's narrowness. If the kernel shipped the five presets, every consumer would carry five styles' worth of vocabulary tables, validator functions, and template strings — even if they only ever use the Default. As optionalDependencies of the CLI, the styles install only when needed. A library consumer that imports getRequirementRegistry from the kernel does not transitively install any style; a CLI user installs the CLI's optional deps by default but can --omit=optional to skip them on slim builds.

Third, the five styles are differentiable enough to deserve sibling-file separation. Default is roughly 80 lines of TypeScript; Industrial is closer to 250 — the IEC standards alone require thirteen status states, eight verification methods, and the SIL 1–4 risk matrix. Lean is roughly 60 lines; Kanban is 120; Agile is 100. Bundling them into a single styles.ts file inside the kernel would have made the file unreviewable. Each in its own src/<name>.ts keeps the file size honest and lets the per-style coverage threshold be applied where the surface actually changes.

The package is also the natural home for future style additions. A planned MedicalDeviceStyle (FDA, ISO 13485, IEC 62304) will arrive here as a sixth file, registered alongside the existing five via createStyleRegistry. The CLI consumes it through the same --style medical-device switch; no other package is affected.

The next page covers the second cross-cutting package — requirements-styles-demo — which exists to show, at runtime, how each style behaves through a scenario-driven CLI.

⬇ Download