What it reifies
@frenchexdev/requirements is the only Tier-3 package in the family — the inbound adapter. Its job is to translate command-line input into Tier-2 use-case invocations and render the result. The bin entry point is requirements; the package.json declares it explicitly:
"bin": {
"requirements": "./bin/requirements-entry.mjs"
}"bin": {
"requirements": "./bin/requirements-entry.mjs"
}Subcommands the CLI today exposes (from the package's own CLAUDE.md):
npx requirements compliance [--strict]
npx requirements trace gaps | matrix <feature> | chain <id> <ac>
npx requirements scaffold test|e2e <id>
npx requirements feature new | sync <spec>
npx requirements requirement new | sync <spec> | list | show <REQ-ID> | orphan
npx requirements schema register [--print] [--force]npx requirements compliance [--strict]
npx requirements trace gaps | matrix <feature> | chain <id> <ac>
npx requirements scaffold test|e2e <id>
npx requirements feature new | sync <spec>
npx requirements requirement new | sync <spec> | list | show <REQ-ID> | orphan
npx requirements schema register [--print] [--force]Each subcommand is a thin commander wrapper that loads the registries, calls the appropriate Tier-2 use case, renders the result with picocolors, and sets the exit code. There is no domain logic in the CLI; the domain logic lives in the Tier-1 analyzers and the Tier-2 use cases.
The public surface
The package exposes a bin plus four subpath exports:
"exports": {
".": "./dist/index.js",
"./analysis":"./dist/analysis/index.js",
"./cli": "./dist/cli/index.js",
"./ports": "./dist/ports/index.js"
}"exports": {
".": "./dist/index.js",
"./analysis":"./dist/analysis/index.js",
"./cli": "./dist/cli/index.js",
"./ports": "./dist/ports/index.js"
}The . export is the public types — Feature, Requirement<S>, the decorators, the style — re-exported so a downstream consumer can import { Feature } from '@frenchexdev/requirements' and get the canonical shapes. The ./analysis export is a transitional re-export of the analyzer surface (scanTestBindings, generateReport, crossReference) — Phase 4 of the roadmap will rewire consumers of ./analysis to import directly from the Tier-1 packages, after which this subpath disappears. The ./cli export is the prompt-shell harness an embedder uses to run a sub-CLI in another process. The ./ports export is a re-export of the FileSystem port for callers that want to construct one without touching the shared kernel directly.
The package depends on every Tier-1 and Tier-2 package plus the runtime libraries it needs to be a shell:
"dependencies": {
"@frenchexdev/requirements-core": "workspace:*",
"@frenchexdev/requirements-compliance": "workspace:*",
"@frenchexdev/requirements-trace": "workspace:*",
"@frenchexdev/requirements-spec-io": "workspace:*",
"@frenchexdev/requirements-scaffolders": "workspace:*",
"@frenchexdev/requirements-sync": "workspace:*",
"@frenchexdev/requirements-wizards": "workspace:*",
"@frenchexdev/requirements-versioning": "workspace:*",
"@frenchexdev/requirements-lib": "workspace:*",
"commander": "catalog:",
"@clack/prompts": "^0.9.0",
"diff": "^7.0.0",
"picocolors": "^1.1.1"
}"dependencies": {
"@frenchexdev/requirements-core": "workspace:*",
"@frenchexdev/requirements-compliance": "workspace:*",
"@frenchexdev/requirements-trace": "workspace:*",
"@frenchexdev/requirements-spec-io": "workspace:*",
"@frenchexdev/requirements-scaffolders": "workspace:*",
"@frenchexdev/requirements-sync": "workspace:*",
"@frenchexdev/requirements-wizards": "workspace:*",
"@frenchexdev/requirements-versioning": "workspace:*",
"@frenchexdev/requirements-lib": "workspace:*",
"commander": "catalog:",
"@clack/prompts": "^0.9.0",
"diff": "^7.0.0",
"picocolors": "^1.1.1"
}The presence of @frenchexdev/requirements-lib in this list is the transitional debt: today the CLI still imports from the old barrel for the parts of the analyzer surface that have not yet been rewired (@frenchexdev/requirements-lib/analysis, ./explore-core, etc.). Phase 4 removes that line.
The styles packages are listed as optionalDependencies:
"optionalDependencies": {
"@frenchexdev/requirements-styles": "workspace:*",
"@frenchexdev/requirements-styles-demo": "workspace:*"
}"optionalDependencies": {
"@frenchexdev/requirements-styles": "workspace:*",
"@frenchexdev/requirements-styles-demo": "workspace:*"
}A user who only ever uses the default style and never runs the demo player skips both transitively. The CLI loads the style at runtime by name (--style industrial, default if absent) and degrades gracefully if the requested style's optional dep is missing.
Where it sits
Tier 3. The only Tier-3 package. Depends on every Tier-1 and Tier-2 package, plus commander, @clack/prompts, diff, and picocolors. The full quality gate from the package's own CLAUDE.md: "100% lines/branches/functions/statements; 778 tests; property + fuzzy via fast-check (16 invariants, 200 runs). Self-audited — npx requirements compliance --strict = PASS. Zero describe/it permitted (REQ-DOG-FOOD); all tests use @FeatureTest/@Verifies/@Satisfies/@Refines."
Three things the CLI must not do:
- Own domain types. Today it carries duplicates —
packages/requirements/src/base.tsships its ownRequirement<S>subclass;packages/requirements/requirements/requirements/req-discoverable-traceability.tsships its own copy of the Requirement that already lives inrequirements-requirements. Phase 4 of the roadmap deletes both. Until then, this is the package's biggest technical debt. - Perform analysis. Subcommands wire pre-existing Tier-1 functions. A new
requirements coverage-trendsubcommand goes into Tier 1; the CLI gets a one-screen wrapper. - Skip its own dog-fooding. The CLI's own Features satisfy at least one Requirement via
@Satisfies; its compliance run gates publication.
A concrete call-site
The CLI is the only call-site at this level — every other consumer is a peer of the CLI, not a caller. Here is the shape of one subcommand, simplified:
import { Command } from 'commander';
import * as p from '@clack/prompts';
import pc from 'picocolors';
import {
generateReport,
renderConsole,
type GeneratedReport,
} from '@frenchexdev/requirements-compliance';
import { scanTestBindings } from '@frenchexdev/requirements-scanner';
import { detectTestSmells } from '@frenchexdev/requirements-test-smells';
import {
getFeatureRegistry,
getRequirementRegistry,
getSatisfactionLinks,
} from '@frenchexdev/requirements-shared-kernel';
import { fs } from '@frenchexdev/requirements-core/ports';
const cli = new Command('requirements');
cli.command('compliance')
.option('--strict', 'fail on any violation', false)
.action(async opts => {
const manifest = await scanTestBindings(fs, { testDir: 'test', srcDir: 'src' });
const smells = await detectTestSmells(fs, { testDir: 'test', manifest });
const report: GeneratedReport = generateReport({
bindings: manifest,
smells,
features: getFeatureRegistry(),
requirements: getRequirementRegistry(),
satisfactions: getSatisfactionLinks(),
});
renderConsole(report, { coloured: true });
if (opts.strict && report.violations.length > 0) process.exit(1);
});
cli.parseAsync(process.argv);import { Command } from 'commander';
import * as p from '@clack/prompts';
import pc from 'picocolors';
import {
generateReport,
renderConsole,
type GeneratedReport,
} from '@frenchexdev/requirements-compliance';
import { scanTestBindings } from '@frenchexdev/requirements-scanner';
import { detectTestSmells } from '@frenchexdev/requirements-test-smells';
import {
getFeatureRegistry,
getRequirementRegistry,
getSatisfactionLinks,
} from '@frenchexdev/requirements-shared-kernel';
import { fs } from '@frenchexdev/requirements-core/ports';
const cli = new Command('requirements');
cli.command('compliance')
.option('--strict', 'fail on any violation', false)
.action(async opts => {
const manifest = await scanTestBindings(fs, { testDir: 'test', srcDir: 'src' });
const smells = await detectTestSmells(fs, { testDir: 'test', manifest });
const report: GeneratedReport = generateReport({
bindings: manifest,
smells,
features: getFeatureRegistry(),
requirements: getRequirementRegistry(),
satisfactions: getSatisfactionLinks(),
});
renderConsole(report, { coloured: true });
if (opts.strict && report.violations.length > 0) process.exit(1);
});
cli.parseAsync(process.argv);Five imports from five different workspace packages; one commander wrap; one render; one exit-code decision. That is the entire shape Tier 3 should have. The wizard subcommands are denser (the @clack/prompts adapter wraps every prompt call) but follow the same flat skeleton: load, call use case, render, exit.
Why it is its own package
Two arguments, both forced by the inbound-adapter pattern.
First, the CLI's dependency surface is incompatible with library use. commander and @clack/prompts are useful inside a terminal; they are noise inside a library consumer. A future LSP extension that wants to reuse requirements-compliance and requirements-wizards from inside VS Code must not pull in commander. Splitting the CLI into its own package means the library consumers depend on the Tier-1 / Tier-2 packages directly and skip the CLI's transitive deps entirely.
Second, the CLI is the only legitimate place for the bin entry point. bin is a single-owner field — only one workspace package can claim requirements. The package that claims it is the package that runs the user's shell. That ownership is also the gate for what the user can run on the command line: every new subcommand requires a PR touching this package. The narrow ownership keeps the CLI surface auditable; expanding subcommands without expanding capabilities (the Tier-1 / Tier-2 work) is impossible.
The package is also the workspace's largest single test surface — 778 tests at the last count, plus fast-check property tests with 200 runs per invariant. The test density is justified by the CLI's role as the integration point: every subcommand exercises a different combination of Tier-1 and Tier-2 calls, and the CLI is where end-to-end regressions surface first. Concentrating that test surface in one package means one vitest config, one coverage threshold, one strict gate.
Phase 4 of the roadmap shrinks the package further. The duplicate req-discoverable-traceability.ts, the duplicate base.ts with DefaultStyleType-bound Requirement<S>, the two-line analysis/test-bindings-scanner.ts shim — all targeted for deletion as the migration completes. End state: requirements/src/bin/, requirements/src/cli/prompt-shell.ts, plus the per-subcommand wiring. Roughly a third of the current line count.
The next two pages cover the cross-cutting band: the style presets and the scenario-driven demo player.