diff --git a/cli/check.mdx b/cli/check.mdx
index 0494992..e148b98 100644
--- a/cli/check.mdx
+++ b/cli/check.mdx
@@ -29,7 +29,7 @@ explicit multi-target configuration runs every target and produces one report.
| Argument | Description |
| --- | --- |
-| `paths...` | Optional. Use these paths instead of the configured `roots` for this run. |
+| `paths...` | Optional for Python and web targets. Use these paths instead of configured `roots`. Rust targets use Cargo discovery and reject positional paths. |
| `--warn` | Also evaluate and report rules configured in `warn`. Warnings never make the command fail. |
| `--no-color` | Disable ANSI color in the output. |
| `--cache` / `--no-cache` | Enable or disable persistent result caching for this run. |
@@ -124,7 +124,7 @@ configuration. Both take rule-code **prefixes**: `FF` is every core rule, `FFR`
family, `FFR3` a hundreds bucket, `FFR301` one rule; `X` is every custom rule,
`XDB` a custom namespace, `XDB001` one custom rule. Activated native packs use the
reserved `FP` namespace: `FPDG` selects the complete Dagster policy and `FPDG022`
-selects one Dagster-native rule. To narrow a run, adjust those keys rather than the
+selects one Dagster-native rule; `FPRS` selects the complete Rust policy. To narrow a run, adjust those keys rather than the
command:
```toml
diff --git a/concepts/analyzers/rust.mdx b/concepts/analyzers/rust.mdx
new file mode 100644
index 0000000..7156b01
--- /dev/null
+++ b/concepts/analyzers/rust.mdx
@@ -0,0 +1,152 @@
+---
+title: Rust
+description: "Configure native Cargo workspace analysis through fensu.toml and the normal Fensu CLI."
+---
+
+Fensu analyzes Rust projects natively. Cargo discovery, Rust parsing, workspace facts,
+and all Rust rules are owned by the `fensu-rust` analyzer and run through the normal
+`fensu` command. There is no separate Rust command or policy file.
+
+## Configure a workspace
+
+Add a named target to `fensu.toml`:
+
+```toml
+[targets.rust]
+analyzer = "rust"
+roots = ["crates"]
+tests = []
+tooling = []
+rule_packs = ["rust"]
+select = ["FPRS"]
+```
+
+`rule_packs = ["rust"]` and `select = ["FPRS"]` are the Rust defaults. Keeping
+them explicit makes the active policy clear in a multi-target repository.
+
+Then run:
+
+```bash
+fensu check --target rust
+```
+
+For a single Cargo package, use `roots = ["src"]`. If the Cargo project is below
+the repository root, set `root` to that directory. Target paths are relative to
+`root`.
+
+Cargo discovers workspace members, package names, library and binary targets, and
+integration-test targets automatically. You do not need to list crates unless you
+deliberately want a closed inventory. Rust targets reject positional paths because
+Cargo owns target discovery.
+
+## What it checks
+
+The `rust` pack exposes all 117 implemented rules under `FPRS`. The rules cover:
+
+- Cargo workspace and dependency policy;
+- module ownership, visibility, layout, and import boundaries;
+- file, function, naming, and error-handling hygiene;
+- function shape and coordination budgets; and
+- unit and integration-test structure and conventions.
+
+Use the normal selectors and policy tiers:
+
+```toml
+select = ["FPRS"]
+warn = []
+ignore = ["FPRSH001"]
+```
+
+Exact file exceptions and path-scoped ignores work as they do for other analyzers.
+Rust exceptions name a Rust source file or `Cargo.toml`; they do not use Python
+definition selectors. Inspect any rule and its effective settings with:
+
+```bash
+fensu rule FPRSS010 --target rust
+```
+
+## Rule options
+
+Adjust a rule under the target's standard `rule_options` table:
+
+```toml
+[targets.rust.rule_options.FPRSS010]
+max_arguments = 8
+
+[targets.rust.rule_options.FPRSR601]
+max_file_lines = 1500
+```
+
+The adjustable structural budgets retain these defaults:
+
+| Rule | Option | Default |
+| --- | --- | --- |
+| `FPRSR601` | `max_file_lines` | `2000` |
+| `FPRSS010` | `max_arguments` | `10` |
+| `FPRSS011` | `max_statements` | `70` |
+| `FPRSS001` | `max_statements` | `40` |
+| `FPRSS002` | `max_distinct_calls` | `20` |
+| `FPRSS003` | `max_locals` | `20` |
+| `FPRSR301` | `max_modules` | `10` |
+| `FPRSR302` | `max_modules` | `20` |
+
+Budgets must be positive integers. Unknown rule codes, option names, and invalid
+value types are rejected instead of being silently ignored.
+
+## Tooling and repository boundaries
+
+Use the target's normal `tooling` paths to identify tooling crates:
+
+```toml
+[targets.rust]
+analyzer = "rust"
+roots = ["crates"]
+tooling = ["crates/project-tools"]
+rule_packs = ["rust"]
+select = ["FPRS"]
+
+[targets.rust.rule_options.FPRSL301]
+forbidden_packages = ["project-tools"]
+```
+
+Tooling paths receive the tooling-layout rules. `forbidden_packages` prevents runtime
+crates outside those paths from depending on the named tooling packages and defaults
+to an empty list.
+
+Parser boundaries are also explicit and repository-neutral by default:
+
+```toml
+[targets.rust.rule_options.FPRSL102]
+packages = ["syntax-parser"]
+restricted_paths = ["rules"]
+remediation = "consume shared fact models instead of parser types"
+```
+
+`packages` defaults to an empty list and `restricted_paths` defaults to `["rules"]`.
+Dependency aliases are resolved when matching package identities.
+
+Closed inventories are optional:
+
+```toml
+[targets.rust.rule_options.FPRSL304]
+crate_names = ["app", "project-tools"]
+
+[targets.rust.rule_options.FPRSL305]
+domain_paths = ["crates/app/src/orders"]
+role_paths = ["crates/app/src/orders/main"]
+intentional_layout_paths = ["crates/app/src/generated"]
+```
+
+Empty crate, domain, and role lists use automatic discovery. Configured paths must
+be canonical repository-relative POSIX paths within the target root. Intentional-layout paths skip aggregate
+layout checks for explicit subtrees while preserving per-file checks.
+
+## Cargo metadata and caching
+
+Rust sources, Cargo manifests, an existing lockfile, and target configuration are
+cache inputs. Fensu uses locked dependency resolution when `Cargo.lock` exists.
+For a lock-free workspace, it reads declared dependencies without creating a
+lockfile.
+
+If Cargo metadata cannot be read, Fensu reports a setup diagnostic and does not cache
+the result. The result remains uncached even when rule selection hides that diagnostic.
diff --git a/concepts/configuration.mdx b/concepts/configuration.mdx
index a4490c9..6e5eac9 100644
--- a/concepts/configuration.mdx
+++ b/concepts/configuration.mdx
@@ -57,7 +57,7 @@ max_positional_args = 1
## Named analyzer targets
The flat form above remains the concise configuration for one Python project.
-Use explicit targets for multiple projects or a TypeScript/Svelte analyzer:
+Use explicit targets for multiple projects or a Rust, TypeScript, or Svelte analyzer:
```toml
[targets.backend]
@@ -75,6 +75,14 @@ tests = ["tests"]
test_layout = "mirrored"
rule_packs = ["sveltekit"]
select = ["FPSK"]
+
+[targets.rust]
+analyzer = "rust"
+roots = ["crates"]
+tests = []
+tooling = ["crates/project-tools"]
+rule_packs = ["rust"]
+select = ["FPRS"]
```
Each target requires `analyzer` and `roots`. `root` defaults to `.` and relocates
@@ -82,7 +90,7 @@ that target's paths relative to the configuration directory. Explicit `targets`
cannot be mixed with legacy flat keys. `fensu check` evaluates every configured
target unless `--target NAME` selects one.
-The registered analyzers are `python`, `typescript`, and `svelte`. Web-only keys
+The registered analyzers are `python`, `rust`, `typescript`, and `svelte`. Web-only keys
include `test_layout`, `ui_kit`, `shadcn`, `openapi`, and `generated`; the normal
policy, threshold, exception, evaluation, cache, and scope keys live inside each
target too. The activated rule pack, rather than a separate framework setting,
@@ -90,6 +98,10 @@ controls SvelteKit policy. See
[TypeScript and SvelteKit](/concepts/analyzers/typescript-sveltekit) for detection,
setup, and current boundaries.
+Rust targets use Cargo to discover workspace members, package identities, and source targets.
+They are configured entirely in `fensu.toml`; use ordinary `rule_options` for per-rule
+adjustments. See [Rust](/concepts/analyzers/rust) for setup and the supported options.
+
## Validation is strict
Fensu fails loudly on a bad config rather than silently ignoring it, because a
@@ -163,6 +175,7 @@ prefix of a valid code works:
| `FP` | Every activated native-pack rule. |
| `FPTS` | The complete activated TypeScript policy. |
| `FPSK` | The complete activated SvelteKit policy. |
+| `FPRS` | The complete activated Rust policy. |
| `FPDG` | The complete activated Dagster policy. |
| `FPDGA` | Dagster aliases of the core annotations family. |
| `FPDG022` | Exactly one Dagster-native policy. |
@@ -176,14 +189,16 @@ see [Rule families](/concepts/rule-families). `FP` is reserved for activated
`X` plus optional uppercase letters plus optional digits, so namespaces nest by
prefix (see [Custom rules](/concepts/custom-rules#codes-and-namespaces)). Web
analyzers use the activated `FPTS` or `FPSK` catalogue described in
-[TypeScript and SvelteKit](/concepts/analyzers/typescript-sveltekit).
+[TypeScript and SvelteKit](/concepts/analyzers/typescript-sveltekit). Rust uses the
+activated `FPRS` catalogue described in [Rust](/concepts/analyzers/rust).
### The default
-`select` defaults to `["FF"]` for Python and `["FP"]` for TypeScript or Svelte.
-For web targets, `FP` selects every activated pack. A web pack must be named in
-`rule_packs` before its selectors become available; generated SvelteKit targets
-activate `sveltekit` and select `FPSK` explicitly.
+`select` defaults to `["FF"]` for Python and `["FP"]` for Rust, TypeScript, or Svelte.
+For native targets, `FP` selects every activated pack. Rust targets activate the
+`rust` pack automatically. Web packs must be named in `rule_packs` before their
+selectors become available; generated SvelteKit targets activate `sveltekit` and
+select `FPSK` explicitly.
### Run only some families
diff --git a/concepts/rule-packs.mdx b/concepts/rule-packs.mdx
index 427d747..7b1470b 100644
--- a/concepts/rule-packs.mdx
+++ b/concepts/rule-packs.mdx
@@ -35,8 +35,8 @@ pack's reference page.
## Codes and namespaces
Fensu reserves `FP` for shipped native packs. Each pack owns a namespace beneath
-that prefix. The Dagster, TypeScript, and SvelteKit packs own `FPDG`, `FPTS`, and
-`FPSK`.
+that prefix. The Dagster, Rust, TypeScript, and SvelteKit packs own `FPDG`, `FPRS`,
+`FPTS`, and `FPSK`.
Pack-native rules append their numeric identity directly, such as `FPDG022`.
Retained core aliases preserve the core family and number after the pack namespace:
@@ -79,4 +79,8 @@ alias target for an activated identity.
Framework-independent TypeScript policy and a standalone SvelteKit policy
with transparent TypeScript aliases.
+
@@ -30,7 +30,7 @@ can tell you, deterministically and every time, when the repo has drifted from i ## What Fensu checks -Fensu is an architecture linter for Python repos. It analyzes your source and +Fensu is an architecture linter for Python, Rust, TypeScript, and Svelte projects. It analyzes your source and reports **faults**, the places where the code has broken away from the architecture you declared. @@ -61,12 +61,18 @@ dataclass-backed parametrization) and annotation conventions (annotate every parameter and return, plus non-scalar local bindings) in the scopes where those apply. See [Rule families](/concepts/rule-families) for the full set. +Python uses Fensu's core `FF` policy. Rust, TypeScript, SvelteKit, and framework-specific +Python projects use native rule packs selected per analyzer target. See +[Rust](/concepts/analyzers/rust), +[TypeScript and SvelteKit](/concepts/analyzers/typescript-sveltekit), and +[Native rule packs](/concepts/rule-packs). + Fensu ships a **coherent default architecture**, not a blank canvas. You adopt it and get a sensible starting point, then disable rules, tune thresholds, activate a framework-specific native policy, or add your own rules deliberately once you know what you are doing. -The default lays out code as domains built from a small set of roles. +The Python default lays out code as domains built from a small set of roles. A domain holds those roles directly, or splits into named subdomains that do: ```text