Skip to main content
Spec files are standard MDX documents with a .mdx extension, saved as valid UTF-8 with no byte-order mark. xspec defines a small, strict subset of MDX constructs you can use — anything outside that subset is a build error. This constraint keeps spec files machine-readable and ensures the dependency graph stays fully static.

Allowed constructs

Inside a spec file you may use:
  • Spec importsimport statements that pull in other .xspec files (covered in Dependencies)
  • <S> / <Spec> sections — the primary structural element (synonyms; <S> is preferred)
  • {text(…)} embeddings — splices another requirement’s text inline
  • MDX comments{/* … */} for editorial notes that won’t appear in output
  • Ordinary Markdown — headings, paragraphs, lists, tables, fenced code blocks, and so on
Everything else — arbitrary JSX elements, general {expression} interpolations, export statements — is forbidden and causes an exit-2 error.

The <S> section tag

The <S> tag (or its alias <Spec>) marks a named requirement node. Each section becomes a node in the dependency graph, identified by its id.
A self-closing form reserves an ID as an empty leaf node without any prose:
Use the self-closing form to stub out a requirement you plan to fill in later — the ID is immediately available as a dependency target.

The implicit root node

Every spec file has an implicit root node identified by the file’s path. Content and <S> sections written at the top level of the file are owned by that root. You never declare the root node explicitly.

Nesting

Place <S> sections inside one another to model a requirement hierarchy. The nesting relationship is recorded as parent–child edges in the graph.
Every non-root section must have an id prop. Omitting id on a non-root <S> is a build error.

Props reference

xspec recognises exactly four props on <S>. Any unknown prop name is an error. Syntax rules:
  • id, coverage, and tags accept plain quoted strings only — brace syntax (id={"login"}) is an error.
  • d accepts a braced expression only — a quoted string is an error.
  • Spread attributes are not allowed.
  • Repeating the same prop on one element is an error.

Markdown compilation

When markdown.emit: true is set in your config, xspec compiles each NAME.mdx spec to NAME.md. The compiled output is pure Markdown: no JSX, no xspec-specific syntax. During compilation:
  • Spec imports are removed entirely.
  • <S> opening and closing tags are stripped; their content remains.
  • MDX comments ({/* … */}) are removed.
  • {text(…)} expressions are replaced by the expanded subtree text of the target node.
  • Everything else is preserved byte-for-byte.
  • Any line that becomes empty purely as a result of removals is dropped.
Use MDX comments freely for notes to your team — they disappear completely from compiled Markdown and never appear in the dependency graph.

Own text vs subtree text

xspec distinguishes two text views for every node:
  • Subtree text — the node’s full contribution to compiled Markdown: its own prose with all descendant sections interleaved in document order.
  • Own text — the same content but with child section contributions excised, leaving only the prose directly written inside that node.
This distinction matters for hashing and {text(…)} embeddings. When you embed a node with {text(BASE.auth.login)}, you get that node’s subtree text. When xspec computes whether a node’s own content has changed, it uses own text so that editing a child doesn’t change the parent’s own hash.

Complete example

The following file demonstrates every allowed construct together:
Breaking it down:
  • The import brings BASE in scope so you can reference BASE.auth and BASE.auth.login.
  • The MDX comment is editorial — it won’t appear anywhere in the output.
  • overview declares a dependency on BASE.auth, opts out of coverage targets, and embeds BASE.auth.login’s subtree text inline.
  • lockout carries two tags and contains a self-closing child lockout.reset that stubs a future requirement.