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

Intent

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.

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.

Flows

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

Contracts

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

Tests

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