<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wojciech Kot</title>
    <description>The latest articles on DEV Community by Wojciech Kot (@wojciech_kot_b82f5d7cbfc6).</description>
    <link>https://dev.to/wojciech_kot_b82f5d7cbfc6</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1937511%2Fec153212-0a22-4f59-ac2b-157f4a5e906b.png</url>
      <title>DEV Community: Wojciech Kot</title>
      <link>https://dev.to/wojciech_kot_b82f5d7cbfc6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wojciech_kot_b82f5d7cbfc6"/>
    <language>en</language>
    <item>
      <title>Stop circular dependencies before they stop you — dependency-cruiser &amp; the Stable Dependencies Principle</title>
      <dc:creator>Wojciech Kot</dc:creator>
      <pubDate>Tue, 23 Jun 2026 16:22:03 +0000</pubDate>
      <link>https://dev.to/wojciech_kot_b82f5d7cbfc6/stop-circular-dependencies-before-they-stop-you-dependency-cruiser-the-stable-dependencies-34ho</link>
      <guid>https://dev.to/wojciech_kot_b82f5d7cbfc6/stop-circular-dependencies-before-they-stop-you-dependency-cruiser-the-stable-dependencies-34ho</guid>
      <description>&lt;p&gt;Circular dependencies are one of those bugs that feel harmless right up until they aren't. No build error. No runtime exception on import. Just a subtle undefined that shows up weeks later in production, in a stack trace that points to the symptom and not the cause.&lt;br&gt;
This article shows how to catch them automatically using dependency-cruiser, and how to go one step further by encoding the Stable Dependencies Principle (SDP) directly into your project rules.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is a circular dependency?
&lt;/h2&gt;

&lt;p&gt;A circular dependency happens when module A imports from B, and B — directly or through a chain — imports back from A.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → B → A          (direct cycle)
A → B → C → A      (indirect cycle)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice it often looks harmless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// user-context.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAccessStatus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./access-status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// access-status.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./user-context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;// cycle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why JavaScript makes this dangerous
&lt;/h2&gt;

&lt;p&gt;JavaScript resolves circular imports silently. No build error, no runtime exception — just wrong behaviour that is hard to trace.&lt;br&gt;
When module A and B are in a cycle, whichever loads first will often see the other's exports as undefined (or trigger a ReferenceError in strict ESM environments) at the exact moment it evaluates. Here is exactly what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 1 — user-context.ts starts loading, hits the import of access-status.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2 — access-status.ts starts loading, hits the import of user-context.ts&lt;/span&gt;
&lt;span class="c1"&gt;//           user-context.ts is already loading but not finished&lt;/span&gt;
&lt;span class="c1"&gt;//           JavaScript returns what has been exported so far → nothing → UserContext is undefined&lt;/span&gt;

&lt;span class="c1"&gt;// Step 3 — access-status.ts finishes loading with undefined as UserContext&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultPermissions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPermissions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Step 4 — user-context.ts finishes, but access-status.ts already captured undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug shows up later when getAccessStatus is called, fails silently, and the stack trace points at the call site — not the import structure that caused it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting cycles with &lt;a href="https://www.npmjs.com/package/dependency-cruiser" rel="noopener noreferrer"&gt;dependency-cruiser&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Instead of guessing where these cycles hide, you can use dependency-cruiser. It scans your source files, maps out your entire dependency graph, and validates it against rules you define. It works right out of the box with TypeScript, path aliases, and monorepos like Yarn workspaces.&lt;/p&gt;

&lt;p&gt;Getting it up and running takes few minutes. First, add it into your dev dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; dependency-cruiser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add a shortcut script to your &lt;code&gt;package.json&lt;/code&gt; pointing to your configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"depcruise"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"depcruise packages --config .dependency-cruiser.js"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, create that &lt;code&gt;.dependency-cruiser.js&lt;/code&gt; file at the root of your repository. We will start with a basic configuration designed purely to catch circular dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import('dependency-cruiser').IConfiguration} */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-circular&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tsConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tsconfig.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;doNotFollow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.spec&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.tsx?$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;moduleSystems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the rules in place, you can kick off the check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn depcruise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your codebase is clean, the command runs silently and exits with code 0. However, if a cycle exists in your project, you'll see a violation that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt;
      &lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt;
      &lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt;
      &lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt;
      &lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of each → as a single import statement. The output traces the entire chain of dependency until the final line loops straight back to the first. That loop is exactly what you need to break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting cycles is reactive. The SDP is proactive.
&lt;/h2&gt;

&lt;p&gt;Finding a cycle and breaking it is the right fix for the immediate problem. But it does not address why the cycle formed. And if you do not address that, it will form again somewhere else.&lt;br&gt;
Cycles are a symptom. The root cause is a dependency arrow pointing the wrong way - a stable module that has been made to depend on an unstable one. The cycle is just the inevitable byproduct of that structural mistake.&lt;/p&gt;

&lt;p&gt;The Stable Dependencies Principle (SDP) is a design rule from Robert C. Martin's Clean Architecture that formalises this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Depend in the direction of stability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Stability here is a structural property, not a code quality score. A module is stable when many other modules depend on it — any change would ripple across all those consumers, and that cost acts as a natural brake. A module that nothing depends on is free to change at any time — structurally unstable, even if the code inside it is excellent.&lt;br&gt;
Martin defines instability (I) as a ratio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;I &lt;span class="o"&gt;=&lt;/span&gt; Fan-Out / &lt;span class="o"&gt;(&lt;/span&gt;Fan-In + Fan-Out&lt;span class="o"&gt;)&lt;/span&gt;

