Skip to main content
Every xspec project is configured through a single xspec.config.ts file at the workspace root. This file declares your spec groups, code groups, Markdown output settings, coverage profiles, and policy rules. Understanding how xspec reads this file — and what it forbids — is the foundation for everything else in your project.

The static-parsing constraint

xspec parses xspec.config.ts statically and never executes it. This means the entire file must be purely declarative: only literal values are allowed. No spreads, no computed keys, no variables, no function calls (other than defineConfig itself), and no other statements besides the two required ones. Any unknown key anywhere in the config object causes an immediate exit-2 error. The required structure is:
You may alias defineConfig on import (import { defineConfig as dc } from "xspec"), but no other import forms are permitted. The defineConfig function is the identity function — it exists solely to give your editor type support. xspec never resolves the "xspec" import.

Full example

specs — spec groups (required)

specs is the only required field. It defines named groups of spec files using glob patterns. Every matched file must end in .mdx.
  • A file may belong to multiple groups — membership is not exclusive.
  • Group names are arbitrary strings and are referenced by name in coverage and policy entries.

code — code groups (optional)

code defines named groups of TypeScript source files. Code groups can serve as a boundary in coverage profiles.
A file matched by both a spec group and a code group is a build error. xspec also automatically excludes files with .xspec. in their name, files under .xspec/ directories, and files at Markdown output destinations from all discovery.

markdown — Markdown output (optional)

Controls compiled Markdown output from spec files.

coverage — coverage profiles (optional)

Each entry in the coverage array defines a named measurement profile: which spec nodes to measure, and what counts as covering them.
string
required
A unique name for this coverage profile. Referenced in reports and CI output.
string
required
The name of the spec group whose nodes you want to measure.
string[]
When provided, restricts measurement to nodes that carry at least one of the listed tags. Nodes without any of the listed tags are excluded from this profile’s targets.
"leaves" | "all"
Which nodes in the target group count as coverage targets. "leaves" (default) measures only leaf nodes — nodes with no spec children. "all" measures every node in the group.
string
required
The name of the spec or code group that is considered to “cover” target nodes. When boundaryKind is ambiguous (a group name exists in both specs and code), you must provide boundaryKind to disambiguate.
"spec" | "code"
Specifies whether boundary refers to a spec group or a code group. Required when the boundary group name exists in both specs and code.
"direct" | "transitive"
required
"direct" — a boundary node covers a target only if it has a direct edge to it. "transitive" — a boundary node covers a target if it can reach it through any chain of edges.
Array<"depends" | "embeds" | "references">
Restricts which edge kinds count toward coverage. When omitted, all edge kinds count. Provide a subset to narrow — for example, ["depends"] to count only explicit d prop relationships.

policy — policy rules (optional)

Policy rules enforce constraints on the dependency graph. Each rule either forbids certain edges or declares that only certain edges are allowed.

Selectors

Selectors appear as the from and to values in policy rules. Each selector is an object with one of the following shapes: Group selector — matches all nodes in a named spec or code group:
When the group name is ambiguous, add kind to disambiguate:
Files selector — matches nodes whose source files match a glob:
Tags selector — matches spec nodes that carry at least one of the listed tags:

Capture wildcards in files selectors

You can use $1, $2, … in a files pattern to capture a path segment and require it to match across from and to:
This matches only edges where the captured segment is the same in both paths — for example, an edge from src/auth/service.ts to specs/auth/login.mdx would match, but one from src/auth/service.ts to specs/payment/checkout.mdx would not.

Glob rules

Globs are used in specs, code, and files selectors. xspec evaluates them byte-wise and case-sensitively: Additional rules:
  • Segments whose names begin with . are only matched by patterns that explicitly start with . in that segment position.
  • xspec never follows symlinks during file discovery.
  • All glob matching is case-sensitive, including on case-insensitive file systems.