Skip to content

usm/internal-dsl-builder [built] ​

Fluent typed TypeScript builder (internal DSL, per Fowler) that compiles to validated .usm YAML — defineFeature/defineService chains populate the semantic model and build() enforces the schema, exported from the package's public API.

Status: built

Why this exists ​

Authoring flows/contracts/tests in raw YAML is verbose and error-prone for humans and agents alike. Fowler's answer for this exact pain is an internal DSL: a fluent host-language builder that populates the semantic model and inherits the host's tooling (types, autocomplete, refactoring). LLMs write TypeScript far better than any bespoke grammar, so the builder aligns human ergonomics with agent capability. Constraints stay enforced by schema validation at build() — the YAML/JSON path remains first-class.

Design decisions ​

internal-over-external ​

Decision: Internal (host-language) DSL, not a custom grammar

Rationale: Fowler's cost test: inherit TS tooling and LLM fluency, skip parser maintenance forever. The semantic model (.usm) remains the asset.

runtime-schema-validation ​

Decision: Enforce constraints by validating at build() rather than type-level branding

Rationale: Keeps the fluent API simple and the JSON Schema the single source of truth; builders cannot silently bypass the same validation MCP tools enforce.

How it works ​

Compose a feature spec with the builder (build-feature) ​

  1. Call — defineFeature('org/slug', { system, service })
  2. Chain — .summary().intent().status().flow(id, fn).contract(id, fn).test(id, fn).decision(id, fn).seeAlso([])
  3. Build — returns { yaml, object, valid, errors } — schema-validated before hand-back

Write a built spec to disk (emit-and-write) ​

  1. Build — the spec via builder
  2. Write — writeFeature(result, path) — refuses invalid specs, atomic write

Extend an existing spec (extend-existing) ​

  1. Load — defineFeature from a parsed existing .usm file
  2. Mutate — chain additions (flows append by id)
  3. Rebuild — revalidate

Guarantees ​

builder-emits-valid-usm ​

Every build() result passes schema validation or reports errors explicitly

Acceptance criteria:

  • [ ] build() runs validateUsm and includes errors in the result
  • [ ] writeFeature refuses to write when valid is false
  • [ ] Generated YAML matches MCP write-tool serialization conventions

required-fields-enforced ​

The builder surface mirrors the schema's required fields

Acceptance criteria:

  • [ ] summary and intent required before a valid build
  • [ ] $system and $service required at construction
  • [ ] flows get steps; contracts get must_have; tests get expect

exported-public-api ​

The DSL ships as part of the package's public entry

Acceptance criteria:

  • [ ] import { defineFeature, defineService, writeFeature } from '@smithgray/usm' works
  • [ ] Types exported for IDE autocomplete

roundtrip-fidelity ​

Builder output parses back to the same object

Acceptance criteria:

  • [ ] yaml → parseUsm → deep-equals object

Test specifications ​

builds-valid-feature ​

Then:

  • assertion: chained builder builds with valid: true
  • assertion: yaml parses back deep-equal to object

invalid-build-reported ​

Given:

  • missing_intent: true

Then:

  • assertion: valid: false with schema errors listed
  • assertion: writeFeature refuses and writes nothing

extend-existing ​

Given:

  • existing_parsed_feature: true

Then:

  • assertion: loaded builder appends flows by id without dropping existing
  • assertion: rebuild validates

service-builder ​

Then:

  • assertion: defineService builds a schema-valid service with type and runtime

Implementation ​

  • Primary: src/dsl/index.ts
  • Test code: tests/dsl.test.ts
  • Test code status: manual

See Also ​

  • usm/mcp-write
  • usm/schema-v1
  • usm/query-layer