Skip to content

Why curfew over tach?

tach is excellent, but its graph visualisation has two options that don't fit a locked-down bank/regulatory environment:

  • tach show --web uploads your module structure to a remote web viewer.
  • The DOT output needs the GraphViz binary installed to render.

curfew is local by construction:

  • No network, ever. No telemetry, no remote rendering, no "phone home". This is the headline differentiator.
  • Zero runtime dependencies. uv tool install curfew pulls in nothing — the engine and CLI run on the standard library alone. Colour is an optional [rich] extra.
  • No mandatory binaries. The default graph output is Mermaid, which renders in GitHub and MkDocs/Zensical with nothing installed. DOT is offered for those who already have GraphViz, but is never required.
  • One tool, both checks. Module boundaries and workspace/dependency validation share one import graph.

What it checks

The engine builds one static import graph (via ast, never executing your code), classifies every edge, and runs two rule-sets:

Check Finds Severity
Module boundary a module importing one it isn't allowed to error
importing a deprecated dependency warning
bypassing a module's public interface error
External / workspace importing an undeclared third-party distribution error
workspace leakage (resolves only via a sibling's dep) error
a declared dependency that is never imported warning
an import that is neither stdlib, first-party, nor installed warning

How leakage detection works

In a uv workspace, every member shares one resolved virtual environment. That makes it easy for member web to import requests and have it work — even though only sibling core declared requests. The day core drops it, web breaks. curfew flags that as ext.leak: imported, not declared by me, but declared by a sibling. The distribution↔import-name mapping uses importlib.metadata.packages_distributions() exclusively, so import yaml is correctly attributed to PyYAML, sklearn to scikit-learn, and so on.