<?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: ali sher</title>
    <description>The latest articles on DEV Community by ali sher (@ali_sher_5fe8bdb517c81548).</description>
    <link>https://dev.to/ali_sher_5fe8bdb517c81548</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%2F4232551%2Fe6c990b3-050f-411d-8213-1f84c5e5a5ca.png</url>
      <title>DEV Community: ali sher</title>
      <link>https://dev.to/ali_sher_5fe8bdb517c81548</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ali_sher_5fe8bdb517c81548"/>
    <language>en</language>
    <item>
      <title>Ripple</title>
      <dc:creator>ali sher</dc:creator>
      <pubDate>Sat, 07 Nov 2026 17:38:28 +0000</pubDate>
      <link>https://dev.to/ali_sher_5fe8bdb517c81548/ripple-5o3</link>
      <guid>https://dev.to/ali_sher_5fe8bdb517c81548/ripple-5o3</guid>
      <description>&lt;h1&gt;
  
  
  Your AI agent's PR passed tests and still broke 37 routes
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Tests prove behavior. ** They don't show reach. Here's how to gate the blast radius of a TypeScript change.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An AI agent opens a PR. The diff is 12 lines. The tests pass. CI is green. Then it ships, and a helper you thought was local turns out to be imported by four routes, two components, and a background job you forgot existed.&lt;/p&gt;

&lt;p&gt;The tests covered the behavior the agent changed. They didn't cover the surface it reached.&lt;/p&gt;

&lt;p&gt;This is the failure mode I kept seeing, so I built Ripple: a CI gate that scores every changed TypeScript/JavaScript file by its blast radius, then blocks the merge when a change reaches too far.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 37-route problem
&lt;/h2&gt;

&lt;p&gt;Call it 37 routes, 12 components, or 80 files. The exact number doesn't matter. The pattern does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The diff is small.&lt;/li&gt;
&lt;li&gt;The tests pass.&lt;/li&gt;
&lt;li&gt;The change lands in a module with more dependents than the author realized.&lt;/li&gt;
&lt;li&gt;Review focuses on the lines, not the reach.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI agents make this worse because they produce plausible changes at machine speed. The PR looks reviewable. The graph underneath is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why tests miss blast radius
&lt;/h2&gt;

&lt;p&gt;Tests are a sample of behavior. They tell you the paths you thought to exercise. They don't tell you which paths exist.&lt;/p&gt;

&lt;p&gt;An import graph tells you what a file depends on. The interesting question is the reverse: &lt;strong&gt;what depends on this file? **&lt;/strong&gt; That's the blast radius. Package managers don't answer it. &lt;code&gt;grep&lt;/code&gt; doesn't answer it. Most dependency graphs are drawn in the wrong direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Ripple actually does
&lt;/h2&gt;

&lt;p&gt;Ripple parses the project with ts-morph, builds the import graph, and walks it in reverse from the changed file. It categorizes everything it reaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;routes&lt;/li&gt;
&lt;li&gt;layouts&lt;/li&gt;
&lt;li&gt;components&lt;/li&gt;
&lt;li&gt;utilities&lt;/li&gt;
&lt;li&gt;tests&lt;/li&gt;
&lt;li&gt;entry points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it scores the change from 0 to 100 using seven weighted signals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Affected files&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry point impacted&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared utility impacted&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public export surface&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests affected&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routes affected&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circular dependency membership&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The score is deterministic and explainable. &lt;code&gt;--verbose&lt;/code&gt; prints every factor and its contribution. No model, no cloud, no telemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real example
&lt;/h2&gt;

&lt;p&gt;The repo ships a small fixture so you can reproduce this line for line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/alimaandev/ripple.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ripple/tests/fixtures/basic
npx @alimaandev/ripple analyze src/authentication/login.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output (trimmed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ripple · impact analysis · v0.7.2

File        src/authentication/login.ts
Risk        MEDIUM · 34.7/100 ███░░░░░░░
Impact      4 routes · 2 components · 1 test
Affected    7 files
Max depth   2
Confidence  100%

› Top impact
● Dashboard (2)
● Admin (1)
● Index TS (1)
● Main TS (1)
● Mobile (1)

› Affected files (7)
├─ src/admin/page.tsx · depth 1
├─ src/dashboard/page.tsx · depth 1
├─ src/dashboard/route.ts · depth 1
├─ src/index.ts · depth 1
├─ src/mobile/route.ts · depth 1
├─ src/tests/login.test.ts · depth 1
└─ src/main.ts · depth 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One file. Seven dependents. Four routes. Two components. One test. The change is not just the lines in the diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate the PR in one step
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ripple&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ripple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alimaandev/ripple-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fetch-depth: 0&lt;/code&gt; matters. Ripple needs the base commit to compute the change set; a shallow checkout gives it the wrong diff.&lt;/p&gt;

&lt;p&gt;When a changed file is at or above the gate, the job fails, and the affected files are annotated on the PR's Files tab. The action can also emit a SARIF report you can upload to Code Scanning, so the same findings show up as code scanning alerts.&lt;/p&gt;

&lt;p&gt;For a legacy codebase, start with &lt;code&gt;gate: critical&lt;/code&gt;, allowlist the worst offenders, and tighten it over time. For a monorepo, point it at the right package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
  &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/web&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this doesn't solve
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't replace tests, review, or type checking.&lt;/li&gt;
&lt;li&gt;It doesn't know whether the affected routes matter to your business.&lt;/li&gt;
&lt;li&gt;It can be noisy in a codebase with a few god modules. Start loose, then tighten the gate.&lt;/li&gt;
&lt;li&gt;It's a prioritization tool, not a proof of safety.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to block every change. It's to make the risky ones obvious before they merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @alimaandev/ripple analyze src/your-file.ts
npx @alimaandev/ripple diff &lt;span class="nt"&gt;--base&lt;/span&gt; origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/alimaandev/ripple" rel="noopener noreferrer"&gt;https://github.com/alimaandev/ripple&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Marketplace: &lt;a href="https://github.com/marketplace/actions/ripple-pr-risk-gate" rel="noopener noreferrer"&gt;https://github.com/marketplace/actions/ripple-pr-risk-gate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@alimaandev/ripple" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@alimaandev/ripple&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run it on a large codebase, I'd like to know where the score is wrong. False positives are what will kill this tool. Tell me which files it flags that you'd ship anyway.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>javascript</category>
      <category>testing</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
