.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 imports —
importstatements that pull in other.xspecfiles (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
{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.
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, andtagsaccept plain quoted strings only — brace syntax (id={"login"}) is an error.daccepts 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
Whenmarkdown.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.
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.
{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:- The
importbringsBASEin scope so you can referenceBASE.authandBASE.auth.login. - The MDX comment is editorial — it won’t appear anywhere in the output.
overviewdeclares a dependency onBASE.auth, opts out of coverage targets, and embedsBASE.auth.login’s subtree text inline.lockoutcarries two tags and contains a self-closing childlockout.resetthat stubs a future requirement.