Blast Radius — blueprint deps
blueprint deps answers one question before you touch a module: who gets hit if I change this? It is read-only, needs no setup beyond the blueprint itself, and never writes a file.
It is the day-to-day companion to blueprint inspect: inspect judges the architecture (violations, cycles, exit 1), deps only describes it — fan-in and fan-out per module, no verdict attached.
How to run it
npx @kekkai/blueprint deps # leaderboard: every module by fan-in
npx @kekkai/blueprint deps hooks/useCart # one module, by module key
npx @kekkai/blueprint deps src/hooks/useCart/useCart.ts # same query, by file pathAll three input forms resolve to the same module key — with or without the src/ prefix, with or without the file extension.
| Flag | Effect |
|---|---|
--json | Machine-readable output (for tooling or an AI agent) |
--framework vue|react | Force the preset when no config exists and detection is ambiguous |
What you will see
Without a target — the blast-radius leaderboard, every module sorted by how many modules import it. The most dangerous file to touch sits on top:
Blast radius (imported-by count):
2 ← hooks/useCart
1 ← services/api
0 ← containers/Cart
0 ← pages/Home
(not under a declared layer, invisible to deps: legacy/)With a target — both directions at once. imported by is the blast radius of changing it; imports is what it stands on:
hooks/useCart
imported by (2):
← containers/Cart
← pages/Home
imports (1):
→ services/apiWith --json — the same data, structured. The leaderboard payload is { modules, skipped }; a target query returns a single module object:
{
"module": "hooks/useCart",
"importedBy": ["containers/Cart", "pages/Home"],
"imports": ["services/api"]
}An unknown target exits 1 with a pointer back to the leaderboard; every successful query exits 0.
Granularity — set by module.layout
The unit of every answer is the module, and what counts as a module follows the blueprint's module.layout (per layer, via layer.module):
folderlayout — each direct child of the layer is one module (hooks/useCart,components/HelloWorld). Direct files drop their extension, sodeps components/HelloWorldandcomponents/HelloWorld.vuename the same module.flatlayout — the whole layer collapses to one node. This fits layers whose nested folders are not modules — a Next.js route tree, for example, whereapp/(marketing)/pricing/page.tsxis a route, not a feature folder. Deps says so explicitly rather than silently switching granularity:
app (flat layer — answers at layer granularity)What is in the graph — and what is not
- Declared layers only. Folders outside
architecture.layersare not scanned into the graph; the leaderboard lists them as skipped (seelegacy/above) so a zero fan-in is never misread as "nobody imports this". Querying into one fails with the reason:✗ "legacy/" is not a declared layer. - Test files are excluded (
architecture.testFiles) — a test importing a module adds nothing to its blast radius, matching the lint side. - Only alias and relative imports form edges. Package imports (
axios,vue) are not part of the module graph — package ownership isinspect's job. - Cycles are listed, not judged. Two modules importing each other simply show up in each other's fan-in and fan-out; the verdict belongs to
inspect.
Config validation
A hand-written blueprint.config.mjs that skips defineBlueprint is validated on load anyway. A structural mistake fails immediately with a precise message instead of crashing mid-command:
✗ blueprint.config.mjs: architecture.module.private must be an array.