Fan-In  &lt;span class="o"&gt;=&lt;/span&gt; modules that import this one
Fan-Out &lt;span class="o"&gt;=&lt;/span&gt; modules this one imports from

I &lt;span class="o"&gt;=&lt;/span&gt; 0  →  maximally stable    &lt;span class="o"&gt;(&lt;/span&gt;everything depends on it, it depends on nothing&lt;span class="o"&gt;)&lt;/span&gt;
I &lt;span class="o"&gt;=&lt;/span&gt; 1  →  maximally unstable  &lt;span class="o"&gt;(&lt;/span&gt;it depends on many things, nothing depends on it&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule stated in terms of I: a module's instability score must be higher than the score of every module it imports from. The dependency arrow must always point toward lower I — toward something that changes less often than you do.&lt;/p&gt;

&lt;p&gt;By engineering your project around the SDP, you ensure that your most heavily relied-upon code never points toward volatile, fast-changing modules. You stop treating dependency design as a matter of gut feeling and turn it into a structural rule. When arrows are strictly forced to point toward stability, you don't just find cycles faster—you make hard for them to form in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a healthy dependency graph looks like
&lt;/h2&gt;

&lt;p&gt;The SDP gives you one rule for every arrow in your graph: it must point toward lower I — toward something more stable than the module drawing the arrow.&lt;/p&gt;

&lt;p&gt;In practice that means your most reused, most depended-upon code sits at the bottom of the graph, and your most frequently changing code sits at the top. Nothing at the bottom imports from anything at the top.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✅  Correct — arrows always point toward stability
  ┌──────────────────────────────────────┐
  │  High-level modules  &lt;span class="o"&gt;(&lt;/span&gt;I ≈ 1.0&lt;span class="o"&gt;)&lt;/span&gt;       │  change often
  │  pages, app entry, top-level config  │  nothing &lt;span class="k"&gt;else &lt;/span&gt;imports from them
  └──────────────────┬───────────────────┘
                     │
                     ▼
  ┌──────────────────────────────────────┐
  │  Mid-level modules  &lt;span class="o"&gt;(&lt;/span&gt;I ≈ 0.5&lt;span class="o"&gt;)&lt;/span&gt;        │  change occasionally
  │  domain logic, feature modules       │  some things depend on them
  └──────────────────┬───────────────────┘
                     │
                     ▼
  ┌──────────────────────────────────────┐
  │  Low-level modules  &lt;span class="o"&gt;(&lt;/span&gt;I ≈ 0.0&lt;span class="o"&gt;)&lt;/span&gt;        │  rarely change
  │  utils, types, constants, base hooks │  everything depends on them
  └──────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❌  Wrong — an arrow points upward &lt;span class="o"&gt;(&lt;/span&gt;toward instability&lt;span class="o"&gt;)&lt;/span&gt;

  ┌──────────────────────────────────────┐
  │  High-level modules  &lt;span class="o"&gt;(&lt;/span&gt;I ≈ 1.0&lt;span class="o"&gt;)&lt;/span&gt;       │
  └──────────────────────────────────────┘
            ▲
            │  ← this import is the SDP violation
  ┌──────────────────────────────────────┐
  │  Low-level modules  &lt;span class="o"&gt;(&lt;/span&gt;I ≈ 0.0&lt;span class="o"&gt;)&lt;/span&gt;        │
  └──────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The upward arrow does not have to produce a cycle immediately. But it creates the structural condition in which a cycle will eventually form — because the high-level module will evolve, import more things, and at some point one of those imports will close the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encoding SDP as dependency-cruiser rules
&lt;/h2&gt;

&lt;p&gt;This is where dependency-cruiser becomes more than a cycle detector — it becomes an architectural rule engine. You declare which import directions are forbidden, and it catches a violation the moment the import is written, before a cycle has a chance to form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-circular&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// low-level modules must never import from mid- or high-level ones&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-utils-to-features&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stable utils must not depend on less stable feature modules.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^src/utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^src/(features|pages)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// mid-level modules must never import from high-level ones&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-features-to-pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feature modules must not depend on pages.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^src/features&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^src/pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;no-utils-to-features is not an arbitrary naming convention. It is the SDP stated as a machine-checkable rule: a module with I≈0 must not import from modules with I≈0.5 or higher. A developer who adds that import sees a CI failure immediately, on that line, with that rule name. The cycle does not need to exist yet.&lt;br&gt;
This is the distinction that matters: cycle detection tells you when a forbidden state has already been created. A direction rule catches the violation before that state can form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Circular dependencies aren't a tooling problem; they are a structural failure. A cycle is simply what happens when dependency arrows point the wrong way for long enough.&lt;/p&gt;

&lt;p&gt;dependency-cruiser gives you two layers of defense: a cycle detector that catches existing fires, and an architectural rule engine that prevents them entirely. While the first is a useful safety net, the second is what actually keeps a codebase clean as it scales.&lt;/p&gt;

&lt;p&gt;The Stable Dependencies Principle provides the "why" behind these rules. Forcing stable code to depend on volatile code isn't just a bad convention. That hidden coupling is exactly what eventually surfaces as a silent undefined in production.&lt;/p&gt;

&lt;p&gt;The configuration file is tiny, and the principle is simple. Together, they transform dependency management from a reactive cleanup task into an automated guardrail that holds the architectural line on every single PR.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>architecture</category>
      <category>typescript</category>
      <category>cleancode</category>
    </item>
  </channel>
</rss>
