drft-cli 0.13.0

A structural integrity checker for linked file systems
Documentation
---
sources:
  - ../../src/rules/staleness.rs
  - ../../src/rules/structural.rs
  - ../../src/config.rs
---

# Rules

A rule is a function over the composed graph: graph in, findings out. `drft check`
runs every rule, joins the lockfile for the staleness rules, and emits findings as
warnings or errors.

Configure rules in `drft.toml` under `[rules]`. Every rule has a severity:
`"warn"`, `"error"`, or `"off"`. All rules default to `warn`; override to
`error` for CI enforcement or `off` to suppress. A finding's `subject` is the
implicated path (the source node for edge-level findings). Edge-level findings
also report the source `lines` where the link appears, annotating the subject in
text output as `subject:line → target`.

```toml
[rules]
stale-node = "error"
stale-edge = "error"
```

## Built-in rules

The rule set is deliberately drift-focused. [`staleness.rs`](../../src/rules/staleness.rs)
derives the drift findings by joining the graph to the lockfile;
[`structural.rs`](../../src/rules/structural.rs) derives the rest from graph shape.

| Rule              | When                                                           |
| ----------------- | -------------------------------------------------------------- |
| `stale-node`      | A node's current hash differs from its locked hash             |
| `stale-edge`      | An edge's locked target hash differs from the target's         |
| `new-edge`        | A current edge has no locked target hash                       |
| `removed-edge`    | The lockfile has an edge absent from the graph                 |
| `removed-node`    | The lockfile has a node absent from the graph                  |
| `unresolved-edge` | An edge target has no defining node (URIs excepted)            |
| `detached-node`   | A node has no inbound or outbound edges (directories excepted) |

`unresolved-edge` carries a `hint` when the link text would resolve from the
graph root but not from the declaring file. Links resolve relative to the file
that declares them, so a root-relative path fails against a target nobody wrote
and reads as a typo; the hint names the base as the cause and suggests the
rewrite. It is withheld for paths written `./`, `../`, or `/`, which are relative
by intent. The hint renders as an indented line under the finding in text output
and as a `hint` field in JSON.

Staleness is computed locally — per node and per edge, with no recursive
propagation — so dependency cycles can't loop or produce ambiguous staleness. A
stale node subsumes its outbound `stale-edge` findings; a removed node subsumes
its `removed-edge` findings.

## Per-rule configuration

```toml
[rules.detached-node]
severity = "off"

[rules.unresolved-edge]
ignore = ["CHANGELOG.md", "LICENSE"]
```

- `severity`: `"error"`, `"warn"`, or `"off"`
- `ignore`: globs matched against the finding's subject path

An `ignore` set directly under `[rules]` (rather than `[rules.<name>]`) applies to
every rule, unioned with each rule's own `ignore`:

```toml
[rules]
ignore = ["vendor/**"] # don't validate these files under any rule
```

This suppresses findings _about_ the matched files (their staleness, broken
internal links, detachment) but not findings about your files that depend on them
— a `stale-edge` whose subject is your file survives, since its subject isn't
matched. The files stay in the graph, so your links to them still resolve. This
is distinct from the top-level `ignore`, which removes paths from the graph
entirely.