DX as a Requirement, not a garnish
Most toolchains treat developer experience as a polish pass — something you bolt on after the engine works, if time allows, if someone cares. The result is the predictable decay curve: a correct tool nobody uses because the seams show on the keyboard. Requirements, invitations, and the friction between them quietly kill adoption long before any architectural decision gets blamed.
The @frenchexdev/requirements package refuses that framing at the spec level. DX is not a garnish. It is a typed commitment in the package's own Requirements corpus — authored in TypeScript, satisfied by concrete Features, verified by tests that will block a merge if they break.
Three of those commitments concern the keyboard side:
REQ-TUI-MODERN— the CLI must use a modern TUI library, arrow-key navigation, coloured output, spinners, grouped multi-step flows. Routed through thePromptport. No directreadlineimports permitted insrc/cli/**.REQ-BOOTSTRAP-ZERO-FRICTION— one command bootstraps a Feature end-to-end. Class, spec.json, scaffolded test stubs, all from a single interactive flow. No manual file editing.REQ-LIVE-FEEDBACK— spec changes must surface validation and coverage diagnostics live. Edit on disk, revalidate within 500 ms, no manualcompliancerun.
Each of these Requirements has a rationale, a fitCriteria, a source, a risk. Each names a concrete Feature that satisfies it. Each Feature has ACs. Each AC has a test. The chain is closed.
This chapter walks the keyboard side of those three Requirements. We show what they look like when a human sits down at a terminal and does the work. The transcripts are real in shape — reproducible because the underlying layer is @clack/prompts, a deterministic prompt library with well-known ANSI output — though the exact glyphs a given terminal renders will vary.
The showcase Feature is FEATURE-NEW-COMMAND — the requirements feature new wizard. It is the maximally-connected Feature in the package: seven Requirements satisfied, eighteen acceptance criteria, one Feature class. Most chapters in this series pick a Feature with two or three links; this one picks the hub. Everything the wizard does is traceable back to a Requirement, and every Requirement on the DX axis finds at least one AC on the wizard.
The running-example carried across the series is FEATURE-TRACE-EXPLORER-TUI — the requirements trace explore browser. It returns in §3 with its own transcript.
Three transcripts, one showcase, two diagrams, one recap. The order follows the user's day:
- open the repo and run
requirements feature new— bootstrap-zero-friction in action - run
requirements trace exploreto browse the traceability graph — tui-modern in action - run
requirements watchto get live feedback while editing — live-feedback in action
The chapter does not re-argue why DX matters. The Requirements argue that, and they are linked at the foot of every section. The chapter argues what modern DX looks like when you have committed to it as a typed contract.
One more framing note before the transcripts. In a typical codebase, the sentence "the wizard should validate IDs" lives as a comment, a PR-review remark, a Slack thread. Here it lives as:
abstract wizardValidatesIdFormat(): ACResult;abstract wizardValidatesIdFormat(): ACResult;That one line is an obligation on every maintainer who ever touches the wizard. It appears in the compliance report. It appears in the traceability matrix. It cannot be silently deleted; deletion is a spec change with an audit log entry. The wizard transcripts below are not demonstrations of what the tool happens to do — they are demonstrations of what the tool must do, because the spec says so, in the same language as the implementation.
With that frame set, we open a terminal.
The scenario
A contributor has just cloned the @frenchexdev/requirements repository. They have pnpm installed, nothing else. They want to add a new Feature — let's say FEATURE-TEMPLATE-REGISTRY, a small helper that caches the --template flag's pre-filled specs.
In a conventional codebase, the steps would be:
- Read the existing Feature files to find the shape.
- Copy-paste the closest neighbour.
- Rename the class, the id, the title.
- Guess which Requirements to link via
@Satisfies(...). - Write the abstract ACs.
- Write the spec.json by hand, or skip it.
- Write the test files, matching the AC names.
- Run compliance. Read the red. Fix the mistakes. Loop.
That is the friction REQ-BOOTSTRAP-ZERO-FRICTION exists to eliminate. The equivalent in this package is one command.
The command
npx requirements feature newnpx requirements feature newThat is it. No arguments. Every option has a prompt; every prompt has a default; every default is typed; every write is previewed.
The full transcript
The shape below is what a real run produces on a standard TTY. Glyphs render as box characters on a modern terminal; the ◆, ◇, │, └ symbols come from @clack/prompts' Unicode intro/outro frame.
$ npx requirements feature new
┌ requirements · Feature bootstrap wizard
│
◆ Welcome. This will create a new Feature class, its spec.json, and
│ scaffolded test stubs. Nothing is written to disk until you confirm.
│
│ ctrl-c aborts cleanly; no files touched.
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Feature ID
│ Must match /^FEATURE-[A-Z0-9-]+$/.
│ › FEATURE-TEMPLATE-REGISTRY
│
◇ FEATURE-TEMPLATE-REGISTRY
│
◆ Feature title
│ › Caches --template flag pre-filled specs for fast re-use
│
◇ Caches --template flag pre-filled specs for fast re-use
│
◆ Priority
│ ● Critical — blocks release
│ ● High — next release
│ ○ Medium — best-effort
│ ● Low — roadmap
│ › Medium
│
◇ Medium
│
◇──────────────────────────────────────────────────────────────────────
│
◆ This Feature satisfies which Requirement(s)?
│ space to toggle, enter to confirm, / to filter
│
│ [x] REQ-BOOTSTRAP-ZERO-FRICTION (High · Approved · UserStory)
│ [ ] REQ-SPEC-IS-SOURCE-OF-TRUTH (High · Approved · NonFunctional)
│ [ ] REQ-EDITOR-FIRST-CLASS (Medium · Approved · NonFunctional)
│ [x] REQ-TUI-MODERN (Medium · Approved · NonFunctional)
│ [ ] REQ-DSL-COMPLETE (High · Approved · Functional)
│ [ ] REQ-PARALLEL-DELIVERABLE (Medium · Draft · Process)
│ [x] REQ-DOG-FOOD (High · Approved · NonFunctional)
│ [ ] REQ-LIVE-FEEDBACK (Medium · Draft · NonFunctional)
│ [ ] REQ-DISCOVERABLE-TRACEABILITY (Medium · Draft · Functional)
│ [ ] <none of the above — author a new Requirement>
│
◇ 3 selected
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Acceptance Criteria — you will be asked for one AC per iteration.
│ Empty input ends the loop. Minimum one AC required.
│
◆ AC 1 — name (camelCase)
│ › registryCachesTemplateSpec
│
◇ registryCachesTemplateSpec
│
◆ AC 1 — JSDoc (one line; longer form can be added later)
│ › caches the template spec.json keyed by template id for O(1) lookup
│
◇ AC captured
│
◆ AC 2 — name (empty to end)
│ › registryEvictsExpiredEntries
│
◇ registryEvictsExpiredEntries
│
◆ AC 2 — JSDoc
│ › evicts entries whose mtime is older than 24 h and reloads from disk
│
◇ AC captured
│
◆ AC 3 — name (empty to end)
│ ›
│
◇ 2 ACs captured — ending loop.
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Test levels per AC (optional — skip with enter)
│ registryCachesTemplateSpec [unit]
│ registryEvictsExpiredEntries [unit, e2e]
│
◇ @Expects placements recorded.
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Preview — here is what will be written (dry-run):
│
│ new file: requirements/features/feature-template-registry.ts
│ ────────────────────────────────────────────────────────────
│ import { Feature, Priority, Satisfies, type ACResult } from '../../src';
│ import { ReqBootstrapZeroFrictionRequirement } from '../requirements/req-bootstrap-zero-friction';
│ import { ReqTuiModernRequirement } from '../requirements/req-tui-modern';
│ import { ReqDogFoodRequirement } from '../requirements/req-dog-food';
│
│ @Satisfies(
│ ReqBootstrapZeroFrictionRequirement,
│ ReqTuiModernRequirement,
│ ReqDogFoodRequirement,
│ )
│ export abstract class FeatureTemplateRegistryFeature extends Feature {
│ readonly id = 'FEATURE-TEMPLATE-REGISTRY';
│ readonly title = 'Caches --template flag pre-filled specs for fast re-use';
│ readonly priority = Priority.Medium;
│
│ /** caches the template spec.json keyed by template id for O(1) lookup */
│ abstract registryCachesTemplateSpec(): ACResult;
│
│ /** evicts entries whose mtime is older than 24 h and reloads from disk */
│ abstract registryEvictsExpiredEntries(): ACResult;
│ }
│
│ new file: requirements/features/feature-template-registry.spec.json
│ ────────────────────────────────────────────────────────────────
│ {
│ "$schema": "../../schemas/feature-spec.schema.json",
│ "$schemaVersion": "2026-04-14",
│ "id": "FEATURE-TEMPLATE-REGISTRY",
│ "title": "Caches --template flag pre-filled specs for fast re-use",
│ "priority": "Medium",
│ "satisfies": [
│ "REQ-BOOTSTRAP-ZERO-FRICTION",
│ "REQ-TUI-MODERN",
│ "REQ-DOG-FOOD"
│ ],
│ "acs": [
│ { "name": "registryCachesTemplateSpec",
│ "doc": "caches the template spec.json keyed by template id for O(1) lookup",
│ "expects": ["unit"] },
│ { "name": "registryEvictsExpiredEntries",
│ "doc": "evicts entries whose mtime is older than 24 h and reloads from disk",
│ "expects": ["unit", "e2e"] }
│ ]
│ }
│
│ new file: test/unit/feature-template-registry.spec.ts
│ ──────────────────────────────────────────────────────
│ (scaffolded — @FeatureTest + @Verifies stubs, 2 tests)
│
│ new file: test/e2e/feature-template-registry.e2e.ts
│ ────────────────────────────────────────────────────
│ (scaffolded — @FeatureTest + @Verifies stubs, 1 test)
│
│ Coverage delta if applied: +2 ACs covered (unit), +1 AC covered (e2e).
│
◇──────────────────────────────────────────────────────────────────────
│
◆ What now?
│ › Apply
│ Review each file
│ Cancel (write nothing)
│
◇ Apply
│
◆ Writing files…
│ ✓ requirements/features/feature-template-registry.ts
│ ✓ requirements/features/feature-template-registry.spec.json
│ ✓ test/unit/feature-template-registry.spec.ts
│ ✓ test/e2e/feature-template-registry.e2e.ts
│
◇ 4 files written.
│
◆ Running `requirements compliance --strict` to verify the new state…
│
│ ⠋ loading features… ✓ (41 features)
│ ⠙ loading requirements… ✓ (37 requirements)
│ ⠹ scanning test files… ✓ (779 tests)
│ ⠸ computing compliance… ✓
│
│ Compliance — PASS (strict)
│ · 0 critical uncovered ACs
│ · 0 orphan Features
│ · 0 Approved Requirements without satisfier
│ · 3 new ACs scaffolded (unit stubs fail — expected, no @Expects yet satisfied)
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Open the new Feature in VS Code?
│ › Yes
│ No
│
◇ Yes
│
│ code requirements/features/feature-template-registry.ts
│
└ Done. Next: fill in the scaffolded tests. Ctrl-click a @Verifies('name')
to jump to the matching abstract AC in the Feature class.$ npx requirements feature new
┌ requirements · Feature bootstrap wizard
│
◆ Welcome. This will create a new Feature class, its spec.json, and
│ scaffolded test stubs. Nothing is written to disk until you confirm.
│
│ ctrl-c aborts cleanly; no files touched.
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Feature ID
│ Must match /^FEATURE-[A-Z0-9-]+$/.
│ › FEATURE-TEMPLATE-REGISTRY
│
◇ FEATURE-TEMPLATE-REGISTRY
│
◆ Feature title
│ › Caches --template flag pre-filled specs for fast re-use
│
◇ Caches --template flag pre-filled specs for fast re-use
│
◆ Priority
│ ● Critical — blocks release
│ ● High — next release
│ ○ Medium — best-effort
│ ● Low — roadmap
│ › Medium
│
◇ Medium
│
◇──────────────────────────────────────────────────────────────────────
│
◆ This Feature satisfies which Requirement(s)?
│ space to toggle, enter to confirm, / to filter
│
│ [x] REQ-BOOTSTRAP-ZERO-FRICTION (High · Approved · UserStory)
│ [ ] REQ-SPEC-IS-SOURCE-OF-TRUTH (High · Approved · NonFunctional)
│ [ ] REQ-EDITOR-FIRST-CLASS (Medium · Approved · NonFunctional)
│ [x] REQ-TUI-MODERN (Medium · Approved · NonFunctional)
│ [ ] REQ-DSL-COMPLETE (High · Approved · Functional)
│ [ ] REQ-PARALLEL-DELIVERABLE (Medium · Draft · Process)
│ [x] REQ-DOG-FOOD (High · Approved · NonFunctional)
│ [ ] REQ-LIVE-FEEDBACK (Medium · Draft · NonFunctional)
│ [ ] REQ-DISCOVERABLE-TRACEABILITY (Medium · Draft · Functional)
│ [ ] <none of the above — author a new Requirement>
│
◇ 3 selected
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Acceptance Criteria — you will be asked for one AC per iteration.
│ Empty input ends the loop. Minimum one AC required.
│
◆ AC 1 — name (camelCase)
│ › registryCachesTemplateSpec
│
◇ registryCachesTemplateSpec
│
◆ AC 1 — JSDoc (one line; longer form can be added later)
│ › caches the template spec.json keyed by template id for O(1) lookup
│
◇ AC captured
│
◆ AC 2 — name (empty to end)
│ › registryEvictsExpiredEntries
│
◇ registryEvictsExpiredEntries
│
◆ AC 2 — JSDoc
│ › evicts entries whose mtime is older than 24 h and reloads from disk
│
◇ AC captured
│
◆ AC 3 — name (empty to end)
│ ›
│
◇ 2 ACs captured — ending loop.
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Test levels per AC (optional — skip with enter)
│ registryCachesTemplateSpec [unit]
│ registryEvictsExpiredEntries [unit, e2e]
│
◇ @Expects placements recorded.
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Preview — here is what will be written (dry-run):
│
│ new file: requirements/features/feature-template-registry.ts
│ ────────────────────────────────────────────────────────────
│ import { Feature, Priority, Satisfies, type ACResult } from '../../src';
│ import { ReqBootstrapZeroFrictionRequirement } from '../requirements/req-bootstrap-zero-friction';
│ import { ReqTuiModernRequirement } from '../requirements/req-tui-modern';
│ import { ReqDogFoodRequirement } from '../requirements/req-dog-food';
│
│ @Satisfies(
│ ReqBootstrapZeroFrictionRequirement,
│ ReqTuiModernRequirement,
│ ReqDogFoodRequirement,
│ )
│ export abstract class FeatureTemplateRegistryFeature extends Feature {
│ readonly id = 'FEATURE-TEMPLATE-REGISTRY';
│ readonly title = 'Caches --template flag pre-filled specs for fast re-use';
│ readonly priority = Priority.Medium;
│
│ /** caches the template spec.json keyed by template id for O(1) lookup */
│ abstract registryCachesTemplateSpec(): ACResult;
│
│ /** evicts entries whose mtime is older than 24 h and reloads from disk */
│ abstract registryEvictsExpiredEntries(): ACResult;
│ }
│
│ new file: requirements/features/feature-template-registry.spec.json
│ ────────────────────────────────────────────────────────────────
│ {
│ "$schema": "../../schemas/feature-spec.schema.json",
│ "$schemaVersion": "2026-04-14",
│ "id": "FEATURE-TEMPLATE-REGISTRY",
│ "title": "Caches --template flag pre-filled specs for fast re-use",
│ "priority": "Medium",
│ "satisfies": [
│ "REQ-BOOTSTRAP-ZERO-FRICTION",
│ "REQ-TUI-MODERN",
│ "REQ-DOG-FOOD"
│ ],
│ "acs": [
│ { "name": "registryCachesTemplateSpec",
│ "doc": "caches the template spec.json keyed by template id for O(1) lookup",
│ "expects": ["unit"] },
│ { "name": "registryEvictsExpiredEntries",
│ "doc": "evicts entries whose mtime is older than 24 h and reloads from disk",
│ "expects": ["unit", "e2e"] }
│ ]
│ }
│
│ new file: test/unit/feature-template-registry.spec.ts
│ ──────────────────────────────────────────────────────
│ (scaffolded — @FeatureTest + @Verifies stubs, 2 tests)
│
│ new file: test/e2e/feature-template-registry.e2e.ts
│ ────────────────────────────────────────────────────
│ (scaffolded — @FeatureTest + @Verifies stubs, 1 test)
│
│ Coverage delta if applied: +2 ACs covered (unit), +1 AC covered (e2e).
│
◇──────────────────────────────────────────────────────────────────────
│
◆ What now?
│ › Apply
│ Review each file
│ Cancel (write nothing)
│
◇ Apply
│
◆ Writing files…
│ ✓ requirements/features/feature-template-registry.ts
│ ✓ requirements/features/feature-template-registry.spec.json
│ ✓ test/unit/feature-template-registry.spec.ts
│ ✓ test/e2e/feature-template-registry.e2e.ts
│
◇ 4 files written.
│
◆ Running `requirements compliance --strict` to verify the new state…
│
│ ⠋ loading features… ✓ (41 features)
│ ⠙ loading requirements… ✓ (37 requirements)
│ ⠹ scanning test files… ✓ (779 tests)
│ ⠸ computing compliance… ✓
│
│ Compliance — PASS (strict)
│ · 0 critical uncovered ACs
│ · 0 orphan Features
│ · 0 Approved Requirements without satisfier
│ · 3 new ACs scaffolded (unit stubs fail — expected, no @Expects yet satisfied)
│
◇──────────────────────────────────────────────────────────────────────
│
◆ Open the new Feature in VS Code?
│ › Yes
│ No
│
◇ Yes
│
│ code requirements/features/feature-template-registry.ts
│
└ Done. Next: fill in the scaffolded tests. Ctrl-click a @Verifies('name')
to jump to the matching abstract AC in the Feature class.What that transcript satisfied
Every boxed step in that run is a direct match for one or more ACs on FEATURE-NEW-COMMAND. The showcase section (§5) walks the mapping in full. For now, the shape to notice:
- No file was written before the confirmation screen. That is AC
previewShowsDiffBeforeAnyWrite— and the preview is not a courtesy, it is a contractual obligation. If a future refactor short-circuits the preview, the test bound to that AC fails and the PR does not merge. - Every prompt had a typed default. Priority is an enum; the multiselect reads the filesystem for existing Requirements; the AC loop terminates on empty input. These are ACs
wizardCollectsIdTitlePriority,wizardLoopsAcsUntilEnd,wizardValidatesIdFormat,wizardValidatesAcNameFormat. - The compliance verify ran automatically. The wizard did not say "you might want to run compliance" — it ran compliance, because the spec says so. The wizard's own transcript ends with a green strict-compliance result.
- The editor-open prompt is opt-in. AC
endOfWizardOffersOpenInEditor— offer, not force. - The filesystem was only touched through the
FileSystemport. ACgeneratedFileGoesToFeaturesDirbounds the path; the port bounds the mechanism. A unit test injects an in-memory map and asserts the write paths without ever touching disk.
The --yes and --replay doors
The wizard is the interactive front door. Two side doors exist for when a human is not at the keyboard, or when a human wants to re-apply a known spec:
# replay an existing spec.json through the wizard, seeding every default
npx requirements feature new --replay requirements/features/feature-template-registry.spec.json
# non-interactive: take every default, skip every prompt, apply immediately
npx requirements feature new --replay feature-template-registry.spec.json --yes# replay an existing spec.json through the wizard, seeding every default
npx requirements feature new --replay requirements/features/feature-template-registry.spec.json
# non-interactive: take every default, skip every prompt, apply immediately
npx requirements feature new --replay feature-template-registry.spec.json --yes--yes skipping all prompts is AC yesFlagSkipsAllPromptsAndApplies. --replay seeding defaults is AC replaySeedsDefaultsFromSpec. The two combine to give CI a deterministic path — the same wizard that a human drives interactively can run unattended on a fresh checkout and regenerate the exact same Feature file.
Safety rails
Two final ACs deserve a note because they are the kind of thing that only shows when something has already gone wrong:
refusesOverwriteUntrackedFile— if the target path exists on disk but is not tracked by git, the wizard refuses to overwrite and tells the user what it found. A careless scaffolder that clobbers untracked work is indistinguishable from data loss.dryRunFlagForcesPreviewCancel—--dry-runforces the preview-then-cancel path regardless of what the user clicks. Useful for CI verification of what would be written without touching the tree.
That is bootstrap-zero-friction on the keyboard. One command, a few minutes, a Feature class + spec + scaffolded tests + compliance verified. The wizard is typed, the typing is dog-fooded, the dog-fooding is in the same repo as the wizard that dog-foods it.
TUI-modern on the keyboard
The running example for this chapter's second transcript is FEATURE-TRACE-EXPLORER-TUI — the browser over the traceability graph. It satisfies REQ-DISCOVERABLE-TRACEABILITY (primary), REQ-DOG-FOOD, and REQ-PARALLEL-DELIVERABLE. It is enabled = false at time of writing — still roadmap — but its spec, ACs, and test scaffolding already exist. This transcript shows the intended interaction.
The command
npx requirements trace explorenpx requirements trace exploreNo arguments. The explorer opens at the graph root — the list of Requirements.
The full transcript
$ npx requirements trace explore
┌ requirements · traceability explorer
│
│ Loaded 37 Requirements · 41 Features · 312 ACs · 779 Tests
│ Graph built in 84 ms. Press ? for help, q to quit, ↑↓ to navigate.
│
├─ Requirements ───────────────────────────────────────────────── 37 ┤
│
│ > REQ-BOOTSTRAP-ZERO-FRICTION High Approved UserStory
│ REQ-SPEC-IS-SOURCE-OF-TRUTH High Approved NonFunctional
│ REQ-DSL-COMPLETE High Approved Functional
│ REQ-EDITOR-FIRST-CLASS Medium Approved NonFunctional
│ REQ-TUI-MODERN Medium Approved NonFunctional
│ REQ-LIVE-FEEDBACK Medium Draft NonFunctional
│ REQ-DISCOVERABLE-TRACEABILITY Medium Draft Functional
│ REQ-DOG-FOOD High Approved NonFunctional
│ REQ-PARALLEL-DELIVERABLE Medium Draft Process
│ REQ-PORT-DRIVEN High Approved NonFunctional
│ …
│
└─ ↑↓ navigate · → drill down · ← back · ? help · / filter · q quit ──$ npx requirements trace explore
┌ requirements · traceability explorer
│
│ Loaded 37 Requirements · 41 Features · 312 ACs · 779 Tests
│ Graph built in 84 ms. Press ? for help, q to quit, ↑↓ to navigate.
│
├─ Requirements ───────────────────────────────────────────────── 37 ┤
│
│ > REQ-BOOTSTRAP-ZERO-FRICTION High Approved UserStory
│ REQ-SPEC-IS-SOURCE-OF-TRUTH High Approved NonFunctional
│ REQ-DSL-COMPLETE High Approved Functional
│ REQ-EDITOR-FIRST-CLASS Medium Approved NonFunctional
│ REQ-TUI-MODERN Medium Approved NonFunctional
│ REQ-LIVE-FEEDBACK Medium Draft NonFunctional
│ REQ-DISCOVERABLE-TRACEABILITY Medium Draft Functional
│ REQ-DOG-FOOD High Approved NonFunctional
│ REQ-PARALLEL-DELIVERABLE Medium Draft Process
│ REQ-PORT-DRIVEN High Approved NonFunctional
│ …
│
└─ ↑↓ navigate · → drill down · ← back · ? help · / filter · q quit ──User presses → on REQ-BOOTSTRAP-ZERO-FRICTION.
┌ REQ-BOOTSTRAP-ZERO-FRICTION · Features that satisfy it
│
│ Title: One command bootstraps a Feature end-to-end
│ Priority: High · Status: Approved · Kind: UserStory
│ Risk: High — if not met, the DSL stays niche.
│
│ Statement (ubiquitous):
│ The system shall let a user create a Feature class, its spec.json,
│ and the scaffolded test stubs by running one interactive command,
│ with no manual file editing.
│
├─ Features ────────────────────────────────────────────────────── 2 ┤
│
│ > FEATURE-NEW-COMMAND High 18 ACs 17 covered
│ FEATURE-REQUIREMENT-NEW-COMMAND High 11 ACs 11 covered
│
└─ ↑↓ · → drill · ← back · ? help · q quit ───────────────────────── ┌ REQ-BOOTSTRAP-ZERO-FRICTION · Features that satisfy it
│
│ Title: One command bootstraps a Feature end-to-end
│ Priority: High · Status: Approved · Kind: UserStory
│ Risk: High — if not met, the DSL stays niche.
│
│ Statement (ubiquitous):
│ The system shall let a user create a Feature class, its spec.json,
│ and the scaffolded test stubs by running one interactive command,
│ with no manual file editing.
│
├─ Features ────────────────────────────────────────────────────── 2 ┤
│
│ > FEATURE-NEW-COMMAND High 18 ACs 17 covered
│ FEATURE-REQUIREMENT-NEW-COMMAND High 11 ACs 11 covered
│
└─ ↑↓ · → drill · ← back · ? help · q quit ─────────────────────────User presses → on FEATURE-NEW-COMMAND.
┌ FEATURE-NEW-COMMAND · Acceptance Criteria
│
│ Title: `requirements feature new` — interactive Feature bootstrap wizard
│ Priority: High · 18 ACs · 17 covered (94%)
│
│ Satisfies:
│ · REQ-BOOTSTRAP-ZERO-FRICTION (primary)
│ · REQ-SPEC-IS-SOURCE-OF-TRUTH (joint)
│ · REQ-EDITOR-FIRST-CLASS (joint)
│ · REQ-TUI-MODERN (joint)
│ · REQ-DSL-COMPLETE (joint)
│ · REQ-PARALLEL-DELIVERABLE (joint)
│ · REQ-DOG-FOOD (joint)
│
├─ ACs ──────────────────────────────────────────────────────────── 18 ┤
│
│ ✓ wizardCollectsIdTitlePriority [unit, e2e]
│ ✓ wizardLoopsAcsUntilEnd [unit, e2e]
│ ✓ wizardValidatesIdFormat [unit]
│ ✓ wizardValidatesAcNameFormat [unit]
│ ✓ wizardRefusesEmptyExpects [unit]
│ ✓ replaySeedsDefaultsFromSpec [unit]
│ ✓ replayOffersKeepEditDeletePerAc [unit]
│ ✓ yesFlagSkipsAllPromptsAndApplies [unit, e2e]
│ ✓ generateFeatureSourceMatchesSpec [unit]
│ ✓ generatedFileGoesToFeaturesDir [unit]
│ ✓ specJsonSavedNextToFeature [unit]
│ ✓ scaffoldsUnitTestsForUncoveredAcs [unit]
│ ✓ scaffoldsE2eOnlyWhenAcExpectsEndToEnd [unit]
│ ✓ noTestFlagSkipsScaffolding [unit]
│ ✓ previewShowsDiffBeforeAnyWrite [unit, e2e]
│ ✓ previewOffersApplyReviewCancel [unit]
│ ✓ dryRunFlagForcesPreviewCancel [unit]
│ ✗ endOfWizardOffersOpenInEditor [ ] not yet covered
│ ✓ refusesOverwriteUntrackedFile [unit]
│ > ✓ wizardOffersRequirementSelection [unit, e2e]
│ ✓ replayValidatesSpecAgainstSchema [unit]
│ ✓ templateFlagPreFillsSpec [unit]
│ ✓ previewIncludesCoverageDelta [unit]
│ ✓ walksFullWizardEndToEnd [e2e]
│
└─ ↑↓ · → drill · ← back · ? help · q quit ───────────────────────── ┌ FEATURE-NEW-COMMAND · Acceptance Criteria
│
│ Title: `requirements feature new` — interactive Feature bootstrap wizard
│ Priority: High · 18 ACs · 17 covered (94%)
│
│ Satisfies:
│ · REQ-BOOTSTRAP-ZERO-FRICTION (primary)
│ · REQ-SPEC-IS-SOURCE-OF-TRUTH (joint)
│ · REQ-EDITOR-FIRST-CLASS (joint)
│ · REQ-TUI-MODERN (joint)
│ · REQ-DSL-COMPLETE (joint)
│ · REQ-PARALLEL-DELIVERABLE (joint)
│ · REQ-DOG-FOOD (joint)
│
├─ ACs ──────────────────────────────────────────────────────────── 18 ┤
│
│ ✓ wizardCollectsIdTitlePriority [unit, e2e]
│ ✓ wizardLoopsAcsUntilEnd [unit, e2e]
│ ✓ wizardValidatesIdFormat [unit]
│ ✓ wizardValidatesAcNameFormat [unit]
│ ✓ wizardRefusesEmptyExpects [unit]
│ ✓ replaySeedsDefaultsFromSpec [unit]
│ ✓ replayOffersKeepEditDeletePerAc [unit]
│ ✓ yesFlagSkipsAllPromptsAndApplies [unit, e2e]
│ ✓ generateFeatureSourceMatchesSpec [unit]
│ ✓ generatedFileGoesToFeaturesDir [unit]
│ ✓ specJsonSavedNextToFeature [unit]
│ ✓ scaffoldsUnitTestsForUncoveredAcs [unit]
│ ✓ scaffoldsE2eOnlyWhenAcExpectsEndToEnd [unit]
│ ✓ noTestFlagSkipsScaffolding [unit]
│ ✓ previewShowsDiffBeforeAnyWrite [unit, e2e]
│ ✓ previewOffersApplyReviewCancel [unit]
│ ✓ dryRunFlagForcesPreviewCancel [unit]
│ ✗ endOfWizardOffersOpenInEditor [ ] not yet covered
│ ✓ refusesOverwriteUntrackedFile [unit]
│ > ✓ wizardOffersRequirementSelection [unit, e2e]
│ ✓ replayValidatesSpecAgainstSchema [unit]
│ ✓ templateFlagPreFillsSpec [unit]
│ ✓ previewIncludesCoverageDelta [unit]
│ ✓ walksFullWizardEndToEnd [e2e]
│
└─ ↑↓ · → drill · ← back · ? help · q quit ─────────────────────────User presses ?.
┌ Help
│
│ ↑ / k cursor up
│ ↓ / j cursor down
│ → / enter drill down into focused node
│ ← / backspace back up one level
│ / filter the current list
│ g jump to top
│ G jump to bottom
│ ? this help overlay (any key dismisses)
│ q / ctrl-c quit
│
│ Current location: Requirement › Feature › ACs
│ Graph nodes: 37 Req · 41 Feat · 312 AC · 779 Test
│
└ press any key to dismiss ───────────────────────────────────────── ┌ Help
│
│ ↑ / k cursor up
│ ↓ / j cursor down
│ → / enter drill down into focused node
│ ← / backspace back up one level
│ / filter the current list
│ g jump to top
│ G jump to bottom
│ ? this help overlay (any key dismisses)
│ q / ctrl-c quit
│
│ Current location: Requirement › Feature › ACs
│ Graph nodes: 37 Req · 41 Feat · 312 AC · 779 Test
│
└ press any key to dismiss ─────────────────────────────────────────User presses any key, is returned to the AC list, presses → on wizardOffersRequirementSelection.
┌ AC · wizardOffersRequirementSelection
│
│ Feature: FEATURE-NEW-COMMAND
│ Expects: [unit, e2e]
│
│ JSDoc:
│ Wizard presents a multiselect of existing Requirements discovered
│ via the FileSystem port. Selecting "<none of the above>" recurses
│ into `requirement new`. Cross-border AC: wizard core + Req module.
│
├─ Tests that verify this AC ────────────────────────────────────── 3 ┤
│
│ > test/unit/feature-new-command.spec.ts:312
│ @FeatureTest(FeatureNewCommandFeature)
│ @Verifies('wizardOffersRequirementSelection')
│ wizardOffersRequirementSelectionTest()
│
│ test/unit/feature-new-command.spec.ts:344
│ @FeatureTest(FeatureNewCommandFeature)
│ @Verifies('wizardOffersRequirementSelection')
│ wizardOffersRequirementSelectionRecursesTest()
│
│ test/e2e/feature-new-command.e2e.ts:88
│ @FeatureTest(FeatureNewCommandFeature)
│ @Verifies('wizardOffersRequirementSelection')
│ @Expects(TestLevel.E2E)
│ walksRequirementSelectionEndToEndTest()
│
└─ ↑↓ · → open in editor · ← back · q quit ────────────────────────── ┌ AC · wizardOffersRequirementSelection
│
│ Feature: FEATURE-NEW-COMMAND
│ Expects: [unit, e2e]
│
│ JSDoc:
│ Wizard presents a multiselect of existing Requirements discovered
│ via the FileSystem port. Selecting "<none of the above>" recurses
│ into `requirement new`. Cross-border AC: wizard core + Req module.
│
├─ Tests that verify this AC ────────────────────────────────────── 3 ┤
│
│ > test/unit/feature-new-command.spec.ts:312
│ @FeatureTest(FeatureNewCommandFeature)
│ @Verifies('wizardOffersRequirementSelection')
│ wizardOffersRequirementSelectionTest()
│
│ test/unit/feature-new-command.spec.ts:344
│ @FeatureTest(FeatureNewCommandFeature)
│ @Verifies('wizardOffersRequirementSelection')
│ wizardOffersRequirementSelectionRecursesTest()
│
│ test/e2e/feature-new-command.e2e.ts:88
│ @FeatureTest(FeatureNewCommandFeature)
│ @Verifies('wizardOffersRequirementSelection')
│ @Expects(TestLevel.E2E)
│ walksRequirementSelectionEndToEndTest()
│
└─ ↑↓ · → open in editor · ← back · q quit ──────────────────────────User presses q.
└ Goodbye. 3 drills, 1 help overlay, 0 edits. └ Goodbye. 3 drills, 1 help overlay, 0 edits.The primitives that make it feel native
No custom rendering engine. No curses. The explorer is a composition of @clack/prompts' stateful primitives:
group— the wizard state machine that gates back/forward navigation.select— the list picker, which handles arrow keys, enter, filtering.note— the framed panels that show Requirement detail, AC detail, test detail.spinner— the graph-loading indicator.- Custom keystroke hook — the
?help overlay and the←back-up are implemented as a thin keystroke listener that emits navigation events into the group. Still inside the Prompt port; no directreadline.
The point of REQ-TUI-MODERN is not that the explorer looks pretty. The point is that a developer used to create-t3-app / create-astro / create-vite recognises the shape immediately and does not need to learn a new set of gestures. The 2026 bar for DX is clack's bar. The Requirement freezes that bar in place.
What the port buys us
The Prompt port — promptShell in src/cli/prompt-shell.ts — has two implementations:
clackPromptShell— the real one, used by the commander binary insrc/bin/.scriptedPromptShell— the unit-test one, used by the 779 tests. Takes an array of answers, returns them in order. Records every call.
Because every interactive prompt in the explorer routes through the port, every interaction is testable without a TTY. The e2e tests drive the real clack shell in a node-pty pseudo-terminal; the unit tests drive the scripted shell in memory. The AC traceExplorerUsesPromptPortForInteraction is what pins that architecture in place. If a future PR imports readline directly in the explorer, an inspection AC catches it and the PR does not land.
The command
npx requirements watchnpx requirements watchThe watcher starts, takes an initial scan of requirements/** and test/**, prints the current compliance delta against the last committed state, then holds the terminal open and waits for events.
The full transcript
The first lines are the boot:
$ npx requirements watch
┌ requirements · watch
│
│ Watching:
│ requirements/**/*.{ts,json}
│ test/**/*.{spec,e2e,a11y,visual,perf}.ts
│
│ Debounce: 75 ms · Latency budget: 500 ms · Port: FileSystem#watch
│
◆ Initial scan…
│ ⠋ features ✓ 41
│ ⠙ requirements ✓ 37
│ ⠹ tests ✓ 779
│ ⠸ compliance ✓ PASS (strict)
│
│ [14:02:17] ready — 41 F · 37 R · 312 AC · 779 T · 0 red
│
└ Ctrl-C to exit ──────────────────────────────────────────────────$ npx requirements watch
┌ requirements · watch
│
│ Watching:
│ requirements/**/*.{ts,json}
│ test/**/*.{spec,e2e,a11y,visual,perf}.ts
│
│ Debounce: 75 ms · Latency budget: 500 ms · Port: FileSystem#watch
│
◆ Initial scan…
│ ⠋ features ✓ 41
│ ⠙ requirements ✓ 37
│ ⠹ tests ✓ 779
│ ⠸ compliance ✓ PASS (strict)
│
│ [14:02:17] ready — 41 F · 37 R · 312 AC · 779 T · 0 red
│
└ Ctrl-C to exit ──────────────────────────────────────────────────At 14:02:41 the user saves requirements/features/feature-template-registry.ts after adding a third AC. The watcher emits:
[14:02:41.112] ↻ change requirements/features/feature-template-registry.ts
[14:02:41.187] ⠋ revalidating feature-template-registry
[14:02:41.329] ✗ FEATURE-TEMPLATE-REGISTRY
+1 AC registryRespectsSizeCap not covered · red
Delta: 41 F · 37 R · 313 AC · 779 T · 1 red (latency 217 ms) [14:02:41.112] ↻ change requirements/features/feature-template-registry.ts
[14:02:41.187] ⠋ revalidating feature-template-registry
[14:02:41.329] ✗ FEATURE-TEMPLATE-REGISTRY
+1 AC registryRespectsSizeCap not covered · red
Delta: 41 F · 37 R · 313 AC · 779 T · 1 red (latency 217 ms)The colours that the terminal renders (✗ and red in red, Delta in yellow, [timestamp] in dim grey, latency in green when under budget) are stripped from this transcript. The ANSI sequences in the real output are \x1b[31m for red, \x1b[33m for yellow, \x1b[32m for green, \x1b[2m for dim. Those sequences are themselves routed through picocolors — never raw — so that a non-TTY consumer (CI log, | tee) gets the text without the control codes.
At 14:03:15 the user saves test/unit/feature-template-registry.spec.ts after adding a test bound to the new AC:
[14:03:15.422] ↻ change test/unit/feature-template-registry.spec.ts
[14:03:15.483] ⠋ revalidating test scan
[14:03:15.601] ✓ FEATURE-TEMPLATE-REGISTRY
+1 test registryRespectsSizeCap covered · green
Delta: 41 F · 37 R · 313 AC · 780 T · 0 red (latency 179 ms) [14:03:15.422] ↻ change test/unit/feature-template-registry.spec.ts
[14:03:15.483] ⠋ revalidating test scan
[14:03:15.601] ✓ FEATURE-TEMPLATE-REGISTRY
+1 test registryRespectsSizeCap covered · green
Delta: 41 F · 37 R · 313 AC · 780 T · 0 red (latency 179 ms)Green. The flip was live. No compliance command was re-run manually. The watcher saw the file change, the debounce closed, the partial re-scan ran, the new test was bound to the AC, the red became green, the summary line was printed.
The burst case
The user now does something normal — saves three times in quick succession (say, because VS Code's formatter triggered a second save after the first, and they hit Ctrl-S again for muscle-memory reasons). Debounce in action:
[14:04:02.100] ↻ change requirements/features/feature-template-registry.ts
[14:04:02.118] ↻ change requirements/features/feature-template-registry.ts (debounced)
[14:04:02.127] ↻ change requirements/features/feature-template-registry.ts (debounced)
[14:04:02.202] ⠋ revalidating feature-template-registry (1 revalidation for 3 saves)
[14:04:02.381] ✓ FEATURE-TEMPLATE-REGISTRY
Delta: 41 F · 37 R · 313 AC · 780 T · 0 red (latency 281 ms) [14:04:02.100] ↻ change requirements/features/feature-template-registry.ts
[14:04:02.118] ↻ change requirements/features/feature-template-registry.ts (debounced)
[14:04:02.127] ↻ change requirements/features/feature-template-registry.ts (debounced)
[14:04:02.202] ⠋ revalidating feature-template-registry (1 revalidation for 3 saves)
[14:04:02.381] ✓ FEATURE-TEMPLATE-REGISTRY
Delta: 41 F · 37 R · 313 AC · 780 T · 0 red (latency 281 ms)Three saves, one revalidation, one summary. That is AC watchDetectsRapidSuccessionAndDebounces on FEATURE-WATCH-MODE, and AC endToEndBurstOfSavesYieldsExactlyOneRevalidation. The debounce window is 75 ms — long enough to collapse a double-save from editor auto-save, short enough that a deliberate second edit 200 ms later still feels live.
Schema violation in flight
The user edits the spec.json directly and introduces a typo — "priority": "Meduim". The watcher catches it before the next compliance run:
[14:04:41.009] ↻ change requirements/features/feature-template-registry.spec.json
[14:04:41.074] ⠋ revalidating feature-template-registry spec
[14:04:41.188] ✗ feature-template-registry.spec.json — schema violation
at $.priority
expected: enum ["Critical","High","Medium","Low"]
got: "Meduim"
Delta: (spec rejected — previous state preserved) (latency 179 ms) [14:04:41.009] ↻ change requirements/features/feature-template-registry.spec.json
[14:04:41.074] ⠋ revalidating feature-template-registry spec
[14:04:41.188] ✗ feature-template-registry.spec.json — schema violation
at $.priority
expected: enum ["Critical","High","Medium","Low"]
got: "Meduim"
Delta: (spec rejected — previous state preserved) (latency 179 ms)The previous compliance state is preserved. The broken spec does not corrupt the in-memory graph. Fix the typo; save; green. AC watchSurfacesSchemaViolationsInline.
Editor swap-files ignored
On Windows, VS Code writes temporary swap files (.feature-template-registry.ts.xxxx.tmp, trailing ~, .swp on neovim users, 4913 for vim's fsync probe). The watcher filters those before the debounce even fires:
[14:05:11.501] (ignored) requirements/features/.feature-template-registry.ts.4913
[14:05:11.502] (ignored) requirements/features/4913 [14:05:11.501] (ignored) requirements/features/.feature-template-registry.ts.4913
[14:05:11.502] (ignored) requirements/features/4913No revalidation. No spam in the summary line. AC watchIgnoresTemporaryEditorSwapFiles.
Clean teardown
Ctrl-C:
^C
[14:08:02.117] ◆ shutting down
│ ✓ closed fs watcher (requirements/**)
│ ✓ closed fs watcher (test/**)
│ ✓ flushed pending debounce
│
└ Bye. 14 changes observed · 11 revalidations · 3 red → green flips. ^C
[14:08:02.117] ◆ shutting down
│ ✓ closed fs watcher (requirements/**)
│ ✓ closed fs watcher (test/**)
│ ✓ flushed pending debounce
│
└ Bye. 14 changes observed · 11 revalidations · 3 red → green flips.Every watcher torn down. No orphan handles. AC watchTearsDownAllWatchersOnCtrlC. On a normal exit the cleanup runs in the finally block of the top-level async scope; on a signal it runs in the SIGINT handler. Both paths close through the same FileSystem#watch port return value.
Showcase — FEATURE-NEW-COMMAND end-to-end
This section walks the maximally-connected Feature in the package. It is the hub of the DX axis: seven Requirements point at it, eighteen acceptance criteria elaborate it, the wizard transcript in §2 executes it.
The class
From packages/requirements/requirements/features/feature-new-command.ts:
@Satisfies(
ReqBootstrapZeroFrictionRequirement, // primary
ReqSpecIsSourceOfTruthRequirement, // joint: preview before write
ReqEditorFirstClassRequirement, // joint: emits $schema key
ReqTuiModernRequirement, // joint: uses Prompt port
ReqDslCompleteRequirement, // joint: wizard asks which Requirement(s)
ReqParallelDeliverableRequirement, // joint: one of the parallel streams
ReqDogFoodRequirement, // joint: tests use @FeatureTest
)
export abstract class FeatureNewCommandFeature extends Feature {
readonly id = 'FEATURE-NEW-COMMAND';
readonly title = '`requirements feature new` — interactive Feature bootstrap wizard';
readonly priority = Priority.High;
// … 18 abstract ACs …
}@Satisfies(
ReqBootstrapZeroFrictionRequirement, // primary
ReqSpecIsSourceOfTruthRequirement, // joint: preview before write
ReqEditorFirstClassRequirement, // joint: emits $schema key
ReqTuiModernRequirement, // joint: uses Prompt port
ReqDslCompleteRequirement, // joint: wizard asks which Requirement(s)
ReqParallelDeliverableRequirement, // joint: one of the parallel streams
ReqDogFoodRequirement, // joint: tests use @FeatureTest
)
export abstract class FeatureNewCommandFeature extends Feature {
readonly id = 'FEATURE-NEW-COMMAND';
readonly title = '`requirements feature new` — interactive Feature bootstrap wizard';
readonly priority = Priority.High;
// … 18 abstract ACs …
}The comments next to each @Satisfies entry are not decoration — they are the joint-contribution ledger. FEATURE-NEW-COMMAND is owned by Stream A (wizard core) but has cross-border contributions from Stream B (spec schema), Stream C (editor integration), and Stream E (dog-fooding). The ledger is how those streams synchronise without a project manager.
The eighteen ACs, mapped to transcript steps
Below, every AC of FEATURE-NEW-COMMAND is mapped to the transcript step that exercises it. The bracketed codes are the section anchors: §2 for the wizard transcript, §3 for the explorer transcript, §4 for the watch transcript.
Wizard flow (5 ACs)
wizardCollectsIdTitlePriority— §2 prompts "Feature ID / Feature title / Priority". Three prompts, three typed captures. Unit test asserts the scripted prompt shell receives exactly those threetext|selectcalls in that order.wizardLoopsAcsUntilEnd— §2 "AC 1 · AC 2 · AC 3 — empty ends the loop". Unit test asserts that N non-empty inputs produce N ACs and an empty input closes the loop.wizardValidatesIdFormat— §2 the ID prompt's hintMust match /^FEATURE-[A-Z0-9-]+$/. A unit test feedsfeature-foo(lowercase) and asserts the validator rejects, re-prompts, and the invalid string never reaches the spec.wizardValidatesAcNameFormat— §2 the AC-name prompt acceptsregistryCachesTemplateSpec(camelCase) but rejectsregistry-cachesorRegistryCachesTemplateSpec. Unit test asserts both negative cases re-prompt.wizardRefusesEmptyExpects— §2 the "Test levels per AC" panel: if you explicitly open an AC's expects list and leave it empty, the wizard re-prompts. Empty is a skip-with-enter, not a confirmed-empty.
Replay (3 ACs)
replaySeedsDefaultsFromSpec—--replay foo.spec.jsonpre-fills every prompt with the file's current value. The user sees their own spec and can accept, edit, or delete each AC.replayOffersKeepEditDeletePerAc— for each AC in the replayed spec the wizard asksKeep / Edit / Delete. This is how thefeature syncflow converges a hand-edited spec back into the class.yesFlagSkipsAllPromptsAndApplies—--replay foo.spec.json --yesproduces no prompts at all, takes every default, applies. This is the CI path.
Source generation (3 ACs)
generateFeatureSourceMatchesSpec— the file the wizard writes, when parsed, yields a spec that is byte-equivalent (modulo key ordering) to the spec.json it also writes. Round-trip test.generatedFileGoesToFeaturesDir— the Feature.tsis written underrequirements/features/, not anywhere else. Port-level path assertion.specJsonSavedNextToFeature— the spec.json sits next to the Feature class it describes, same basename, same directory. Makesfeature synctrivial to implement.
Test scaffolding (3 ACs)
scaffoldsUnitTestsForUncoveredAcs— for every AC that lacks@Expects, a unit stub is written with@FeatureTest + @Verifies + @Expects(TestLevel.Unit). The stub throwsNotImplementedErrorso the test runs red until a human fills it.scaffoldsE2eOnlyWhenAcExpectsEndToEnd— e2e stubs are generated only when the AC's expects list includesTestLevel.E2E. No blanket e2e scaffolding.noTestFlagSkipsScaffolding—--no-testinhibits all scaffolder writes. The flag is for the--yes --replaypath where the caller has their own test strategy.
Preview / dry-run (3 ACs)
previewShowsDiffBeforeAnyWrite— §2 the "Preview — here is what will be written" panel. No file touches the disk before the user sees this. Unit test: drive the wizard with the scripted shell, capture the recorded prompt calls, assert that the firstfileSystem.writeFilecall happens after the "What now?" prompt is answered withApply.previewOffersApplyReviewCancel— §2 the three-option prompt. Unit test asserts the select options are exactly those three strings.dryRunFlagForcesPreviewCancel—--dry-runprints the preview panel then exits with code 0, no write. Unit test asserts the recordedfileSystemhas zero write calls after a--dry-runrun.
Post-apply editor open (1 AC)
endOfWizardOffersOpenInEditor— §2 the finalOpen the new Feature in VS Code?prompt. Note: this AC is the single uncovered AC in the §3 drilldown transcript — the test for it is roadmap.
Safety (1 AC)
refusesOverwriteUntrackedFile— if the Feature path already exists but is not tracked in git, the wizard prints a warning and offersAbort / Overwrite anyway. Default is abort.
Cross-link (1 AC)
wizardOffersRequirementSelection— §2 the@Satisfiesmultiselect. The "<none of the above — author a new Requirement>" option recurses intorequirement newinline and returns control to the outer wizard with the new Requirement pre-selected. This is the cross-border AC with Stream F (requirement module).
Schema validation pre-write (1 AC)
replayValidatesSpecAgainstSchema— in--replaymode the replayed spec is validated againstrequirement-spec.schema.jsonbefore the wizard seeds defaults. A broken spec is rejected with the exact$.pathof the violation, not a cryptic parse error later in the flow.
Templates (1 AC)
templateFlagPreFillsSpec—--template <id>looks up a named pre-filled spec in the template registry (see the FEATURE-TEMPLATE-REGISTRY built in §2!) and seeds the wizard with it. The user edits from the template instead of from a blank slate.
Coverage delta in preview (1 AC)
previewIncludesCoverageDelta— §2 theCoverage delta if applied: +2 ACs coveredline. The preview runs an in-memory compliance against the projected post-apply state and reports the delta.
End-to-end (1 AC)
walksFullWizardEndToEnd— the single e2e test. Drives a realnode-ptysession through every prompt in §2, with real files written to a tempdir, and asserts the resulting compliance isPASS (strict). This is the test that runs the transcript verbatim.
Why this Feature is the hub
Counting: the typical Feature in this package links to two Requirements. The median is two. The mode is two. FEATURE-NEW-COMMAND links to seven — it is the most-connected node in the traceability graph. That is not a coincidence. The wizard is the DX front door, and DX is where every non-functional concern in the package converges:
- Bootstrap-zero-friction is the primary concern — the reason the Feature exists at all.
- Spec-is-source-of-truth is joint because the preview-before-write contract is what keeps the spec.json and the
.tsclass from diverging. - Editor-first-class is joint because the wizard emits
$schemain every generated spec.json, which is what makes VS Code autocomplete work. - Tui-modern is joint because the wizard is the single largest consumer of the Prompt port.
- Dsl-complete is joint because the satisfies-multiselect only works if the Requirements module is fully modelled.
- Parallel-deliverable is joint because the wizard is one of the Streams in the parallel-stream plan.
- Dog-food is joint because the wizard's own tests are written with
@FeatureTest/@Verifies, same as every other test in the package.
Seven Requirements, one Feature, eighteen ACs, zero manual glue. That is what a typed DX commitment produces.
Diagram 1 — wizard state diagram
The diagram below shows the states of the requirements feature new wizard as implemented in src/cli/feature-new-core.ts. Each state maps to one or more clack primitives; each transition is typed; the failing paths are the cancel exit at every point.
Alt text: State diagram of the requirements feature new wizard. States are Welcome, IdPrompt, TitlePrompt, PriorityPrompt, SatisfiesMultiselect, RequirementNewInline, AcLoop, AcNamePrompt, AcDocPrompt, ExpectsPrompt, Preview, ApplyReviewCancel, ReviewPerFile, Write, ComplianceVerify, EditorOpenPrompt. Ctrl-C exits cleanly from every state before Write. Compliance failure aborts the wizard with exit 1; compliance pass proceeds to the editor-open prompt.
Caption: Figure 1 — the wizard state machine. Every state corresponds to one clack primitive (intro, text, select, multiselect, note, confirm). Every transition is a typed event. The ctrl-c edge is the universal escape hatch — every state honours it, and the FileSystem port guarantees no partial writes because the first write only happens in the Write state, after ApplyReviewCancel has cleared.
Diagram 2 — watch-mode event flow
The diagram below shows one revalidation cycle in requirements watch, from the user's save to the green/red flip on their terminal. The sequence is what AC watchDetectsSpecChange and the latency-budget fitCriterion together pin in place.
Alt text: Sequence diagram of one requirements watch revalidation. User saves in VS Code; VS Code writes to the file system; the FileSystem port's watch() emits a change event; a 75 ms debounce collapses rapid successive saves; the analysis core partial-scans the affected feature and re-scans touching tests; compliance computes the delta; the TTY renders an ANSI-coloured summary line. Total budget is 500 ms p95. Schema violations preserve the previous compliance state.
Caption: Figure 2 — watch-mode revalidation lifecycle. The latency budget (500 ms p95) is a metric fitCriterion on REQ-LIVE-FEEDBACK; the debounce (75 ms) is the compromise between collapsing editor auto-saves and feeling live on a deliberate second edit. Every arrow is a testable boundary — the scripted FileSystem port lets unit tests simulate the change events without a real disk, and the node-pty e2e test drives the full chain with a real filesystem and a real terminal.
Running-example recap — TRACE-EXPLORER-TUI
The primary running example of the series is FEATURE-TRACE-EXPLORER-TUI. It appeared in §3 as the TUI-modern exemplar, and the shape of its transcript — arrow-key navigation, drilldown, help overlay, back-up, clean quit — is the shape every interactive command in the package aspires to. Its three @Satisfies links (REQ-DISCOVERABLE-TRACEABILITY, REQ-DOG-FOOD, REQ-PARALLEL-DELIVERABLE) are a more typical fan-in than the seven-link hub of FEATURE-NEW-COMMAND; the contrast between the two Features is the contrast between a specialist tool and a convergence point. This chapter featured the convergence point as its showcase; the running example carries the specialist's shape forward into the next chapters.
Related reading
- typed-specs-product/06-dx.md — DX as a typed commitment: the companion theory chapter that frames why these three Requirements exist at all.
- dont-burden-developers.md — the broader essay on developer burden: where friction compounds, where automation pays back, which categories of ceremony belong in spec vs. in code.
- 13-the-running-example-feature-trace-explorer-tui.md — the previous chapter: deep dive on the running example, its ACs, its tests, its port surface.
- 13c-when-compliance-fails-diagnostics-and-recovery.md — the next chapter: what happens when a commit breaks compliance, how the tooling diagnoses the failure, and how the wizard's
--replayflow is the recovery path. - 22b-ci-integration-and-merge-gates.md — how
compliance --strictwires into the merge gate, howwatchmode relates to pre-commit hooks, and why the non-interactive door (--yes --replay) is the CI seam.