<?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>Forem: Soumyadeep Mahapatra</title>
    <description>The latest articles on Forem by Soumyadeep Mahapatra (@deepcodersinc).</description>
    <link>https://forem.com/deepcodersinc</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3881232%2F76faec99-5411-45e3-abf4-e6c6b94d144b.png</url>
      <title>Forem: Soumyadeep Mahapatra</title>
      <link>https://forem.com/deepcodersinc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/deepcodersinc"/>
    <language>en</language>
    <item>
      <title>Architectural drift with Agentic coding. Here's what I built to fix it.</title>
      <dc:creator>Soumyadeep Mahapatra</dc:creator>
      <pubDate>Wed, 15 Apr 2026 21:24:50 +0000</pubDate>
      <link>https://forem.com/deepcodersinc/agentic-coding-architectural-drift-heres-what-i-built-to-fix-it-4h2j</link>
      <guid>https://forem.com/deepcodersinc/agentic-coding-architectural-drift-heres-what-i-built-to-fix-it-4h2j</guid>
      <description>&lt;p&gt;I've been shipping features with Claude Code for months now. The velocity is incredible — what used to take days takes an afternoon. But something kept bugging me.&lt;/p&gt;

&lt;p&gt;The code diffs on my PRs looked fine. Tests passed. Lint was clean. But every few weeks I'd open a file I hadn't touched in a month and find it importing from three new places, calling services that shouldn't know about each other, and slowly becoming unrecognizable.&lt;/p&gt;

&lt;p&gt;The architecture was drifting. Nobody noticed because nobody was looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why code review misses architectural drift
&lt;/h2&gt;

&lt;p&gt;Pull request review is built around &lt;em&gt;lines of code&lt;/em&gt;. You see what changed in a file. You don't see how those changes affect the shape of the system.&lt;/p&gt;

&lt;p&gt;A reviewer looking at &lt;code&gt;+ import { db } from '../../prisma'&lt;/code&gt; in a frontend component won't catch that the frontend should never touch the database directly. The line looks reasonable in isolation. The &lt;em&gt;architectural implication&lt;/em&gt; is invisible unless you already have the system map in your head.&lt;/p&gt;

&lt;p&gt;And in the AI era, the velocity problem makes this worse. An agent can add 14 new imports across 8 files in a single PR. The diff is too big to hold in your head, so you skim and approve.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wanted
&lt;/h2&gt;

&lt;p&gt;I wanted something that looked at PRs and said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This PR introduces a new dependency from &lt;code&gt;Auth&lt;/code&gt; to &lt;code&gt;Billing&lt;/code&gt;. Is that intended?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not a black-box ML model that flags "drift detected." Just a clear, visual statement of what changed architecturally. Reviewable. Explainable. Reversible.&lt;/p&gt;

&lt;p&gt;I also wanted the architecture map to live &lt;em&gt;in the repo&lt;/em&gt; — not in Confluence, not in a SaaS dashboard. Versioned, diffable, the single source of truth that travels with the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;A Claude Code skill called &lt;a href="https://github.com/deepcodersinc/unkode" rel="noopener noreferrer"&gt;unkode&lt;/a&gt;. Here's what it does:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. One command generates a YAML architecture map.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/unkode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude reads the codebase and writes &lt;code&gt;unkode.yaml&lt;/code&gt; — a list of modules, components, their dependencies, and deployment topology. Commit it to main. That's your baseline.&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;architecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Web Application&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/remix&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="na"&gt;tech&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;TypeScript&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;React&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Main user-facing app&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;API Layer&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Authentication&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;API Layer&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packages/trpc&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;tech&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;TypeScript&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tRPC&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Type-safe API between frontend and services&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Database&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Authentication&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PostgreSQL&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Primary data store&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Every code change, you run &lt;code&gt;/unkode&lt;/code&gt; again.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It diffs your changes against the existing YAML and updates only what changed. Incremental sync costs around 500 tokens — pennies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The diagram regenerates automatically.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A deterministic script converts the YAML to Mermaid, written to &lt;code&gt;arch_map.md&lt;/code&gt;. Zero AI involved. GitHub renders it natively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. On every PR, a GitHub Action posts a diff.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Color-coded. Green = new, red = removed, amber = modified.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F627m2n0w5y02gwojjxfi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F627m2n0w5y02gwojjxfi.png" alt="Example architecture diff" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reviewer now sees, at a glance: "This PR added a Billing module that depends on Stripe, and added a new dependency from Authentication to Redis." Any reviewer can make a judgment call on that. They don't need to read 40 files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this approach instead of ML-based drift detection
&lt;/h2&gt;

