What it reifies
@frenchexdev/requirements-shared-kernel is the only Tier-0 package every other package in the family must depend on. Its job is to define — and only to define — the contracts that the entire ecosystem agrees to share: the base classes Feature and Requirement<S>, the six decorators that stamp metadata onto them, the RequirementStyle abstraction that parameterises a project's vocabulary, the branded ID primitives, and the FileSystem port. It is zero-dependency by construction (peer-dep on typescript >=5.4, no runtime dep, no dependencies block in package.json). It is the Shared Kernel in the DDD sense: the smallest, most tightly-controlled subset of the domain everyone shares.
The public surface
The full src/index.ts is compact enough to read end-to-end. Three groups:
// ── Base types ──
export { Feature, Requirement, Priority, TestLevel, type ACResult } from './base';
export type {
RequirementStatement,
RequirementRationale,
RequirementEvidence,
RequirementFitCriterion,
RequirementProvenance,
RequirementRisk,
TracedToLink,
RequirementHistoryEntry,
} from './base';
// ── Decorators ──
export {
FeatureTest, Verifies, Expects, Exclude, TypeCheckOnly, Satisfies, Refines,
getRequirementRegistry, getExcludedMethods, getSatisfactionLinks, getRefinementLinks,
type RequirementRef, type FeatureTestOptions,
type SatisfactionLink, type RefinementLink,
} from './decorators';
// ── Style system — abstract only, no presets ──
export type {
RequirementStyle, StyleVocabulary, StyleValidators, StyleTemplates,
RequirementReporter, RequirementTemplate, TemplateSkeleton,
StatusWorkflow, RiskTaxonomy, RiskMatrix,
SourceKindSchema, SourceSlot, StatementPatternSchema, StatementSlot,
ValidationResult, ValidationError,
KindsOf, StatusesOf, RiskLevelsOf, VerificationMethodsOf,
RationaleKindsOf, SourceKindsOf, StatementPatternsOf,
} from './style';
// ── Pure ID → name transformations ──
export * from './id-naming';// ── Base types ──
export { Feature, Requirement, Priority, TestLevel, type ACResult } from './base';
export type {
RequirementStatement,
RequirementRationale,
RequirementEvidence,
RequirementFitCriterion,
RequirementProvenance,
RequirementRisk,
TracedToLink,
RequirementHistoryEntry,
} from './base';
// ── Decorators ──
export {
FeatureTest, Verifies, Expects, Exclude, TypeCheckOnly, Satisfies, Refines,
getRequirementRegistry, getExcludedMethods, getSatisfactionLinks, getRefinementLinks,
type RequirementRef, type FeatureTestOptions,
type SatisfactionLink, type RefinementLink,
} from './decorators';
// ── Style system — abstract only, no presets ──
export type {
RequirementStyle, StyleVocabulary, StyleValidators, StyleTemplates,
RequirementReporter, RequirementTemplate, TemplateSkeleton,
StatusWorkflow, RiskTaxonomy, RiskMatrix,
SourceKindSchema, SourceSlot, StatementPatternSchema, StatementSlot,
ValidationResult, ValidationError,
KindsOf, StatusesOf, RiskLevelsOf, VerificationMethodsOf,
RationaleKindsOf, SourceKindsOf, StatementPatternsOf,
} from './style';
// ── Pure ID → name transformations ──
export * from './id-naming';The shape is deliberate: every named export is either a type, a class, an enum, a decorator function, or a registry accessor. There is no business logic, no codegen, no I/O. Nothing here imports from anywhere except ./base, ./decorators, ./style, and ./id-naming — all sibling files inside the same package.
Feature and Requirement<S> are the abstract base classes. Both declare only abstract readonly fields plus, in Requirement's case, a generic parameter S extends RequirementStyle that lets a project narrow kind, status, risk levels, and statement patterns at compile time. The signature is worth showing in isolation:
export abstract class Requirement<S extends RequirementStyle = RequirementStyle> {
abstract readonly id: string;
abstract readonly title: string;
abstract readonly priority: Priority;
abstract readonly status: StatusesOf<S>;
abstract readonly kind: KindsOf<S>;
abstract readonly statement: RequirementStatement;
abstract readonly rationale: RequirementRationale;
abstract readonly fitCriteria: readonly RequirementFitCriterion[];
abstract readonly verificationMethod: VerificationMethodsOf<S>;
abstract readonly source: RequirementProvenance;
abstract readonly risk: RequirementRisk;
readonly tracedTo?: readonly TracedToLink[];
readonly history?: readonly RequirementHistoryEntry[];
}export abstract class Requirement<S extends RequirementStyle = RequirementStyle> {
abstract readonly id: string;
abstract readonly title: string;
abstract readonly priority: Priority;
abstract readonly status: StatusesOf<S>;
abstract readonly kind: KindsOf<S>;
abstract readonly statement: RequirementStatement;
abstract readonly rationale: RequirementRationale;
abstract readonly fitCriteria: readonly RequirementFitCriterion[];
abstract readonly verificationMethod: VerificationMethodsOf<S>;
abstract readonly source: RequirementProvenance;
abstract readonly risk: RequirementRisk;
readonly tracedTo?: readonly TracedToLink[];
readonly history?: readonly RequirementHistoryEntry[];
}StatusesOf<S>, KindsOf<S>, and VerificationMethodsOf<S> are mapped-type extractors. When a project supplies its Style as a const (e.g. the Industrial preset), the abstract status field is narrowed at the call-site to the literal union of that style's statusWorkflow.states. The narrowing is compile-time only — no runtime cost — and it eliminates a whole class of typos that prose specs cannot catch.
Where it sits
Tier 0. Zero runtime dependencies. Every other package in @frenchexdev/requirements-* depends on it. Three things the package must not do:
- It must not ship concrete
RequirementorFeaturesubclasses. Those live inrequirements-requirements(Part 05). - It must not ship
RequirementStylepresets. Default, Industrial, Lean, Agile, Kanban live inrequirements-styles(Part 17). The kernel ships only the abstract shape. - It must not perform I/O.
FileSystemis a port — an interface — implemented by adapters in higher tiers.
These three rules are what keep the shared kernel narrow. The moment the kernel started shipping presets, every consumer would pull in five RequirementStyle implementations they did not ask for; the moment it shipped concrete req-*.ts files, the dog-fooding loop would collapse into the kernel itself; the moment it performed I/O, the zero-dep claim would die.
A concrete call-site
Every Tier-1+ package consumes the kernel the same way. Here is the opening of requirements-requirements/src/base.ts, the Feature declaration for the kernel's own base.ts file:
import { Feature, Priority, Satisfies, type ACResult } from '@frenchexdev/requirements-shared-kernel';
import { ReqDslCompleteRequirement } from './req-dsl-complete';
import { ReqRefactorSafeRequirement } from './req-refactor-safe';
@Satisfies(
ReqDslCompleteRequirement,
ReqRefactorSafeRequirement,
)
export abstract class BaseTypesFeature extends Feature {
readonly id = 'BASE-TYPES';
readonly title = 'Requirements DSL — base types (Priority, TestLevel, Feature)';
readonly priority = Priority.High;
abstract priorityEnumExposesAllLevels(): ACResult;
abstract testLevelEnumExposesAllLevels(): ACResult;
abstract featureClassIsAbstract(): ACResult;
}import { Feature, Priority, Satisfies, type ACResult } from '@frenchexdev/requirements-shared-kernel';
import { ReqDslCompleteRequirement } from './req-dsl-complete';
import { ReqRefactorSafeRequirement } from './req-refactor-safe';
@Satisfies(
ReqDslCompleteRequirement,
ReqRefactorSafeRequirement,
)
export abstract class BaseTypesFeature extends Feature {
readonly id = 'BASE-TYPES';
readonly title = 'Requirements DSL — base types (Priority, TestLevel, Feature)';
readonly priority = Priority.High;
abstract priorityEnumExposesAllLevels(): ACResult;
abstract testLevelEnumExposesAllLevels(): ACResult;
abstract featureClassIsAbstract(): ACResult;
}Four kernel imports — Feature, Priority, Satisfies, ACResult — are all this Feature declaration needs. The @Satisfies(...) decorator stamps a bidirectional link in the SatisfactionLinks registry the kernel maintains: the Feature now knows which Requirements it helps meet, and the Requirements know which Features realise them. The compliance reporter (Tier 1, Part 07) walks both registries at scan time to produce the traceability matrix.
The abstract methods priorityEnumExposesAllLevels, testLevelEnumExposesAllLevels, and featureClassIsAbstract are the Acceptance Criteria. Each becomes a node in the trace graph, each is bound to one or more test names via @FeatureTest/@Verifies in the test file, and each is the target of a coverage check.
Why it is its own package
Two reasons, both forced by the SOLID/DRY analysis the mega-split made explicit.
The first is dependency inversion at the workspace level. Every other package in the family imports from @frenchexdev/requirements-shared-kernel directly; nothing imports from requirements-lib for the base contracts. That choice means the Tier-1 analyzers, the Tier-2 use cases, and the Tier-3 CLI all share a single source of truth for what a Feature is, what a Requirement is, what a decorator does. If two packages disagreed about the shape of RequirementStatement — as the pre-split state of requirements/src/base.ts did, where the CLI shipped its own duplicate base class — the trace graph and the compliance reporter would silently produce different results depending on which package's import resolved first. Sharing the kernel makes that bug impossible.
The second is the zero-dependency claim. A package consumed by sixteen others sets the dependency floor for the whole family. If the kernel pulled in ts-morph, every scanner consumer would too; if it pulled in picocolors, every CLI-less consumer (an LSP, an IDE extension, a CI plugin) would carry an unused colours library. Keeping the kernel at zero runtime deps means the smallest possible consumer surface, the fastest possible install, and the cleanest possible attack-surface analysis.
The package is also slated for the workspace's largest mechanical change. Phase 5 of the roadmap renames the older sibling @frenchexdev/requirements-core to @frenchexdev/requirements-sharedkernel (or merges its remaining transitional cores into this package); the codemod touches roughly 1,263 files across the workspace. The rename is mechanical, but the breadth is what makes it Phase 5 rather than Phase 1 — it waits for the rest of the family to settle so that the codemod runs against a stable surface.
The next page covers the second Tier-0 package — requirements-requirements — the vocabulary that turns the kernel's abstract contracts into the concrete twenty-two-Requirement specification of the ecosystem itself.