Part 22 — Composing the Requirements IDE: case study end-to-end
The series closes by composing — at the level of a written plan, not running code — the smallest useful IDE for the Requirements DSL. The same DSL the meta-ide-dsl prototype targeted as one mixed-decorator class is now expressed as a kernel plus six micro-DSLs plus two hosts. The compose list is deliberately minimal: this is what suffices to ship a working extension; everything else is opt-in. Eight packages, one consumer-side package.json declaring them as dependencies, one sourcegen.config.ts orchestrating extraction-and-emit, one bundled .vsix folder ready for vsce package.
This article walks the composition end to end. It names which micro-DSL contributes what to the Requirements IDE; it traces the user flow from opening a .req.ts file to clicking a CodeLens to triggering a regenerate command; it names the dog-food loop (the series' own acceptance-criteria assets are written in the Requirements DSL the IDE serves); it bridges backward to meta-ide-dsl article 07 (the prototype reframed as one specific composition) and forward to ide-dsl article 01 (the build series that picks up where this one stops, starting the implementation with the decomposition pinned).
The compose list
// consumer's package.json (excerpt)
{
"name": "requirements-ide",
"version": "0.1.0",
"engines": { "vscode": "^1.85.0" },
"dependencies": {
"@frenchexdev/ide-dsl-kernel": "^1.0.0",
"@frenchexdev/ide-dsl-language": "^1.0.0",
"@frenchexdev/ide-dsl-syntax": "^1.0.0",
"@frenchexdev/ide-dsl-snippets": "^1.0.0",
"@frenchexdev/ide-dsl-completion": "^1.0.0",
"@frenchexdev/ide-dsl-hover": "^1.0.0",
"@frenchexdev/ide-dsl-codelens": "^1.0.0",
"@frenchexdev/ide-dsl-generator": "^1.0.0",
"@frenchexdev/ide-dsl-lsp-host": "^1.0.0",
"@frenchexdev/ide-dsl-extension-host": "^1.0.0",
"@frenchexdev/ts-codegen-pipeline": "^0.1.0"
}
}// consumer's package.json (excerpt)
{
"name": "requirements-ide",
"version": "0.1.0",
"engines": { "vscode": "^1.85.0" },
"dependencies": {
"@frenchexdev/ide-dsl-kernel": "^1.0.0",
"@frenchexdev/ide-dsl-language": "^1.0.0",
"@frenchexdev/ide-dsl-syntax": "^1.0.0",
"@frenchexdev/ide-dsl-snippets": "^1.0.0",
"@frenchexdev/ide-dsl-completion": "^1.0.0",
"@frenchexdev/ide-dsl-hover": "^1.0.0",
"@frenchexdev/ide-dsl-codelens": "^1.0.0",
"@frenchexdev/ide-dsl-generator": "^1.0.0",
"@frenchexdev/ide-dsl-lsp-host": "^1.0.0",
"@frenchexdev/ide-dsl-extension-host": "^1.0.0",
"@frenchexdev/ts-codegen-pipeline": "^0.1.0"
}
}Eleven dependencies. Eight are micro-DSL packages from this series (kernel + six micro-DSLs + the LSP host + the Extension host). One is the source generator pipeline. The Custom Editor host (article 21) is not in this minimal list — the Requirements DSL ships in this composition with text-only editing through the standard VSCode text editor. Adding the Custom Editor host plus the Projection micro-DSL is opt-in for consumers who want the multi-notation editing surface.
The justification per micro-DSL inclusion:
- Kernel — non-optional. The M2 decorators, AST, PatchBus, Banner, EditLog underlie everything else.
- Language — declares
requirementsas the language id,.req.tsas its extension,source.ts.requirementsas the TextMate scope. Required for any IDE behaviour at all. - Syntax — declares the highlighting tokens. Required for the visual presentation.
- Snippets —
feat,ac,ftestprefixes for the common boilerplate. Quality-of-life essential. - Completion — context-aware suggestion of
Requirementsubclasses after@Satisfies(, ofPriority.*enum values afterpriority: '. Quality-of-life essential. - Hover —
Requirements.FEATURE-XXXresolves to the canonical declaration's title and ACs. Cross-file resolution is the IDE's most-cited feature; required for "this references something useful" behaviour. - CodeLens —
N satisfying testsandM implementationsover each Feature declaration. Required for at-a-glance status; this is what makes the Requirements DSL a tracking tool rather than just a typing exercise. - Generator — emits
compliance.json,features.index.ts, per-Feature validators. Required because the existingcompliance-coreconsumer reads what the Generator writes; without it, the CMF dog-food loop is broken.
Six excluded micro-DSLs and the reason each is left out of this minimal composition:
- Diagnostics — the constraint validators are useful but not strictly required for shipping the IDE; the kernel still validates
@Property({ constraint })at extraction time, just without per-edit squiggles. Adding Diagnostics is one line inpackage.json. - Commands — the Commands the Generator and CodeLens reference are implicitly registered through the
ide-dsl-extension-host's default-command set. Explicit Commands declarations become useful when the consumer wants custom commands beyond the defaults. - Views — the workspace tree is useful but not required; users navigate through
Cmd+T(workspace symbols) instead. Adding Views is opt-in. - Formatter — text in
.req.tsfiles is already TypeScript and formatted by Prettier or the user's TypeScript formatter; a Requirements-specific Formatter is overkill for a pure-text composition. The Formatter becomes essential when the Custom Editor host is added (text projection needs canonical layout). - Symbols — workspace symbol navigation is provided through the kernel's index even without an explicit Symbols contribution; ts-morph populates the index via the
findDecoratedwalker. Explicit Symbols declarations become useful for richer outline customisation. - Refactoring — rename refactorings are valuable but not strictly required for shipping; users can rename through the standard TypeScript rename refactoring (which works because
.req.tsfiles are TypeScript files). Explicit Refactoring declarations become useful for DSL-aware renames that update@ReferenceLinktargets across files.
The exclusions document the suite's graceful-degradation property. A composition with fewer micro-DSLs ships a less-featured but still working IDE. Each addition is incremental; the minimum is small.
Package map
The dependency graph for the Requirements IDE composition:
@frenchexdev/ide-dsl-kernel
(Concept, AST, PatchBus, Banner, EditLog)
^
|
+----------+----------+----------+----------+----------+----------+----------+
| | | | | | | |
language syntax snippets completion hover codelens generator ts-codegen-
microdsl microdsl microdsl microdsl microdsl microdsl microdsl pipeline
^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | |
| (lsp-host imports completion, hover, codelens) | |
| (extension-host imports language, syntax, snippets) | |
| | |
| (consumer's sourcegen.config.ts imports generator + pipeline) |
| |
| requirements-ide consumer package
+----- imports all of the above + lsp-host + extension-host ---------------+ @frenchexdev/ide-dsl-kernel
(Concept, AST, PatchBus, Banner, EditLog)
^
|
+----------+----------+----------+----------+----------+----------+----------+
| | | | | | | |
language syntax snippets completion hover codelens generator ts-codegen-
microdsl microdsl microdsl microdsl microdsl microdsl microdsl pipeline
^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | |
| (lsp-host imports completion, hover, codelens) | |
| (extension-host imports language, syntax, snippets) | |
| | |
| (consumer's sourcegen.config.ts imports generator + pipeline) |
| |
| requirements-ide consumer package
+----- imports all of the above + lsp-host + extension-host ---------------+Eleven nodes, no cycles, dependencies all pointing toward the kernel and the pipeline at the foundation. Replacing any single micro-DSL with an alternative implementation is a package.json change in the consumer; nothing else moves.
End-to-end walk-through
The user opens feature-checkout.req.ts:
- Activation — VSCode reads the consumer's
package.json, finds the language declaration forrequirements, the grammar declaration forsource.ts.requirements, the snippet declaration forrequirements, the activation events for the registered commands. The Extension host's generatedextension.tsactivates: it starts the LSP host as a child process, registers the language server client, registers the snippet contributions, registers the command handlers. - Highlighting — VSCode applies the TextMate grammar emitted by the Syntax micro-DSL. Decorator names, priority literals, feature ids show their declared scopes; the rest falls through to the inherited
source.tsgrammar. - The user types
@Sat— the editor sendstextDocument/completion. The LSP host routes to the Completion micro-DSL plus the Snippets micro-DSL. Snippets returns nothing (the prefix matches no snippet). Completion recognises the@Satpartial as a completion candidate for the@Satisfiesdecorator and proposes it. The user accepts; the editor inserts@Satisfies(. The trigger character(fires another completion request; the Completion micro-DSL returns the list ofRequirementsubclasses; the user picks one; the import statement is added byadditionalTextEdits. - The user hovers on
Requirements.FEATURE-156— VSCode sendstextDocument/hover. The LSP host routes to the Hover micro-DSL. Hover's pattern matcher recognises the reference shape, queries the kernel's workspace index forFEATURE-156, finds the canonical declaration infeature-cart.req.ts, formats a markdown summary (title, priority, ACs), returns it. VSCode renders the tooltip. - A CodeLens above the Feature shows "3 satisfying tests" — the editor sends
textDocument/codeLens. The LSP host routes to the CodeLens micro-DSL. The lens's count callback queries the kernel's workspace index for@FeatureTestdecorators referencingFEATURE-156, finds three, formats the title. The lens is clickable and references therequirements.runSatisfyingTestscommand (registered by the Extension host's default command set). - The user clicks the CodeLens — VSCode invokes the command. The handler walks the workspace for the relevant test files, runs
npx vitest runagainst them, surfaces the output in a VSCode terminal. - The user invokes
Cmd+Shift+P → Requirements: Regenerate— the command dispatches to the Generator micro-DSL. The Generator invokesrunFixpointfromts-codegen-pipelineagainst the consumer'ssourcegen.config.ts. The pipeline walks the AST, emitsFeatureValidator.tsfiles (banner-stamped), updatescompliance.json(banner-stamped JSON variant), updatesfeatures.index.ts. Files unchanged-by-content are not rewritten (Banner idempotence). The user sees a status notification; the generated files appear (or stay unchanged) in the workspace.
Every step in the trace is one micro-DSL doing its job. The host coordinates, the micro-DSLs author, the kernel underlies. No micro-DSL needed to know about another; the user-visible behaviour is the union of small contributions.
The dog-food loop
The series' own acceptance-criteria assets — content/blog/microdsls-and-kernel/assets/features.ts — are themselves written in the Requirements DSL. Twenty-two Feature classes, one per article in this series, each with abstract acceptance-criterion methods, each with @Satisfies(...) linking to a Requirement. The series tracks itself through the same DSL it designs an IDE for. Reading features.ts with the Requirements IDE composed in this article would surface — through the LSP host, through Hover, through CodeLens, through every micro-DSL named here — the cross-references between the articles and the design's own ACs.
The loop closes one more turn: the Generator micro-DSL, invoked on features.ts, would emit a compliance.json listing the series' ACs and their satisfaction status. That JSON is the same shape the existing compliance-core package consumes for the rest of the monorepo. The IDE generates the data; the existing tooling reads it; the same dog-food relationship the closing-the-loop series describes for tests applies here for design.
Bridge back to meta-ide-dsl article 07
The meta-ide-dsl prototype — one class with six decorator families — is one specific composition of this suite. Reading the prototype now, after twenty-two articles of decomposition, every decorator there maps to one of the micro-DSLs:
@Language(...)→ the Language micro-DSL (article 06)@Token(...)→ the Syntax micro-DSL (article 07)@Rule(...)→ folded into the Syntax micro-DSL via grammar inheritance fromsource.ts@Snippet(...)→ the Snippets micro-DSL (article 09)@LspFeature('hover'),@LspFeature('completion'),@LspFeature('diagnostics'),@LspFeature('definition')→ the corresponding micro-DSLs from articles 08, 10, 11, plus Symbols (article 16) for definition@Executor(...)→ the Commands micro-DSL (article 13)
The prototype's choice — put all six in one class — is a perfectly reasonable engineering choice for a single-DSL, single-team starting point. Article 01 made the case for when that choice stops scaling. The fourteen micro-DSLs of Part III are what scaling looks like.
Bridge forward to ide-dsl article 01
The ide-dsl/ series (the build counterpart of meta-ide-dsl) is fifteen articles walking the implementation of the meta-DSL — the source generators, the extractors, the language IR, the LSP server, the manifest emitter, the snippet emitter, the extension entry. As of this writing eight of the fifteen articles are written; the remaining seven are planned in toc.json. This series is the architectural bridge between the two: meta-ide-dsl's design is one composition; ide-dsl's build will adopt the full decomposition described here. When ide-dsl article 09 picks up where article 08 leaves off, the package boundaries for the kernel and the eight micro-DSLs from the build's scope will be the boundaries this series named.
Three things the ide-dsl series will inherit from this one and not need to re-justify:
- The kernel surface. The five concepts (M2 decorators, AST + NodeId, PatchBus, Banner, EditLog) are named in articles 02, 04, 05.
- The dependency direction. The kernel ← micro-DSL ← host theorem from article 03 is the load-bearing constraint the build series will hold.
- The contribution interface. The
MicroDslContribution<T>shape that the LSP host (article 20) and the Custom Editor host (article 21) consume is the kernel's primary external API; the build series will design its TypeScript shape but not its semantics.
What the ide-dsl series adds, which this series deliberately deferred:
- The actual TypeScript code for the kernel, the micro-DSLs, the hosts.
- The integration tests verifying the dependency direction holds, the kernel exports stay small, the contribution discovery works.
- The release engineering for fifteen separately-published packages with coordinated kernel-major releases.
- The publication path for the example
.vsix.
The two series therefore form a deliberate pair. The design pins the architecture; the build implements it. Reading them in order — meta-ide-dsl design (the prototype), microdsls-and-kernel (this series, the decomposition), ide-dsl build (the implementation) — is the linear path from idea to artefact.
What this article verifies
This article verifies the five acceptance criteria of FEAT-MICRODSL-22 declared in assets/features.ts:
- minimalMicroDslComposeListEnumerated — the Compose list section names eight kernel-or-micro-DSL packages, justifies each inclusion, names the six exclusions and their justification.
- walkThroughKernelToVscodeArtefactsShown — the End-to-end walk-through section traces seven user flows from open to regenerate, naming which micro-DSL handles each.
- dogFoodLoopWithRequirementsDslMadeExplicit — the Dog-food loop section names that the series' own ACs are Requirements-DSL Features and that the same
compliance-coreconsumes the generated output. - bridgeBackToMetaIdeDslArticleSevenStated — the Bridge back section maps the prototype's six decorators to the new micro-DSLs and names the architectural relationship.
- bridgeForwardToIdeDslBuildSeriesStated — the Bridge forward section names what the build series inherits from this one and what it adds.
End of series
Twenty-two articles, the same number of Feature classes in assets/features.ts, one decomposition pinned. The next move belongs to the ide-dsl/ build series — and ultimately to the implementation that will eventually live in packages/ide-dsl-*/. This series argued, in fifty thousand words of dense prose, that an IDE meta-DSL is best designed as a small kernel and a suite of bounded contexts composed at consumption time. The argument is now public; the design is now a corpus other people can hold to account.