&lt;p&gt;There are other tools that do drift detection. They use learned embeddings and risk scores. Fine for what they are, but two things bugged me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;They're black boxes.&lt;/strong&gt; When the tool says "arch is drifting" what do I do with that? I can't inspect it. I can't disagree with it. I can't explain it in a standup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The baseline is theirs, not mine.&lt;/strong&gt; I don't get to say "actually, the Auth module having a dependency on Redis is fine." I have to live with whatever the model thinks.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unkode flips this. The YAML is yours. You can edit it by hand. You can override what Claude generated. You can encode deliberate architectural decisions. The diff is visible, the rules are inspectable, nothing is hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  The token cost I was worried about
&lt;/h2&gt;

&lt;p&gt;My biggest concern was cost. A tool that eats tokens on every commit is a non-starter.&lt;/p&gt;

&lt;p&gt;After several iterations (and burning my tokens testing large repos) the numbers surprised me:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First-time generation on a 1.5M LOC monorepo&lt;/td&gt;
&lt;td&gt;~4,000&lt;/td&gt;
&lt;td&gt;~3 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incremental sync on a branch&lt;/td&gt;
&lt;td&gt;~500–1,000&lt;/td&gt;
&lt;td&gt;under a minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mermaid rendering&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR diff in CI&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Mermaid diagram and the PR diff are pure Python — deterministic transforms on the YAML. They use zero AI tokens. Only the YAML update touches an LLM.&lt;/p&gt;

&lt;p&gt;The first-time cost scales more with how well-documented your project is than how big it is. A 1.5M LOC monorepo with clean package boundaries costs roughly the same as a smaller but messier repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing it
&lt;/h2&gt;

&lt;p&gt;Three steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Copy the skill into your repo.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; unkode/skills/unkode .claude/skills/unkode

&lt;span class="c"&gt;# Or for Codex, Cursor, Aider, etc.&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; unkode/skills/unkode .agents/skills/unkode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Generate the baseline.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/unkode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit &lt;code&gt;unkode.yaml&lt;/code&gt; and &lt;code&gt;arch_map.md&lt;/code&gt; to main.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. (Recommended) Add the GitHub Action.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/actions/unkode
&lt;span class="nb"&gt;cp &lt;/span&gt;unkode/config/github/action.yml .github/actions/unkode/action.yml
&lt;span class="nb"&gt;cp &lt;/span&gt;unkode/config/github/unkode_arch_check.yml .github/workflows/unkode_arch_check.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From then on, every PR gets a diff diagram as a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The tool works today but it's early. A few things I'm still figuring out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hallucination prevention.&lt;/strong&gt; Right now the agent trusts well-written READMEs a little too much. Adding strict path validation so every module in the YAML must resolve to an actual folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-repo view.&lt;/strong&gt; Useful for monorepos and microservices alike.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History timeline.&lt;/strong&gt; Right now git log is the history. A visual timeline of how the architecture evolved would be nice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this resonates with problems you've hit — I'd love feedback. The repo is &lt;a href="https://github.com/deepcodersinc/unkode" rel="noopener noreferrer"&gt;github.com/deepcodersinc/unkode&lt;/a&gt;, Apache 2.0, and a star helps if you find it useful ⭐&lt;/p&gt;

&lt;p&gt;If you'd like to hear about major updates as they ship, there's a &lt;a href="https://tally.so/r/lb7odB" rel="noopener noreferrer"&gt;waitlist&lt;/a&gt; — no marketing, no spam, just a single notification when a new iteration is ready.&lt;/p&gt;

&lt;p&gt;Would love to know: how is your team tracking architecture today? Confluence diagrams that nobody updates? A weekly tech debt meeting? Nothing at all? A better tool? Genuinely curious.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>opensource</category>
      <category>architecture</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
