<?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: untactit</title>
    <description>The latest articles on DEV Community by untactit (@untactit).</description>
    <link>https://dev.to/untactit</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%2F4073792%2Fd8ba9cbb-1019-4bb5-9480-3dd40b8c2b35.png</url>
      <title>DEV Community: untactit</title>
      <link>https://dev.to/untactit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/untactit"/>
    <language>en</language>
    <item>
      <title>Generating your agent rules from one file does not stop them drifting</title>
      <dc:creator>untactit</dc:creator>
      <pubDate>Tue, 11 Aug 2026 22:51:21 +0000</pubDate>
      <link>https://dev.to/untactit/generating-your-agent-rules-from-one-file-does-not-stop-them-drifting-3gpb</link>
      <guid>https://dev.to/untactit/generating-your-agent-rules-from-one-file-does-not-stop-them-drifting-3gpb</guid>
      <description>&lt;p&gt;The obvious fix for "our agent instruction files keep diverging" is to stop maintaining copies. Write &lt;code&gt;AGENTS.md&lt;/code&gt;, generate &lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;.cursor/rules/*.mdc&lt;/code&gt; and &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; from it, done.&lt;/p&gt;

&lt;p&gt;I built that. It works. It also does not solve the problem, and the gap between those two statements is worth spelling out, because I only saw it after running the thing on a real machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What generation actually covers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 agent_fanout.py

create    CLAUDE.md
create    .cursor/rules/from-agents-md.mdc
create    .github/copilot-instructions.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One source, several derived files, a header on each so nobody edits the derived copy by accident:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Generated from AGENTS.md by agent-fanout. Do not edit this file. --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add it to CI and the pull request that edits &lt;code&gt;CLAUDE.md&lt;/code&gt; directly turns the build red:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3 agent_fanout.py . --check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That covers &lt;strong&gt;this repository, on the machines that run CI&lt;/strong&gt;. Which sounds like everything until you list what it isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not cover
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Other repositories.&lt;/strong&gt; Your team has more than one. Each has its own &lt;code&gt;AGENTS.md&lt;/code&gt;, and they were copy-pasted from each other at some point. Generation keeps each repo internally consistent while the repos drift apart from one another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global config.&lt;/strong&gt; Claude Code reads &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; in addition to the project file. Cursor has user-level rules. Those live outside any repository, are never in CI, and are exactly where people put the rule they didn't want to argue about in review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The window between edits.&lt;/strong&gt; Generation runs when someone runs it. Between that moment and the next CI run, a derived file can be edited and used. The agent reads it immediately; CI notices on push, if there is a push.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repos without CI.&lt;/strong&gt; Prototypes, scratch clones, the repo someone made last Tuesday. Those are where instructions get freely modified, and they are the ones with no gate at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Machines, not repositories.&lt;/strong&gt; The unit that runs an agent is a laptop. A laptop has many checkouts, several of the same repo, and a home directory. Nothing that operates per-repository can see across that.&lt;/p&gt;

&lt;h2&gt;
  
  
  So you need to look, not just prevent
&lt;/h2&gt;

&lt;p&gt;Prevention is a policy. Detection is a measurement. Policies get bypassed in ways that are invisible until you measure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 agent_drift.py ~/work ~/side-projects

Scanned 47 instruction files.

DRIFT: 2 documents, 5 distinct versions between them.

  claude-code:CLAUDE.md
    6 copies, 3 versions
      9b01aeaa204d  3 files, 406 lines
      2dc3c616c279  2 files, 411 lines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one scans paths rather than repositories, and groups files by content similarity rather than by filename — two unrelated projects having different &lt;code&gt;CLAUDE.md&lt;/code&gt; files is not drift, and reporting it as drift makes the output worthless. (Getting that grouping right took three rewrites; &lt;a href="https://dev.to/untactit/i-found-18-versions-of-the-same-claudemd-on-one-laptop-bbc"&gt;I wrote that part up separately&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Run it across your whole working directory, not one project. The interesting results are the ones that cross repository boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pairing
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent-fanout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;one repository&lt;/td&gt;
&lt;td&gt;"are the derived files current?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent-drift&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;whole machine, many paths&lt;/td&gt;
&lt;td&gt;"where did copies diverge anyway?"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Generation removes the &lt;em&gt;reason&lt;/em&gt; copies exist. Detection catches the copies that exist for reasons you did not anticipate. Neither is redundant, and doing only the first one gives you a false sense of coverage — which is the actual failure mode I want to warn about, because it is the one I walked into.&lt;/p&gt;

&lt;p&gt;Both are single-file Python, no dependencies, read-only where it matters, MIT:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/untactit/agent-fanout" rel="noopener noreferrer"&gt;agent-fanout&lt;/a&gt; — write &lt;code&gt;AGENTS.md&lt;/code&gt; once, generate the rest&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/untactit/agent-drift" rel="noopener noreferrer"&gt;agent-drift&lt;/a&gt; — find the copies that no longer agree&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this goes
&lt;/h2&gt;

&lt;p&gt;The honest end state is that neither script is necessary, because the assets are not files sitting on laptops at all — they live in one reviewed place and reach every machine without anyone copying anything. That is what I am building at &lt;a href="https://untactit.com" rel="noopener noreferrer"&gt;untactit&lt;/a&gt;, currently pre-launch.&lt;/p&gt;

&lt;p&gt;The scripts stand on their own and don't depend on it. Use them, ignore the rest.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I found 18 versions of the same CLAUDE.md on one laptop</title>
      <dc:creator>untactit</dc:creator>
      <pubDate>Tue, 11 Aug 2026 22:29:37 +0000</pubDate>
      <link>https://dev.to/untactit/i-found-18-versions-of-the-same-claudemd-on-one-laptop-bbc</link>
      <guid>https://dev.to/untactit/i-found-18-versions-of-the-same-claudemd-on-one-laptop-bbc</guid>
      <description>&lt;p&gt;Your team runs Claude Code, Cursor, and Copilot. Each of them reads a file before it acts — &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;.cursor/rules/*.mdc&lt;/code&gt;, &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;. Those files get copied: into another repo, onto another laptop, into someone's scratch directory.&lt;/p&gt;

&lt;p&gt;Then somebody edits a copy.&lt;/p&gt;

&lt;p&gt;Nothing errors. No warning, no diff, no failing test. The agent on that machine just behaves differently — a rule someone removed six weeks ago, a convention only half the team's agents know about. It shows up as "works on my machine" with nothing to bisect.&lt;/p&gt;

&lt;p&gt;I wrote a script to find those files and tell you which copies disagree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-O&lt;/span&gt; https://raw.githubusercontent.com/untactit/agent-drift/main/agent_drift.py
python3 agent_drift.py ~/work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanned 47 instruction files.

DRIFT: 2 documents, 5 distinct versions between them.

  claude-code:CLAUDE.md
    6 copies, 3 versions
      9b01aeaa204d  3 files, 406 lines
        ~/work/api/CLAUDE.md
        ~/work/web/CLAUDE.md
      2dc3c616c279  2 files, 411 lines
        ~/work/infra/CLAUDE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One file, standard library only, Python 3.8+. Reads only — it never writes to the files it scans. MIT.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting part is what I got wrong
&lt;/h2&gt;

&lt;p&gt;The naive version took me ten minutes and was useless. The three fixes are the whole story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grouping by filename is wrong
&lt;/h3&gt;

&lt;p&gt;First version: collect every &lt;code&gt;CLAUDE.md&lt;/code&gt;, hash them, report the ones that differ.&lt;/p&gt;

&lt;p&gt;Ran it on my own machine. It reported &lt;strong&gt;21 files as one document with 18 versions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Of course it did. Unrelated projects have unrelated instructions. They were never supposed to match. A tool that reports that as drift is noise, and a noisy tool is worse than no tool — you stop reading it by the third run.&lt;/p&gt;

&lt;p&gt;Files are the same document when their &lt;strong&gt;content&lt;/strong&gt; says so, not when their filename does.&lt;/p&gt;

&lt;h3&gt;
  
  
  A bag of words is not enough either
&lt;/h3&gt;

&lt;p&gt;So: normalise whitespace, tokenise, compare with Jaccard similarity, threshold 0.7.&lt;/p&gt;

&lt;p&gt;Still one giant cluster. I measured actual pairs to find out why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;012Hki23QBMr ↔ 016ct6suZ14M : 0.000
012Hki23QBMr ↔ 016avoYh97Eu : 0.764
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instruction files written for the same tool share almost all of their vocabulary. &lt;code&gt;claude&lt;/code&gt;, &lt;code&gt;skill&lt;/code&gt;, &lt;code&gt;agent&lt;/code&gt;, &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;never&lt;/code&gt;, &lt;code&gt;always&lt;/code&gt;. Two documents with nothing in common still overlap heavily as word sets.&lt;/p&gt;

&lt;p&gt;Switched to &lt;strong&gt;5-word shingles&lt;/strong&gt; — the set of every consecutive 5-word phrase. Vocabulary can collide; phrasing rarely does. Re-measured: unrelated pairs dropped to 0.000, genuinely forked documents landed at 0.53–0.76. That separation is what you want, and it came from measuring, not from reasoning about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single-linkage clustering chains everything together
&lt;/h3&gt;

&lt;p&gt;Still one cluster. This one is a classic.&lt;/p&gt;

&lt;p&gt;Union-find with "similar to any member" means A joins B, B joins C, and now A and C are in the same cluster despite sharing nothing. One weak link and the cluster swallows the tree.&lt;/p&gt;

&lt;p&gt;Each cluster now keeps a &lt;strong&gt;representative&lt;/strong&gt; — its largest member — and a file joins only if it resembles that representative directly. Clusters stay coherent regardless of scan order.&lt;/p&gt;

&lt;p&gt;Final result on my machine: 15 files, 9 of them 0.53–0.65 similar to the representative. Those really are forks of one template. Correct detection, and the number is small enough to act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put it in CI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3 agent_drift.py . --fail-on-drift&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit 1 when drift exists. The build goes red the day two copies of your team's instructions stop matching, instead of you finding out in six months when someone asks why the agent stopped following a rule.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--json&lt;/code&gt; gives you the full report if you'd rather post-process it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it covers
&lt;/h2&gt;

&lt;p&gt;Claude Code, Codex/&lt;code&gt;AGENTS.md&lt;/code&gt;, Cursor, GitHub Copilot, Gemini, Windsurf, Cline, Aider, Continue, Zed. The patterns are one constant at the top of the file — PR or issue if yours is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  After you find it
&lt;/h2&gt;

&lt;p&gt;Detection is the easy half. The copies exist for a reason, and until that reason is gone the drift comes back. What removes it is one reviewed source that reaches every machine without anyone pasting anything.&lt;/p&gt;

&lt;p&gt;That's the problem I'm building &lt;a href="https://untactit.com" rel="noopener noreferrer"&gt;untactit&lt;/a&gt; around — it's pre-launch. This script doesn't depend on it and never will.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/untactit/agent-drift" rel="noopener noreferrer"&gt;github.com/untactit/agent-drift&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
