Dependency findings
Dependency findings consume the import graph that crimes builds
once per scan. They flag architecture-shaped problems that
file-by-file detectors can’t see: cycles, deep cross-package reaches,
modules with too many neighbours, and architecture.layers rule
violations.
For the wire format, see docs/json-schema.md.
For the agent workflow that consumes findings, see
docs/agent-usage.md.
What ships
Section titled “What ships”Finding.type | Charge | Severity range | Confidence |
|---|---|---|---|
layer_violation | Layer Border Crossing | medium-high | 0.85 |
circular_dependency | Tangled Imports | medium-high | 0.90 |
deep_import | Deep Import Abuse | low-medium | 0.70-0.85 |
high_fan_in_fan_out | Crowded Module | low-medium | 0.70-0.80 |
All four use the standard Finding shape and add an imports_limited
flag to ScanReport when the graph hit its performance budget — same
shape as HotspotsReport.history_limited.
Layer Border Crossing (layer_violation)
Section titled “Layer Border Crossing (layer_violation)”What it detects. An import that crosses a forbidden boundary in
crimes.config.json architecture.layers + architecture.rules.
The config defines named layers by file glob and explicit
from → cannotImport rules; the detector emits one finding per
imported file that violates a rule.
Example evidence.
file src/ui/PricingPage.tsx imports src/db/billing.tslayer "ui" cannotImport from "db"matching rule: { from: "ui", cannotImport: ["db", "infrastructure"] }Why it matters. Architecture rules are usually enforced
informally — a tribal “UI components don’t talk to the database”
norm. Coding agents have no access to that norm, so the first edit
that pulls a db query into a component looks reasonable in isolation
and gets merged. Encoding the rule in crimes.config.json makes the
boundary review-able.
Configuration knobs. architecture.layers[].name /
architecture.layers[].pattern (glob, e.g. "src/ui/**") and
architecture.rules[].from / architecture.rules[].cannotImport.
Layers default to “no rules”, so adding the section is opt-in. See
docs/configuration.md.
Tangled Imports (circular_dependency)
Section titled “Tangled Imports (circular_dependency)”What it detects. Strongly-connected components in the import
graph with ≥ 2 files. A 2-file cycle (a.ts ↔ b.ts) is flagged at
medium severity; ≥ 3 files at high severity.
Example evidence.
2-file cycle: src/billing.ts → src/coupon.ts → src/billing.tsimports at: billing.ts:14 → coupon.ts; coupon.ts:7 → billing.tsWhy it matters. Circular imports usually mean the two files share too much state. Beyond bundler quirks (TDZ errors, hoisting oddities), the cycle is a code-review smell: every PR that touches either file has to consider the other. Breaking the cycle into a shared types module or a one-way dependency makes the relationship explicit.
Suggested fix. Identify the genuinely shared declarations (types, constants) and move them to a third file both can import. If the cycle is between sibling modules, consider whether one should own the other rather than peer with it.
Deep Import Abuse (deep_import)
Section titled “Deep Import Abuse (deep_import)”What it detects. Imports that reach more than n segments deep
into another package’s source tree — typically
@org/pkg/src/internal/util/format. The detector applies to
workspace-relative imports too.
Example evidence.
import path: ../../../../core/src/internal/format6 segments deepcrosses workspace boundary into packages/coreWhy it matters. Deep imports bypass a package’s public surface and pin the consumer to internal implementation details. The next refactor of the internal file breaks every consumer that reached in, even though nothing about the package’s public API changed.
Suggested fix. Add the function to the package’s public
index.ts and import it from there. If the function isn’t ready to
be public, the import was probably premature — find a cheaper
surface to depend on.
Crowded Module (high_fan_in_fan_out)
Section titled “Crowded Module (high_fan_in_fan_out)”What it detects. Files with unusually high fan-in (number of
files importing this one) and/or fan-out (number of files this one
imports). Threshold is calibrated to mid-size repos; very large
monorepos can lift it via crimes.config.json.
Example evidence.
fan-in: 47 files import this modulefan-out: 14 files imported heretop-3 importers: src/billing.ts, src/account.ts, src/team.tstop-3 imports: src/util/format.ts, src/clock.ts, src/log.tsWhy it matters. High fan-in modules are the blast-radius
hotspots — any edit ripples across many consumers, so getting them
right is disproportionately important. High fan-out modules are
glue: when they break, downstream work stops. Either signal lifts
the file’s scores.blast_radius in the unified agent_risk
formula.
Suggested fix. For high fan-out, consider splitting the module
along its natural seams — the imports usually cluster by concern.
For high fan-in, there’s often no action: it’s the legitimate seam
your codebase reached for. The finding is informational; tune the
threshold in crimes.config.json if your repo’s normal sits higher.