with one click
rmc-architecture-rules
Enforce Rust crate-edge rules.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Enforce Rust crate-edge rules.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Preview a Rust symbol rename — exact reference set & refactor probe.
Audit a Rust crate's public API.
Audit Rust attributes and doc-comments.
Rust fn-level call graphs.
Task-conditioned workspace subgraph — nodes, edges, hierarchy.
Rust complexity hotspots by blast radius.
| name | rmc-architecture-rules |
| description | Enforce Rust crate-edge rules. |
| argument-hint | [rules-file-or-inline-rules] |
| allowed-tools | Read, Bash, mcp__rust-code-mcp__* |
Declarative crate-edge rule check. CI-friendly: rules are passed as a
list, the tool returns concrete violations. Empty violations is the
pass signal. Scope: workspace-wide.
For raw cross-crate edge inspection without rules, use
rmc-imports-exports. For sortable per-crate metrics (Robert Martin
instability), use rmc-dependency-metric.
build_hypergraph(directory=<absolute-path>)
Each rule has a glob-style consumer pattern and producer pattern
(with * wildcards), plus optional except (consumer-side override),
severity, and message:
rules = [
{ consumer: "domain*", producer: "tokio", severity: "error", message: "domain crates must be runtime-agnostic" },
{ consumer: "domain*", producer: "serde_json", severity: "warn" },
{ consumer: "domain*", producer: "reqwest", severity: "error" },
{ consumer: "domain*", producer: "hyper", severity: "error" },
{ consumer: "domain*", producer: "bevy*", severity: "error" },
]
Glob semantics: * matches zero or more characters in the crate name.
consumer="domain*" catches domain, domain_core, domain_types, etc.
forbidden_dependency_check(directory=..., rules=[...])
Returns:
{
"rule_count": 5,
"violation_count": 2,
"violations": [
{ "rule": { "consumer": "domain*", "producer": "tokio" },
"edge": { "consumer_crate": "domain_x", "producer_crate": "tokio" },
"sample_symbol": "tokio::spawn",
"unique_symbols": 5,
"total_refs": 17 }
]
}
One violation per (rule × matching edge). The tool is a pure filter over
crate_edges — same data, declarative shape, no extra graph cost.
For each violation:
read_file_content(file=<sample_call_site>) at the span (use
who_imports(target=<sample_symbol>) to surface the actual file:line).except clause to the rule (or narrow
the consumer glob) and re-run.For each layer pair where the lower layer must not consume from the upper:
forbidden_dependency_check(rules=[
{ consumer: "domain*", producer: "agent*", severity: "error" },
{ consumer: "domain*", producer: "tui*", severity: "error" },
{ consumer: "agent*", producer: "tui*", severity: "error" },
])
Empty violations confirms the layer DAG holds. Any non-empty result is
a layering break.
The domain crate must not import async runtimes:
forbidden_dependency_check(rules=[
{ consumer: "domain*", producer: "tokio", severity: "error" },
{ consumer: "domain*", producer: "futures", severity: "error" },
{ consumer: "domain*", producer: "async-*", severity: "error" },
])
Real example: domain imported tokio::sync::Mutex because a refactor
never finished — surfaced as a single violation with unique_symbols=1.
forbidden_dependency_check(rules=[
{ consumer: "domain*", producer: "bevy*", severity: "error" },
{ consumer: "domain*", producer: "reqwest", severity: "error" },
{ consumer: "domain*", producer: "hyper", severity: "error" },
{ consumer: "domain*", producer: "axum", severity: "error" },
{ consumer: "domain*", producer: "actix-web", severity: "error" },
])
| Question | Answer |
|---|---|
| Should this rule live in CI? | Yes — violation_count > 0 is a non-zero exit-code candidate. |
| Should it run in-IDE? | Cheap enough (filter over crate_edges) to run on every save. |
| How to handle "partial" rules? | Use except for consumer-side overrides (e.g. domain may import serde but not serde_json — express as two rules with the broader rule narrowed). |
| Multiple rules contradicting? | The check evaluates each rule independently — overlapping rules each produce their own violation rows. Keep rules orthogonal. |
| Use case | Rule shape |
|---|---|
| Layered DAG | consumer: "lower*", producer: "upper*" |
| Async-free domain | consumer: "domain*", producer: "tokio" |
| Framework-free domain | consumer: "domain*", producer: "bevy*" |
| Forbid binary→library reverse | consumer: "lib*", producer: "bin*" |
Rules: <n>
Violations: <m>
Severity breakdown: error <e>, warn <w>
Top offenders:
- domain_x → tokio: 5 unique symbols, 17 refs (error)
- ...
Verdict: <PASS | FAIL (e errors, w warnings)>
get_imports(module=...) and
hand-roll a check.crate_edges lists forward edges; the check is
filter-only. For cycles, walk crate_edges manually.total_refs. A consumer that imports a trait but only uses it via
method dispatch may register total_refs=0 while still violating the
rule.*-only — no ?, no character classes, no negation in the
pattern itself (use except for that).