con un clic
rmc-architecture-rules
Enforce Rust crate-edge rules.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Enforce Rust crate-edge rules.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
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).