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

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"
  }
}

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 requirements as the language id, .req.ts as its extension, source.ts.requirements as the TextMate scope. Required for any IDE behaviour at all.
  • Syntax — declares the highlighting tokens. Required for the visual presentation.
  • Snippetsfeat, ac, ftest prefixes for the common boilerplate. Quality-of-life essential.
  • Completion — context-aware suggestion of Requirement subclasses after @Satisfies(, of Priority.* enum values after priority: '. Quality-of-life essential.
  • HoverRequirements.FEATURE-XXX resolves 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.
  • CodeLensN satisfying tests and M implementations over 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 existing compliance-core consumer 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 in package.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.ts files 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 findDecorated walker. 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.ts files are TypeScript files). Explicit Refactoring declarations become useful for DSL-aware renames that update @ReferenceLink targets 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  ---------------+

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:

  1. Activation — VSCode reads the consumer's package.json, finds the language declaration for requirements, the grammar declaration for source.ts.requirements, the snippet declaration for requirements, the activation events for the registered commands. The Extension host's generated extension.ts activates: it starts the LSP host as a child process, registers the language server client, registers the snippet contributions, registers the command handlers.
  2. 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.ts grammar.
  3. The user types @Sat — the editor sends textDocument/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 @Sat partial as a completion candidate for the @Satisfies decorator and proposes it. The user accepts; the editor inserts @Satisfies(. The trigger character ( fires another completion request; the Completion micro-DSL returns the list of Requirement subclasses; the user picks one; the import statement is added by additionalTextEdits.
  4. The user hovers on Requirements.FEATURE-156 — VSCode sends textDocument/hover. The LSP host routes to the Hover micro-DSL. Hover's pattern matcher recognises the reference shape, queries the kernel's workspace index for FEATURE-156, finds the canonical declaration in feature-cart.req.ts, formats a markdown summary (title, priority, ACs), returns it. VSCode renders the tooltip.
  5. 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 @FeatureTest decorators referencing FEATURE-156, finds three, formats the title. The lens is clickable and references the requirements.runSatisfyingTests command (registered by the Extension host's default command set).
  6. The user clicks the CodeLens — VSCode invokes the command. The handler walks the workspace for the relevant test files, runs npx vitest run against them, surfaces the output in a VSCode terminal.
  7. The user invokes Cmd+Shift+P → Requirements: Regenerate — the command dispatches to the Generator micro-DSL. The Generator invokes runFixpoint from ts-codegen-pipeline against the consumer's sourcegen.config.ts. The pipeline walks the AST, emits FeatureValidator.ts files (banner-stamped), updates compliance.json (banner-stamped JSON variant), updates features.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 from source.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:

  1. The kernel surface. The five concepts (M2 decorators, AST + NodeId, PatchBus, Banner, EditLog) are named in articles 02, 04, 05.
  2. The dependency direction. The kernel ← micro-DSL ← host theorem from article 03 is the load-bearing constraint the build series will hold.
  3. 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-core consumes 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.

⬇ Download