> ## Documentation Index
> Fetch the complete documentation index at: https://xspec.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuring an xspec Project with xspec.config.ts

> Reference for xspec.config.ts: static-parsing rules, specs and code groups, markdown output, coverage profiles, policy rules, selectors, and glob syntax.

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:

```ts theme={null}
import { defineConfig } from "xspec"

export default defineConfig({ /* ... */ })
```

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

```ts theme={null}
import { defineConfig } from "xspec"

export default defineConfig({
  specs: {
    product: ["specs/product/**/*.mdx"],
    tests: ["specs/tests/**/*.mdx"]
  },
  code: {
    app: ["src/**/*.ts", "src/**/*.tsx"],
    tests: ["test/**/*.ts", "test/**/*.tsx"]
  },
  markdown: { emit: true, outDir: "build/md" },
  coverage: [
    {
      name: "product-tested",
      target: "product",
      boundary: "tests",
      boundaryKind: "code",
      mode: "direct"
    }
  ],
  policy: [
    {
      name: "product-depends-on-nothing",
      type: "forbidden",
      from: { group: "product" },
      to: { group: "tests", kind: "spec" }
    }
  ]
})
```

## `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`.

```ts theme={null}
specs: {
  product: ["specs/product/**/*.mdx"],
  tests:   ["specs/tests/**/*.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.

```ts theme={null}
code: {
  app:   ["src/**/*.ts", "src/**/*.tsx"],
  tests: ["test/**/*.ts", "test/**/*.tsx"]
}
```

<Warning>
  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.
</Warning>

## `markdown` — Markdown output (optional)

Controls compiled Markdown output from spec files.

```ts theme={null}
markdown: { emit: true, outDir: "build/md" }
```

| Field    | Required | Description                                                           |
| -------- | -------- | --------------------------------------------------------------------- |
| `emit`   | yes      | When `true`, compile each `NAME.mdx` to `NAME.md`                     |
| `outDir` | no       | Directory for compiled output; must resolve inside the workspace root |

## `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.

```ts theme={null}
coverage: [
  {
    name: "product-tested",
    target: "product",
    targetTags: ["p0"],
    targets: "leaves",
    boundary: "tests",
    boundaryKind: "code",
    mode: "direct",
    edgeKinds: ["depends", "embeds"]
  }
]
```

<ParamField path="name" type="string" required>
  A unique name for this coverage profile. Referenced in reports and CI output.
</ParamField>

<ParamField path="target" type="string" required>
  The name of the spec group whose nodes you want to measure.
</ParamField>

<ParamField path="targetTags" type="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.
</ParamField>

<ParamField path="targets" type="&#x22;leaves&#x22; | &#x22;all&#x22;">
  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.
</ParamField>

<ParamField path="boundary" type="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.
</ParamField>

<ParamField path="boundaryKind" type="&#x22;spec&#x22; | &#x22;code&#x22;">
  Specifies whether `boundary` refers to a spec group or a code group. Required when the boundary group name exists in both `specs` and `code`.
</ParamField>

<ParamField path="mode" type="&#x22;direct&#x22; | &#x22;transitive&#x22;" 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.
</ParamField>

<ParamField path="edgeKinds" type="Array<&#x22;depends&#x22; | &#x22;embeds&#x22; | &#x22;references&#x22;>">
  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.
</ParamField>

## `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.

```ts theme={null}
policy: [
  {
    name: "product-depends-on-nothing",
    type: "forbidden",
    from: { group: "product" },
    to: { group: "tests", kind: "spec" },
    kinds: ["depends"]
  }
]
```

| Field   | Required | Description                                                                                    |
| ------- | -------- | ---------------------------------------------------------------------------------------------- |
| `name`  | yes      | Unique rule name, used in violation reports                                                    |
| `type`  | yes      | `"forbidden"` — matched edges are violations; `"allowedOnly"` — unmatched edges are violations |
| `from`  | yes      | Selector for the source end of the edge                                                        |
| `to`    | yes      | Selector for the destination end of the edge                                                   |
| `kinds` | no       | Subset of `["depends", "embeds", "references"]`; omit to match all edge kinds                  |

## 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:

```ts theme={null}
{ group: "product" }
```

When the group name is ambiguous, add `kind` to disambiguate:

```ts theme={null}
{ group: "tests", kind: "code" }
```

**Files selector** — matches nodes whose source files match a glob:

```ts theme={null}
{ files: "src/legacy/**" }
```

**Tags selector** — matches spec nodes that carry at least one of the listed tags:

```ts theme={null}
{ tags: ["draft", "internal"] }
```

### 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`:

```ts theme={null}
{
  from: { files: "src/$1/**" },
  to:   { files: "specs/$1/**" }
}
```

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:

| Pattern | Matches                                            |
| ------- | -------------------------------------------------- |
| `*`     | Any sequence of bytes within a single path segment |
| `?`     | Exactly one byte within a single path segment      |
| `**`    | Any number of whole path segments (including zero) |

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.
