<?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: drmikecrowe</title>
    <description>The latest articles on DEV Community by drmikecrowe (@drmikecrowe).</description>
    <link>https://dev.to/drmikecrowe</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%2F236482%2F461a5f5c-6342-4928-a444-57e99a32b97a.png</url>
      <title>DEV Community: drmikecrowe</title>
      <link>https://dev.to/drmikecrowe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/drmikecrowe"/>
    <language>en</language>
    <item>
      <title>First Drafts All the Way Down</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Mon, 19 Oct 2026 11:46:06 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/first-drafts-all-the-way-down-1ppi</link>
      <guid>https://dev.to/drmikecrowe/first-drafts-all-the-way-down-1ppi</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was written with AI assistance (Claude) for structure, formatting, and pulling the numbers out of my logs. The process, the opinions, and the voice are my own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If your AI workflow is plan, build, tests pass, ship, you are shipping first drafts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don't mean that as a dig at the models. I mean it literally. The model hands you a plan with total confidence, then hands you code with a green test run and the same total confidence, and both of them are first drafts. Nobody would ship a junior engineer's first draft without a review. We've somehow decided the frontier model gets a pass.&lt;/p&gt;

&lt;p&gt;I didn't want to argue that from vibes, so I measured it. Over the last month I ran 83 stories through my AI SDLC process across three repositories. Opus 5.5 wrote almost every plan and implementation, which is the best model I can reasonably afford to run all day. Separate review agents, mostly on Sonnet, checked the work at every step. Here's what they found:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3cgvsvvkj3esao8gha2y.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3cgvsvvkj3esao8gha2y.png" alt="80% (53 / 66) of plans needed rework before the build started; 48% (31 / 64) of implementations, every test passing, needed rework before the PR; 25% (14 / 56) of plans pointed the wrong way until a person overruled the model on a Decide call" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Four out of five plans. Half of the green builds. One plan in four heading in the wrong direction. That's the latest model I can afford, working inside a process built to make it succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this came from
&lt;/h2&gt;

&lt;p&gt;I read Itamar Gilad's &lt;a href="https://itamargilad.com/book-evidence-guided/" rel="noopener noreferrer"&gt;Evidence-Guided&lt;/a&gt;, and it changed how I think about delivering software. The book is about product management, but the core move applies everywhere: stop deciding on opinion and confidence, and combine human judgement with evidence. Your confidence in an idea should match the evidence you have for it, not how good it sounds in the meeting.&lt;/p&gt;

&lt;p&gt;Here's the thing: a frontier model is the most confident colleague you will ever work with. It never says "I think." It says "Done." Gilad's book taught me to be suspicious of confidence without evidence, and it turns out that's exactly the right instinct for AI-written software.&lt;/p&gt;

&lt;h2&gt;
  
  
  We can't read our way out of this
&lt;/h2&gt;

&lt;p&gt;This week Anthropic published how they &lt;a href="https://www.anthropic.com/engineering/redesigning-source-control" rel="noopener noreferrer"&gt;redesigned source control&lt;/a&gt;. They went from about 2,500 merged changes a day in May 2026 to over 30,000 a day in September, and Git couldn't keep up, so they built their own system (Scion). Their line that stuck with me: "the latent demand for merging code is practically infinite, and every bottleneck we remove immediately leads to a further increase in the amount of code we produce."&lt;/p&gt;

&lt;p&gt;Think about what that means for code review. If the company that makes the model can't keep its version control on top of the volume, nobody is reading all those diffs. I'm certainly not reading all of mine. For most of my career, reading the PR &lt;em&gt;was&lt;/em&gt; the quality process. That doesn't scale to this.&lt;/p&gt;

&lt;p&gt;So the standard has to change. &lt;strong&gt;The new standard is evidence that we met the plan and the story&lt;/strong&gt;, assembled by the process and checked by something other than the thing that wrote the code. I read the evidence. I don't read every line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: you need real tests first
&lt;/h2&gt;

&lt;p&gt;I want to be blunt about this one: &lt;strong&gt;you won't get acceleration from AI without thorough testing.&lt;/strong&gt; Tests are the only thing that steers the model. Without them, every one of the checks below has nothing to stand on, and "it compiles" becomes your definition of done.&lt;/p&gt;

&lt;p&gt;And "we have tests" isn't the bar either. I wrote about this in &lt;a href="https://pinnsg.com/the-tests-that-would-not-notice/" rel="noopener noreferrer"&gt;The Tests That Would Not Notice&lt;/a&gt;. In this data set, 46 implementations ran mutation testing (1,055 mutants), and 8 of them had passing tests that wouldn't notice a broken line until new tests were written. Testing is step 0. Good testing is step 0 done right. Nothing in the AI SDLC works until it's there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The process, briefly
&lt;/h2&gt;

&lt;p&gt;Every story goes through the same steps. The model writes, and a separate agent checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Story.&lt;/strong&gt; The author answers questions until the acceptance criteria are testable. A story check grades whether it's ready to plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan.&lt;/strong&gt; The model writes the plan. An adversarial plan review hunts for places a builder would build something the story didn't mean. A plan check grades whether an engineer who never saw the conversation could build it. A person rules on the Decide calls, the choices that belong to a person and not the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build.&lt;/strong&gt; Tests first, then code, until the gauntlet (tests, lint, types, and whatever else the repo declares) is green. Then an adversarial code review tries to prove the diff wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence.&lt;/strong&gt; The model writes the evidence. An evidence-vs-plan check holds it against the plan's scenarios, and an evidence-vs-story check holds it against the story's acceptance criteria.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR.&lt;/strong&gt; Only then does it go up for review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The checks are cheap and fast. The median judge run takes under a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using /plan without a review is a first draft
&lt;/h2&gt;

&lt;p&gt;The plan is where I expected the model to shine. It reads the story, reads the code, and writes a clean, numbered plan. It looks done. It almost never is.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F22aehhh2preto34bbr4k.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F22aehhh2preto34bbr4k.png" alt="80% (53 / 66) of plans needed rework before the build started; 69% (45 / 65) of plans the plan check sent back at least once; +9m of 18m in all added to planning, on average, to review the plan and fix the gaps it found" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The adversarial plan review raised gaps on nearly every plan it read. In the 31 plans that logged what happened to them, 124 gaps were fixed in the plan and 66 were declined with a written reason. That second number matters to me. The review is advice, not law, and the model pushes back when the review is wrong. But 124 real fixes across 31 plans is about four holes per plan that would otherwise have become code.&lt;/p&gt;

&lt;p&gt;Planning roughly doubled in agent time. Nine extra minutes per plan is the cheapest fix in this whole process, because every hole caught in the plan is one that never gets built, tested, reviewed, and thrown away.&lt;/p&gt;

&lt;p&gt;So when someone tells me they run &lt;code&gt;/plan&lt;/code&gt; and go straight to build, my answer is that they're building from a first draft.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plan pointed the wrong way
&lt;/h2&gt;

&lt;p&gt;If I could put one number from this whole exercise on a slide, it would come from this section.&lt;/p&gt;

&lt;p&gt;The review agents check whether a plan is complete and buildable. They can't check whether it's heading in the right direction, because the direction lives in someone's head: the customer, the business, how the product is actually used. So the process makes the model stop and ask. Every choice that belongs to a person becomes a &lt;strong&gt;Decide call&lt;/strong&gt;: the question, the options, and the model's recommended default. A person rules on each one before the plan is approved.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyhw569qdwgxghiz9pw26.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyhw569qdwgxghiz9pw26.png" alt="181 Decide calls across 56 plans, about three per plan; 10% (17 / 165) of rulings went against the model's recommended default; 25% (14 / 56) of plans with Decide calls changed direction because a person overruled the model" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read that last tile again. One plan in four was confidently pointed somewhere it shouldn't go. The problem wasn't missing detail. The plan was wrong about what we needed. The review agents grade whether a plan is complete and buildable, and a plan can be perfectly buildable in the wrong direction.&lt;/p&gt;

&lt;p&gt;The model's default was right 90% of the time, which is good. But the 10% it got wrong are the expensive ones. A missed edge case costs you a fix. A plan pointed the wrong way costs you the whole build, built correctly, tested, reviewed, and thrown away. If your process lets the model make those calls silently, you won't find out until the demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing doesn't mean it's ready
&lt;/h2&gt;

&lt;p&gt;Now the build. By the time the adversarial code review runs, the gauntlet is green. Every test the model wrote passes. By the usual definition, the work is done.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpoyb9orciscevovehjp0.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpoyb9orciscevovehjp0.png" alt="48% (31 / 64) of implementations, every test passing, needed rework before the PR; 35 blocking defects found in code whose tests all passed; 54% (19 / 35) of those defects a user would have seen; +12m of 38m in all added to the build, on average, to review the code and fix the defects it found" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what kind of defects they were:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu4iituej5rrk49fa840s.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu4iituej5rrk49fa840s.png" alt="Missed edge case: 10; Wrong behavior: 8; False evidence: 5; Drift from the plan: 4; Security: 3; Test gap: 2; Other: 3" width="798" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few of them, in plain words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security (GH-120):&lt;/strong&gt; extra request fields could overwrite checked values like parent, project or summary, because the guards ran before the fields were merged in. Tests passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling (GH-566):&lt;/strong&gt; if launching a child process failed in an unexpected way, the worker died silently and the child sat there waiting forever. Tests passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test gap (GH-159):&lt;/strong&gt; a new test file was never wired into any test layer, so it never ran and proved nothing. Every test that &lt;em&gt;did&lt;/em&gt; run passed, which is exactly the problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is my favorite, in the way a scary movie is your favorite. Green was a lie, and nothing in the test run could have told me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching the plan isn't matching the story
&lt;/h2&gt;

&lt;p&gt;This is the control I'm proudest of, and the one I'd push hardest on any team doing AI-driven delivery.&lt;/p&gt;

&lt;p&gt;At sign-off the model writes the evidence: here's each requirement, here's the test or run that proves it. The evidence-vs-plan check holds that evidence against the plan. The evidence-vs-story check holds the same evidence against the story's acceptance criteria. Most AI workflows stop at the first one, if they have it at all. The plan is the model's own document, so of course the code matches it.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx6n0f4w77ok9uckfhfi8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx6n0f4w77ok9uckfhfi8.png" alt="31% (14 / 45) of implementations held at sign-off by the evidence-vs-story check: a story requirement had no proof; 25% (4 / 16) of implementations passed the evidence-vs-plan check and failed the evidence-vs-story check in the same round" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That second tile is the scary one. The plan was built. The build succeeded. Every test passed. The evidence-vs-plan check said ready. And the evidence-vs-story check said no, because the plan never covered everything the story asked for.&lt;/p&gt;

&lt;p&gt;My clearest example is GH-566. The story asked for something to work while a pod was running. The tests proved it worked while an agent was running &lt;em&gt;in the same process&lt;/em&gt;. Plan matched, tests green, evidence complete, and we'd built the wrong thing. Only the gate that went back to the original story caught it.&lt;/p&gt;

&lt;p&gt;(16 is a small base. I added the evidence-vs-plan check to the process later, so only 16 implementations went through both.)&lt;/p&gt;

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

&lt;p&gt;None of this is free, so here's the bill:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4285eu20ybp638nqpr1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4285eu20ybp638nqpr1.png" alt="$98 for every review check across all 83 stories, about $0.40 per blocking issue sent back; 6% of the total cost of the work went to review" width="799" height="127"&gt;&lt;/a&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqf89b0lvsj0mtl4z204i.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqf89b0lvsj0mtl4z204i.png" alt="Table of checks: Story check, 0.8 min median, $18.12, 15 of 55 stories needed rework (27%); Adversarial plan review, 0.2 min, $10.18, 30 of 31 plans (97%); Plan check, 0.3 min, $18.08, 45 of 65 plans (69%); Adversarial code review, 0.7 min, $30.94, 18 of 60 implementations (30%); Evidence vs plan, 0.4 min, $7.22, 9 of 32 implementations (28%); Evidence vs story, 0.4 min, $13.73, 14 of 45 implementations (31%)" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The judges run on Sonnet, which is about half the per-token price of the Opus model doing the building. Review took about 10% of the agent's working time and 6% of the cost. Forty cents to catch a defect before a PR is the best deal in my whole workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still got through
&lt;/h2&gt;

&lt;p&gt;After all of that, I went back through every issue and pull request filed after these stories merged, looking for bugs that traced back to one of them.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2ajqjcybnadcqvd2qgy.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2ajqjcybnadcqvd2qgy.png" alt="2% (2 / 83) of stories had a defect reported after merge" width="800" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One was a merge that duplicated an import another story had already added. The other (GH-120) had two bugs in a Jira adapter that a later issue reported without naming the story, so I had to trace them back through git. Not zero. But I'll take 2 out of 83.&lt;/p&gt;

&lt;h2&gt;
  
  
  People still matter
&lt;/h2&gt;

&lt;p&gt;The machines found most of the volume. They checked every plan, every diff, and every piece of evidence, and they're cheap and fast enough to do it on every story. But the 17 Decide calls where a person overruled the model are the ones no agent could have caught, because the right answer wasn't in the code or the story. It was in someone's head. Those are the calls about what we're building and why, and I don't want a model making them. People approve, agents check.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's still a small (but significant) sample.&lt;/strong&gt; 83 stories is enough to see a pattern, not enough to call it a law.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escapes only count what someone reported.&lt;/strong&gt; A bug nobody filed an issue for isn't in my 2%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some reviews never block.&lt;/strong&gt; The adversarial plan review is advice, and 66 of its gaps were declined. I counted only the ones that changed the plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing is the agent's working time,&lt;/strong&gt; with time spent waiting on me left out. It's an estimate built from the logs, not a stopwatch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The new definition of done
&lt;/h2&gt;

&lt;p&gt;Here's where I've landed. The model isn't the problem. It's very good, and it gets better every few months. The problem is treating its first output as its final output because it arrived confident and green.&lt;/p&gt;

&lt;p&gt;Done used to mean "the tests pass and someone read the diff." With this much code coming at us, done now means &lt;strong&gt;the evidence shows we built what the plan said, and the plan covers what the story asked&lt;/strong&gt;, checked by something other than the thing that wrote it. And none of it works unless the tests underneath are real.&lt;/p&gt;

&lt;p&gt;If you want help building this kind of process for your team, that's what we do at &lt;a href="https://pinnsg.com/" rel="noopener noreferrer"&gt;Pinnacle Solutions Group&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have your own numbers on how often AI output is right the first time? I'd love to compare notes. Hit me up on &lt;a href="https://github.com/drmikecrowe" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or wherever you found this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>softwareengineering</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Tests That Would Not Notice</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sat, 10 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/the-tests-that-would-not-notice-4mi6</link>
      <guid>https://dev.to/drmikecrowe/the-tests-that-would-not-notice-4mi6</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdzjm2u6j1mmmo6fvfw3u.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdzjm2u6j1mmmo6fvfw3u.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance (Claude) for structure and formatting. The data, the bugs, and the opinions are entirely my own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Until I started building out an AI-driven SDLC this year, I had never run a mutation test. I'm not sure I could have told you what one was. I've been writing software for a long time, I've shipped plenty of code with a coverage badge on it, and I treated that badge as the answer to "is this tested?" It isn't, and it took an agent pipeline to make me find out.&lt;/p&gt;

&lt;p&gt;Here's the thing: both projects in this post already had significant unit tests and good coverage. Neither one was a neglected codebase. Mutation testing and adversarial review still found a pile of problems in both, and that's the point I care most about. When an AI writes most of your code, your tests are the only thing that proves it's moving in the direction you intended. A suite that runs every line but wouldn't notice a wrong one can't do that job, so improving test quality has gone from a nice-to-have to the core of how I keep AI-written code honest.&lt;/p&gt;

&lt;p&gt;I've written about &lt;a href="https://mikesshinyobjects.tech//posts/2026/2026-08-24-people-approve-agents-check/" rel="noopener noreferrer"&gt;where AI belongs in software delivery&lt;/a&gt; and about &lt;a href="https://dev.to/posts/2026/2026-08-26-the-pr-that-fooled-me/"&gt;the PR that fooled me&lt;/a&gt; into adding adversarial review. Mutation testing came out of the same work. Once agents were writing most of the code, I needed checks that didn't depend on me reading every line, and "the tests pass" turned out to be a much weaker statement than I'd assumed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutation testing in two lines
&lt;/h2&gt;

&lt;p&gt;A mutation tool makes a small change to your code (flip &lt;code&gt;&amp;lt;&lt;/code&gt; to &lt;code&gt;&amp;lt;=&lt;/code&gt;, delete an argument, swap a string), reruns your tests, and checks that some test fails. If no test fails, the mutant "survived", which means your tests would not notice that change if a human or an agent made it for real.&lt;/p&gt;

&lt;p&gt;That's the whole idea. Coverage tells you a line ran. Mutation testing tells you whether anything would notice if that line were wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here's the scenario: a client app with a Python FastAPI backend and a TypeScript/SvelteKit frontend. It had a healthy test suite by the usual measures (about 80% statement coverage on the frontend) and nobody had any reason to think it was undertested. I added mutmut on the backend and StrykerJS on the frontend, then used the survivor lists to decide which tests to write.&lt;/p&gt;

&lt;p&gt;The scope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend (mutmut 3.8):&lt;/strong&gt; 32 Python files, about 2,200 non-blank lines of production code, 1,766 mutants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend (StrykerJS 10):&lt;/strong&gt; 16 TypeScript files, about 1,180 non-blank lines, about 1,070 mutants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total:&lt;/strong&gt; about 3,400 lines in 48 files, roughly 2,800 mutants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stryker only mutated &lt;code&gt;.ts&lt;/code&gt; files. The &lt;code&gt;.svelte&lt;/code&gt; components were out, for reasons I'll get to in the gotchas.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend mutants detected (killed or timed out)&lt;/td&gt;
&lt;td&gt;65% (1,139 of 1,757)&lt;/td&gt;
&lt;td&gt;82% (1,447 of 1,766)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend survivors&lt;/td&gt;
&lt;td&gt;613&lt;/td&gt;
&lt;td&gt;319&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend mutants killed&lt;/td&gt;
&lt;td&gt;257&lt;/td&gt;
&lt;td&gt;723&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend survivors&lt;/td&gt;
&lt;td&gt;464&lt;/td&gt;
&lt;td&gt;141&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend mutants with no covering test&lt;/td&gt;
&lt;td&gt;345&lt;/td&gt;
&lt;td&gt;202&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend tests&lt;/td&gt;
&lt;td&gt;294&lt;/td&gt;
&lt;td&gt;399&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend unit tests&lt;/td&gt;
&lt;td&gt;501&lt;/td&gt;
&lt;td&gt;695&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Some per-file moves on the frontend: a schema validator went from 10% to 77%, an auth module from 34% to 89%, a types module from 15% to 97%. On the backend, the LLM client dropped from 131 survivors to 43 and the index loader from 101 to 42.&lt;/p&gt;

&lt;p&gt;Both "before" columns were taken after I fixed two tool problems, so they aren't a raw first run. A raw first run would have looked much worse, and most of that would have been environment noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually found
&lt;/h2&gt;

&lt;p&gt;None of this was known before I started. Where a survivor led straight to the bug I'll say so, and where it only pointed me at the right module I'll say that instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three real application bugs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The streaming client missed the standard end-of-stream signal.&lt;/strong&gt; It read &lt;code&gt;finish_reason&lt;/code&gt; only from the &lt;code&gt;delta&lt;/code&gt; object, but OpenAI-compatible streams put it on the choice. A quick probe showed tokens after the stop signal still came through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# before the fix
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AFTER-STOP&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# after the fix
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The impact is bounded because a &lt;code&gt;[DONE]&lt;/code&gt; marker normally follows, but a proxy that omits it would leak trailing content or never stop. The existing test passed because it used a non-standard stream shape. A surviving mutant in the stream parser led directly to this one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two of the three chunking strategies silently dropped any text before the first heading.&lt;/strong&gt; The baseline strategy kept it; heading-aware and hybrid didn't. So switching strategies would quietly lose content. It's latent (no current page has text above its first heading, and production uses the baseline), but it was sitting there waiting. A surviving mutant in the section splitter led to it, and the new tests fail on the old code and pass on the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A greedy frontmatter regex made a full index rebuild impossible for a third of the content.&lt;/strong&gt; Any page body with a markdown horizontal rule (&lt;code&gt;---&lt;/code&gt;) got swallowed into the YAML block and crashed the parser. That hit 123 of 354 source pages. Production kept working only because the committed index predated those pages; a rebuild would have failed. The fix was one character, &lt;code&gt;*&lt;/code&gt; to &lt;code&gt;*?&lt;/code&gt;. The survivor list sent me into that module, but I found the bug by probing real data while sizing the chunking problem. The tool narrowed where to look and I made the find.&lt;/p&gt;

&lt;h3&gt;
  
  
  The test suite had bugs too
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A fixture removed a shared auth override&lt;/strong&gt;, so component auth tests returned 401 depending on test order. The default order hid it, and nine tests failed under a different order. mutmut runs the suite in its own order as a "clean run" check, which is how this surfaced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A depth-filter test could not fail.&lt;/strong&gt; Its fixture page was small enough to be filtered out as a stub anyway, so the test passed with the depth filter deleted entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The surprise: a slow tool forced me to look at a slow suite
&lt;/h3&gt;

&lt;p&gt;Mutation testing reruns your tests for every mutant. A suite that takes 48 seconds is a minor annoyance when you run it once, and a real problem when the tool runs it against hundreds of mutants. So before I could get useful sweeps, I had to find out why the tests were slow.&lt;/p&gt;

&lt;p&gt;The first answer was embarrassing. &lt;strong&gt;Two retry tests slept for real&lt;/strong&gt;, 7 seconds each, because nobody had mocked &lt;code&gt;sleep()&lt;/code&gt; in the exponential backoff. The tests were sitting through the actual retry delays. Patching the sleep took those seconds out, and it also let me assert the backoff schedule, which nothing had checked before.&lt;/p&gt;

&lt;p&gt;The second answer was worse. Every test-client startup posted spans and events to a real collector and an Application Insights endpoint, about a second of network time per test. Our unit tests were sending telemetry to production monitoring and nobody knew. A single environment variable (dummy mode in the monitoring library) stopped it.&lt;/p&gt;

&lt;p&gt;Between the two fixes, the backend suite dropped from 48.6 seconds to about 27. Neither came from a surviving mutant. They came from the cost of running mutation testing at all, which made a slow suite too expensive to ignore. Some of the value came from the tool, and some came from the work the tool forced me to do around it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gaps with no bug, but nothing guarding them
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The retrieval core (cosine-similarity search) had no tests at all, zero-vector and ordering behavior included.&lt;/li&gt;
&lt;li&gt;The LLM client's request URL, headers, payload keys, and defaults were untested.&lt;/li&gt;
&lt;li&gt;The embeddings client's batching and order preservation were untested.&lt;/li&gt;
&lt;li&gt;JWT validation had holes around required claims, exact issuer matching, identity-claim fallbacks, and key-rotation refresh.&lt;/li&gt;
&lt;li&gt;The chat markdown renderer, which sanitizes untrusted model output, had no tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one should make anyone nervous. It's the code standing between an LLM's output and your users' browsers, and the coverage report gave no hint it was bare.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I added it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install with the project's existing package managers: mutmut as a Python dev dependency, Stryker and its Vitest runner as npm dev dependencies. Commit the lockfiles.&lt;/li&gt;
&lt;li&gt;Scope it to production code only. Exclude tests, generated output, and vendored code.&lt;/li&gt;
&lt;li&gt;Smoke-test one backend module and one frontend file first.&lt;/li&gt;
&lt;li&gt;Fix environment problems until a clean run works.&lt;/li&gt;
&lt;li&gt;Run the full sweep and read the results as a ranked list of "tests that would not notice."&lt;/li&gt;
&lt;li&gt;Triage. Separate log-message and string-literal survivors from logic survivors, because their value is wildly different.&lt;/li&gt;
&lt;li&gt;Write tests for the logic survivors and run each against the unmodified code. A new test that fails there has found a bug.&lt;/li&gt;
&lt;li&gt;Re-run the affected modules and compare.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Here are some of the things that drove me crazy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The tools copy your tree.&lt;/strong&gt; mutmut runs tests in a copy of the repo, so every file the app reads at import time or in tests (config, content, data) has to be listed to get copied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A dangling symlink crashed the copy.&lt;/strong&gt; Listing source directories one at a time worked around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A macOS-only crash looked like a real result.&lt;/strong&gt; Proxy auto-detection crashed forked workers that built an HTTP client, and mutmut reported 383 mutants as "segfault." Setting the no-proxy variable fixed it. Without that, the LLM and embeddings modules would have looked untestable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts count as detected.&lt;/strong&gt; A mutant that breaks a loop makes a test hang until the time limit, and the tool scores that as a kill. About 170 mutants ended that way, and they dominate the runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stryker can't score Vitest Browser Mode tests.&lt;/strong&gt; All the component tests here use it, so five files sit at 0% even though they have tests. That's also why I didn't mutate the Svelte components: every mutant would have shown "no coverage."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;import.meta.glob&lt;/code&gt; breaks instrumentation.&lt;/strong&gt; Stryker rewrites its string argument, so four files that use it are excluded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No type-checker plugin&lt;/strong&gt;, so some frontend survivors are mutants that wouldn't even compile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run time isn't constant.&lt;/strong&gt; The backend sweep took about 6 minutes alone and about 35 minutes once when other jobs overlapped. I never pinned down why.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Raw wall clock was about 19 hours, but most of that was an overnight gap and a few stretches where I walked away. Capping idle gaps at 20 minutes puts active time at about 4 hours. I sent 12 prompts total. The machine time is small (about 6 minutes for the backend sweep, 2 for the frontend), so nearly all the elapsed time went into writing the tests the survivors pointed at.&lt;/p&gt;

&lt;p&gt;The output was about 300 new tests, roughly 2,300 lines of test code, 230 lines of tooling and CI config, and 5 lines of application fixes. Five lines. That ratio tells you where the work is.&lt;/p&gt;

&lt;p&gt;On tokens: about 190,000 output tokens and 57 million cache-read tokens across 286 turns, and that's a lower bound because the transcripts miss part of the session. The cache-read number looks scary but it's the conversation being re-read each turn, billed far below fresh input. What kept it from ballooning was redirecting every command's output to a file and reading only summaries. Unfiltered mutation output will eat a context window alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this changed how I think about review
&lt;/h2&gt;

&lt;p&gt;This is where the client work connects to my own projects. In &lt;a href="https://github.com/drmikecrowe/harnessed" rel="noopener noreferrer"&gt;harnessed&lt;/a&gt;, I went back through 42 PRs over six weeks and tallied every finding by whatever layer caught it, counting only what the PR body names explicitly:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk9mhaz3ysr0yvavyefua.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk9mhaz3ysr0yvavyefua.png" alt="Which layer pays: 229 findings by what caught them. Adversarial review 114, mutation 54, author re-reading 18, real execution 9, changed-line coverage 9, test suite or existing gate 6, spec review 5, lint 4, CI or external bot 4, property tests 3, static types 2, integration check 1." width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the third bar. "Author re-reading" is me reading the PR, which is the traditional human code review I've done my whole career. It caught 18 findings. Adversarial review caught 114 and mutation testing caught 54. Together those two layers found more than nine times what my own careful reading did.&lt;/p&gt;

&lt;p&gt;I'm not throwing out human review. I still read every PR, and I still approve every merge, because people approve and agents check. But for most of my career, reading the diff &lt;em&gt;was&lt;/em&gt; the quality process, with tests, lint, and types as a safety net underneath. This chart says the safety net and my reading together account for a sliver. Tests, lint, and types combined found 12.&lt;/p&gt;

&lt;p&gt;The cost side matters too. The whole mechanical gauntlet on one PR ran in 81 seconds. An adversarial review round is about 5 minutes. A mutation run is the expensive layer at about 25 minutes. A quarter of my findings for 25 minutes of machine time is a trade I'll take every time.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;I'm not claiming a code review could never find the client-app bugs. But my team cross-reviewed that code, and we missed every one of them. As we accelerate with more AI-generated code, that kind of miss is only going to get more common.&lt;/li&gt;
&lt;li&gt;About half the 319 remaining backend survivors look like log-message or string changes, but that's an unverified heuristic.&lt;/li&gt;
&lt;li&gt;I counted the ~170 timeouts (141 in the chat router alone) as detected and didn't investigate them.&lt;/li&gt;
&lt;li&gt;I wrote the CI workflow and helper commands but haven't run them end to end yet. I ran the tools directly.&lt;/li&gt;
&lt;li&gt;I deliberately set no score threshold. There's no baseline to justify one, and the timeouts and browser-only code need a decision first.&lt;/li&gt;
&lt;li&gt;The harnessed numbers are floors. A finding counts only if the PR body names what caught it, so anything the suite caught earlier never shows up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it as an audit, not a gate
&lt;/h2&gt;

&lt;p&gt;If you take one thing from this: point a mutation tool at a codebase you think is well tested and read the survivor list. Don't make it a CI gate on day one, because the first runs mostly measure your environment problems. Treat the output as a ranked list of places your tests wouldn't notice a change, and go write the tests it asks for.&lt;/p&gt;

&lt;p&gt;I spent years trusting a green coverage report. Four hours with mutmut and Stryker found three bugs, a test that couldn't fail, and my unit tests chatting with production telemetry. That was a well-tested codebase. If the AI is going to write the code, the tests are how I steer it, and I want tests that would actually notice when it drifts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have your own mutation testing stories, or a survivor that turned into a real bug? Hit me up on &lt;a href="https://github.com/drmikecrowe" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or wherever you found this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>testing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The PR That Fooled Me</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Wed, 26 Aug 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/the-pr-that-fooled-me-34dn</link>
      <guid>https://dev.to/drmikecrowe/the-pr-that-fooled-me-34dn</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was written with AI assistance (Claude) for structure and formatting. The PRs, the bug, and the opinions are entirely my own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best-looking PR in my repo had a data-loss race in it, and the model that wrote it reviewed its own work and found nothing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've written before about &lt;a href="https://dev.to/posts/2026/2026-08-24-people-approve-agents-check/"&gt;where AI belongs in software delivery&lt;/a&gt;, and the short version of that post is: people approve, agents check. This post is the origin story. It's three weeks of commit history in one public repo, and I'm going to walk you through it in order, because the order is the argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  Act one: the PR that looked perfect
&lt;/h2&gt;

&lt;p&gt;On July 28 I merged &lt;a href="https://github.com/drmikecrowe/harnessed/pull/169" rel="noopener noreferrer"&gt;PR #169&lt;/a&gt; in &lt;a href="https://github.com/drmikecrowe/harnessed" rel="noopener noreferrer"&gt;harnessed&lt;/a&gt;. Opus wrote most of it. Go look at the PR body, because the body is the point.&lt;/p&gt;

&lt;p&gt;(A word about the repo, since I'm about to link into it four times and I haven't formally announced it. &lt;code&gt;harnessed&lt;/code&gt; is a tool for people who want different configurations of their agentic harness in container isolation: you compose recipes of skills, MCP servers, and services into a named stack, and it launches in a pod with nothing leaking in from your host config. Toolchains come from mise, every dependency is pinned, updates respect a seven-day minimum release age so a poisoned package gets caught before it reaches a build, and images get Snyk and Socket scans at build time. It's my daily driver and I'm dogfooding it hard, but it's alpha: today it supports Claude Code and omp, with more harnesses planned. If that's a tool you've been wanting and you'd like to help shape it, come on in. Otherwise, the announcement post is coming when it's ready for you.)&lt;/p&gt;

&lt;p&gt;It has a measured-cost table justifying the design (a one-line edit to an install script cost 307 seconds through the container build and 4.3 seconds natively, so the installs moved to runtime volumes). It lists two premises that were tested and disproved along the way, with links to where each was recorded instead of quietly dropped. It documents four bugs found during the work, each fixed with a test that fails without the fix.&lt;/p&gt;

&lt;p&gt;That is a better PR writeup than most humans produce. I know because I'm one of the humans. When your AI hands you work at that level of polish, with benchmarks and disproved hypotheses and regression tests, skepticism starts to feel rude. I got comfortable. The polish was doing exactly what polish does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Act two: a second opinion I didn't ask nicely for
&lt;/h2&gt;

&lt;p&gt;Four days later I put up &lt;a href="https://github.com/drmikecrowe/harnessed/pull/185" rel="noopener noreferrer"&gt;PR #185&lt;/a&gt;, a rework of the CLI verbs. Same workflow, same model, same confident writeup: 1,766 tests passing, every removed test accounted for in a table, docs updated.&lt;/p&gt;

&lt;p&gt;This was also the first PR I ran through CodeRabbit, mostly out of curiosity. I had already asked Opus to review its own work. It found nothing worth stopping for.&lt;/p&gt;

&lt;p&gt;CodeRabbit found this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Two concurrent &lt;code&gt;--recipe&lt;/code&gt; invocations with the same recipe set derive the same name. Both can observe &lt;code&gt;preexisting=False&lt;/code&gt; before either mints, so both return a non-&lt;code&gt;None&lt;/code&gt; &lt;code&gt;minted_dir&lt;/code&gt;. [...] If one of the two concurrent launches fails &lt;code&gt;_build_stack&lt;/code&gt; while the other is still using or building the same stack, the failing process deletes a manifest the other process depends on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A classic check-then-act race, in the one code path where concurrent same-name launches aren't exotic (sharing one stack across repos is the design goal, so two repos starting together is the expected case). A failed build could delete a manifest another live launch was using. Data loss, sitting inside a PR whose test table I had just admired.&lt;/p&gt;

&lt;p&gt;Here's the part that actually rattled me. The race wasn't even introduced by #185. It was pre-existing code that the refactor made visible. The model that wrote it, the model that refactored it, and the model that reviewed both had walked past it every time. A differently-trained model looked at it once and stopped.&lt;/p&gt;

&lt;p&gt;That's when the lesson landed. The author always finds a reason the work is right. That's true of me, and it turns out to be true of a frontier model reviewing its own output. Self-review isn't review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Act three: moving the fight upstream
&lt;/h2&gt;

&lt;p&gt;I didn't fix the race in #185. I scoped it out deliberately (that PR was about CLI grammar, and mixing a concurrency fix into it is how you get neither reviewed well) and recorded it as &lt;a href="https://github.com/drmikecrowe/harnessed/issues/287" rel="noopener noreferrer"&gt;issue #287&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By the time that issue got picked up in mid-August, my process had changed shape. Read the issue comments in order and you can see the new machinery:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;DECISION&lt;/strong&gt; comment recording the fix approach, with current line numbers, so the implementer doesn't re-derive the research.&lt;/li&gt;
&lt;li&gt;A full &lt;strong&gt;SPEC&lt;/strong&gt; posted as a comment, before any code existed, with a line I now consider load-bearing: &lt;em&gt;"Approval of this spec is the gate that authorizes implementation."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The spec itself went through adversarial review before I approved it, and the review changed it. Poking holes in a document costs minutes. Poking the same holes in an implementation costs a rewrite.&lt;/li&gt;
&lt;li&gt;A human (me) applying the &lt;code&gt;spec-approved&lt;/code&gt; label, timestamped.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third point deserves a beat. When I started running adversarial reviews against code, the payoff was catching bugs. When I pointed the same treatment at specs, the payoff was bigger: every hole caught at spec time is a hole that never becomes code. The mint-race spec that got implemented was not the spec I first wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Act four: what "done" looks like now
&lt;/h2&gt;

&lt;p&gt;The fix landed as &lt;a href="https://github.com/drmikecrowe/harnessed/pull/414" rel="noopener noreferrer"&gt;PR #414&lt;/a&gt;, and its body is an EVIDENCE document. Not a description of the change. Proof, organized so a reviewer can attack it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;spec-to-test mapping&lt;/strong&gt;: eight scenarios from the approved spec, each mapped to a named test.&lt;/li&gt;
&lt;li&gt;A concurrency test that &lt;strong&gt;deterministically fails against the old code&lt;/strong&gt; (two threads claim ownership) and passes with the fix (one does). Not "we added tests." A reproduction first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Five hand-planted mutants&lt;/strong&gt;, all killed, because if your test suite can't catch a deliberate mistake it wasn't going to catch an accidental one.&lt;/li&gt;
&lt;li&gt;Two rounds of &lt;strong&gt;adversarial review&lt;/strong&gt;, findings fixed and re-checked.&lt;/li&gt;
&lt;li&gt;And a section titled &lt;strong&gt;"Not proven"&lt;/strong&gt;: the container build path the suite never executes, the NFS caveat on advisory locks, the mutation tool that wouldn't run in a worktree, the fact that the final SHA's adversarial pass was substituted rather than re-run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last section is the one I'd defend hardest. Evidence that can't tell you what it doesn't cover isn't evidence, it's marketing. The "Not proven" list is what makes the rest of the document worth believing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The confidence is a constant. The quality is not.&lt;/strong&gt; Every PR in this story arrived with the same certainty. One of them carried a data-loss race. Nothing in the presentation distinguishes them, which means the presentation carries no information. You have to attack the work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-review doesn't work, for silicon or for me.&lt;/strong&gt; The reviewer needs to carry none of the author's assumptions. A fresh session helps. A different model with different training helps more (with one caveat: frontier models share training data and failure modes, so even two-model review has correlated blind spots; it's a design goal, not a guarantee).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upstream beats downstream.&lt;/strong&gt; Adversarial review of code catches mistakes. Adversarial review of the spec prevents them, at a fraction of the cost. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This costs tokens, and it's the cheapest spend in the budget.&lt;/strong&gt; Every adversarial pass is a second model re-reading finished work. I meant it when I called them the cheapest tokens I spent all year: the alternative was shipping the race and paying for it during someone's demo.&lt;/p&gt;

&lt;p&gt;If you want the team-scale version of this, with gates and human sign-off and the process we're building around it at Pinnacle Solutions Group, that's the &lt;a href="https://dev.to/posts/2026/2026-08-24-people-approve-agents-check/"&gt;People Approve, Agents Check&lt;/a&gt; post. This one is just the receipt trail: one repo, three weeks, and the PR that taught me to stop trusting a clean explanation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have your own "the AI sounded so sure" stories? Hit me up on &lt;a href="https://github.com/drmikecrowe" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or wherever you found this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>codereview</category>
    </item>
    <item>
      <title>People Approve, Agents Check</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Mon, 24 Aug 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/people-approve-agents-check-4101</link>
      <guid>https://dev.to/drmikecrowe/people-approve-agents-check-4101</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was written with AI assistance (Claude) for structure and formatting. The design, opinions, and specifics are entirely my own.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I think the industry is measuring the wrong thing, and I think the leaderboard is making software worse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every vendor pitch I've sat through in the last year answers the same question: how much of the work can the AI do? Percentage of code generated. Tickets closed without a human. Autonomy tiers, like a self-driving car. That's a fine question if you're selling a model, but it's the wrong question if you're responsible for an engineering organization, because it smuggles in two assumptions. The first is that the bottleneck is typing. The second, which nobody says out loud in the meeting but everybody hears, is that a successful AI rollout ends with fewer engineers.&lt;/p&gt;

&lt;p&gt;Both are wrong, and the second one is expensively wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this came from
&lt;/h2&gt;

&lt;p&gt;This started with adversarial code review. I began having an independent reviewer, one carrying none of my assumptions, try to break my finished work instead of blessing it. Its entire brief was to find the input that makes the code fail. That alone was worth the tokens.&lt;/p&gt;

&lt;p&gt;I'll admit why I needed it. I had drifted into trusting the high-end models. When Opus hands you code, it hands it to you with total confidence: no hedging, no "you may want to check this," just a clean explanation of why it did what it did. I got comfortable, and you can probably see where this is going. When I started pointing a second model at that output with instructions to break it, I was genuinely shocked at the gap between how certain the first model sounded and how well the code actually held up. The confidence is a constant. The quality is not. Nothing has made me more careful than watching a model I trust get taken apart by a second model with no stake in being right.&lt;/p&gt;

&lt;p&gt;Then I moved the whole thing upstream. I was already writing specs first, so before any code existed I pointed the same adversarial treatment at the spec itself: find the requirement that can't be tested, find the two bullets that contradict each other, find the thing I assumed and never wrote down.&lt;/p&gt;

&lt;p&gt;That turned out to be even more valuable. My specs were full of holes I could not see, because I was the one who put them there, and every hole caught at spec time was a hole that never became code. Reviewing the output catches mistakes; reviewing the intent prevents them. The tokens I spent arguing with a machine about what I actually meant were the cheapest tokens I spent all year.&lt;/p&gt;

&lt;p&gt;That's the part I couldn't stop thinking about. If an adversarial loop does that for one engineer working alone, what does it do for a team? So we started building it into a real process at Pinnacle Solutions Group, the consulting firm I'm part of, and we're genuinely excited about where it's going. This is us thinking out loud about where AI belongs in software delivery, and I'd rather share it while it's still being shaped than wait until it's a polished case study.&lt;/p&gt;

&lt;p&gt;Two disclaimers. Helping engineering orgs put this in place is work Pinnacle Solutions Group sells, so read the whole thing with that in mind. And it's running on a live client engagement right now, being developed with them and fitted to how they already work. I'm describing a process I believe in and am actively proving, not results I can hand you. When I get to what it costs, I'll be specific about what I don't yet know.&lt;/p&gt;

&lt;p&gt;The personal arc, though, is public if you want to check my work. It played out in the open on a side project of mine: &lt;a href="https://github.com/drmikecrowe/harnessed/pull/185" rel="noopener noreferrer"&gt;the review that converted me&lt;/a&gt;, where a differently-trained reviewer found a concurrency bug in code that self-review had blessed, and &lt;a href="https://github.com/drmikecrowe/harnessed/pull/414" rel="noopener noreferrer"&gt;a complete evidence run&lt;/a&gt;, spec through adversarial review through proof, on the fix for that same finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivery breaks at the edges, not in the middle
&lt;/h2&gt;

&lt;p&gt;Think back through the projects that went badly for stupid reasons, not the ones with genuinely hard technical problems. Anecdotally, across the ones I've watched up close, nearly all of them broke at one of two boundaries.&lt;/p&gt;

&lt;p&gt;The first is the handoff from product to engineering. Somebody writes down what they want, and what they write down is incomplete: acceptance criteria you can't actually test, an assumption nobody stated, two requirements that quietly contradict each other. Nobody notices at write time. Somebody notices on day four of the sprint, in a meeting, after the money is already spent.&lt;/p&gt;

&lt;p&gt;The second is the handoff from "the work is done" to "the work is proven." A change gets reviewed by looking at code. Looking at code answers a question (is this reasonable code?) that nobody actually needed answered. The question is whether this change does what product asked for, and how we know.&lt;/p&gt;

&lt;p&gt;The middle, where the typing happens, is mostly fine. That's where every vendor is aiming.&lt;/p&gt;

&lt;p&gt;So we aimed somewhere else, and not out of contrarianism. The speedup in the middle is real, but it's a commodity: every tool on the market delivers it, and your team collects it no matter whose logo is on the invoice. The failures at the edges cost sprints, not minutes, and nobody was selling anything there.&lt;/p&gt;

&lt;p&gt;We put an AI agent at each of those two boundaries, and we gave it a job description with a hard limit on it: &lt;strong&gt;people approve, agents check.&lt;/strong&gt; An agent in this design never approves anything, never merges anything, and never decides what gets built. It reads, it tests, and it reports, and the verdict belongs to a person every time.&lt;/p&gt;

&lt;p&gt;Underneath, there's nothing exotic here. It's spec-driven development feeding test-driven development, wrapped in a workflow that makes both of them non-optional.&lt;/p&gt;

&lt;p&gt;None of the individual pieces is new. Planting a deliberate fault to prove the tests would catch it is mutation testing, an idea from the 1970s. Coverage on changed lines is standard CI configuration. Requirements reviews, design reviews, spec-first, test-first: all decades old. What I'd defend as new is where they sit. When AI writes most of the code, plausible work arrives faster than anyone can judge it, and these old disciplines are how judgment keeps up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 1: AI that protects human judgment
&lt;/h2&gt;

&lt;p&gt;The first gate sits between product and engineering. It runs on a story that's headed into refinement, and it answers one question: is this specified well enough to &lt;em&gt;be&lt;/em&gt; refined?&lt;/p&gt;

&lt;p&gt;An agent reads the story and puts it to this test: could an engineer prove, from what is written here, that the intent was met? That question does a lot of work. It forces every acceptance criterion to be verifiable rather than aspirational. It surfaces the assumption sitting under the third bullet that nobody wrote down. It catches the place where criterion two and criterion five can't both be true.&lt;/p&gt;

&lt;p&gt;This is aimed squarely at helping the people who write those stories. Product and marketing are often not technical, and they are being asked to express intent precisely enough for engineers to manifest it. That's a hard thing to do with no feedback loop. Today the feedback arrives four days into the sprint, in a meeting, in front of people. This gate gives them the same feedback in writing, in minutes, from a machine that doesn't sigh.&lt;/p&gt;

&lt;p&gt;A story that passes moves on to refinement. That's not an agent approving the work; it's an agent confirming that product has finished product's job, so engineering can start engineering's job. Nobody's clear, well-specified ticket sits in a queue waiting for a human to rubber-stamp what an agent already confirmed. What gets built is still decided by people in refinement, downstream of this gate, exactly as it was before.&lt;/p&gt;

&lt;p&gt;A story that fails bounces back to whoever wrote it, with written findings. And not vague ones like "this needs work," but specific ones: &lt;em&gt;criterion three has no observable outcome, here is what's missing; criteria one and four conflict under this condition; this assumes the user is already authenticated and the story never says so.&lt;/em&gt; Bad tickets bounce before they cost a sprint, and they bounce with a document instead of a meeting.&lt;/p&gt;

&lt;p&gt;The part that matters most: the gate does not fill in the gaps. It finds them and hands them back.&lt;/p&gt;

&lt;p&gt;The industry instinct is the opposite. Vague requirement? Let the model guess. It'll produce something plausible, and plausible is very close to correct, and the demo will go great. But deciding what the business actually wants is not a gap-filling exercise. It's the highest-judgment work in the building, done by the people with the context and the scars. Automating a plausible guess over the top of that doesn't remove the work, it just hides it until it's expensive.&lt;/p&gt;

&lt;p&gt;So we deliberately preserve the human conversation at exactly the point where the human is the value. What we change is the price and the timing: earlier, cheaper, in writing, before anyone has spent a sprint on the wrong thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Between the gates: building a specification worth implementing
&lt;/h2&gt;

&lt;p&gt;This is where spec-driven development actually lives, and it's the part I had wrong in my own head until recently. I'd lumped it all into "refinement," which is one word doing two jobs.&lt;/p&gt;

&lt;p&gt;Refinement answers &lt;em&gt;is this item ready&lt;/em&gt;: is it clear, is it testable, roughly how big, in what order. That's product's item, and Gate 1 is what protects it. Building the &lt;strong&gt;Implementation Plan&lt;/strong&gt; answers a different question, &lt;em&gt;how will we build this&lt;/em&gt;, and it belongs to the team. Those are two artifacts with two owners, and collapsing them is how you end up with a product owner refereeing a design argument.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-assembling the context
&lt;/h3&gt;

&lt;p&gt;Once a story clears Gate 1, an agent reads the codebase against it and assembles a context pack: which subsystems this touches, the patterns already in use there, the tests that cover that ground today, the prior art from the last time somebody did something similar.&lt;/p&gt;

&lt;p&gt;This is small and it's cheap and I think it's one of the highest-leverage pieces in the whole process, because it gets produced while nobody is waiting on it. If your team still holds a refinement meeting, everyone walks in already knowing what the story touches instead of speculating out loud, which is most of what sizing actually needs, without paying for a full design on every item in the backlog. Later, it's the input the planning agent needs, already gathered.&lt;/p&gt;

&lt;p&gt;When it runs is a knob, not a rule. A team that plans ahead can build the pack as soon as the story clears the gate. A team running lean can build it just in time, when an engineer picks up the story, so it reflects the codebase as it is that morning instead of three weeks ago. The orchestration bends to the team; the artifact is what matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Then the plan, when someone picks up the work
&lt;/h3&gt;

&lt;p&gt;An engineer takes the story. An agent drafts the Implementation Plan from the approved Requirements and the context pack: approach, the tests that will prove each acceptance criterion, risks, and how we back it out. A second agent, with none of the first one's context, attacks that draft. The engineer resolves the findings, owns the result, and signs it. Then code gets written.&lt;/p&gt;

&lt;p&gt;That agent draft is not a shortcut around the engineer. It's a better starting point than the engineer would produce alone under sprint pressure, because it actually read forty files instead of pattern-matching off four. What it cannot do is decide whether this approach is right for where the system is going, whether the rollback is real, or whether the risk list is honest. That's the signature.&lt;/p&gt;

&lt;p&gt;And when the plan reveals the estimate was wrong, that's the process working. You find out on day one, in writing, with a reason. A plan can bounce a story back the same way Gate 1 does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the agent may fill this gap and not the other one
&lt;/h3&gt;

&lt;p&gt;Gate 1 refuses to fill gaps in the Requirements. Here an agent drafts a whole specification. That looks inconsistent. The difference is the cost of a wrong guess.&lt;/p&gt;

&lt;p&gt;A wrong guess about product intent is unrecoverable. Nothing downstream can detect it. Every test passes, the evidence is clean, and you shipped the wrong thing correctly. A wrong guess about technical approach is checkable. The Requirements check it, an adversarial reviewer checks it, and so does what we call &lt;strong&gt;the gauntlet&lt;/strong&gt;: an automated review that holds the implementation to a standard while it's built, with every changed line tested, the tests proven able to catch a deliberately planted fault, and every check the merge gate will run passing before the work is called done. So does the engineer who has to put their name on it. The blast radius is different, so the rule is different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 2: AI that forces human understanding
&lt;/h2&gt;

&lt;p&gt;The gauntlet ends with output: check results, coverage of the changed lines, planted faults caught. That output says the code was built to standard. It does not say the right thing was built, and treating those as the same claim is exactly the conflation this gate exists to prevent.&lt;/p&gt;

&lt;p&gt;That's what the &lt;strong&gt;Evidence&lt;/strong&gt; document is for. Its shape is simple: each acceptance criterion from the Requirements, how the change satisfies it, and how that was verified. Tests mapped to specific criteria. The gauntlet's results. Screenshots where the change touches something a person looks at. And one section every Evidence document must carry: what is &lt;em&gt;not&lt;/em&gt; proven. Proof that can't state its own limits isn't proof, it's marketing. The document isn't written at the end as paperwork; it accumulates as the work is built. Evidence is the proof that the acceptance criteria were met, assembled from what the gauntlet produced.&lt;/p&gt;

&lt;p&gt;Gate 2 is the formal check on that proof. An agent validates the Evidence against the Requirements before the change is ever put up for review: does what the gauntlet proved actually prove what product asked for? Then the engineer must read the Evidence against the Requirements and personally decide whether the work meets the intent. The question isn't whether the tests are green; it's whether the thing does what was wanted. The AI assembles the proof, and the engineer renders the verdict and signs their name to it.&lt;/p&gt;

&lt;p&gt;So a piece of work carries three named documents by the time it ships. &lt;strong&gt;Requirements&lt;/strong&gt; says what and how we'll know. &lt;strong&gt;Implementation Plan&lt;/strong&gt; says how, before the build. &lt;strong&gt;Evidence&lt;/strong&gt; proves it. Each one carries a human approval, and each one was checked by an agent first.&lt;/p&gt;

&lt;p&gt;But a signature is cheap. Nothing about signing a document stops an engineer from skimming it and signing anyway. If all this gate did was swap "skim a green build and merge" for "skim an Evidence doc and sign," I'd have added ceremony and called it rigor. A process that only asks for a signature gets signatures.&lt;/p&gt;

&lt;p&gt;That's why the signature isn't the mechanism. The next section is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The forcing function: every change becomes knowledge transfer
&lt;/h2&gt;

&lt;p&gt;The engineer who signed the verdict then uses that same Evidence to walk reviewers and QA through the change, out loud, in front of colleagues. It's not a diff review, it's a narrative: here's what product asked for, here's how this satisfies it, here's how we know.&lt;/p&gt;

&lt;p&gt;That's the part you cannot fake. You can sign a document you didn't read, but you cannot stand in front of two colleagues and explain a change you don't understand, and everyone in the room knows it within about ninety seconds. The Evidence document isn't the enforcement. It's the script for a conversation that &lt;em&gt;is&lt;/em&gt; the enforcement.&lt;/p&gt;

&lt;p&gt;"Out loud" flexes with the team. For a distributed group that means a live call or a recorded walkthrough rather than a conference room, and the rule holds either way: the engineer explains the change to people who can ask questions.&lt;/p&gt;

&lt;p&gt;This is the thing every engineering leader is quietly afraid of: an engineer generating a change they don't understand, skimming a green build, and shipping it. I can't make that structurally impossible; anyone who tells you they can is selling. What I can do is put the moment of "explain this to your peers" between the AI's output and production, and make it the normal cost of shipping. Not understanding your own change stops being invisible.&lt;/p&gt;

&lt;p&gt;And then think about what that does over a quarter. Every single change becomes a teaching moment, delivered by someone who is required to understand it well enough to defend it. The team learns how the system is changing, update by update, from a person rather than from a diff nobody reads. Institutional knowledge stops being a thing that lives in three people's heads and starts being a thing the process produces as a byproduct.&lt;/p&gt;

&lt;p&gt;This is the same set of models everyone else is using, aimed at a different problem. The difference isn't the tooling; it's where the human sits, and what the process makes them do when they get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your engineers matter more now, not less
&lt;/h2&gt;

&lt;p&gt;Nothing in this design is aimed at needing fewer engineers. It's aimed at raising the quality of what your engineers ship when they work with AI. Those are different goals, and they lead to opposite decisions at nearly every fork.&lt;/p&gt;

&lt;p&gt;A model will produce something plausible for almost any request. Plausible is the dangerous part: it compiles, reads well in review, passes the tests it was written alongside, and quietly does the wrong thing. The only reliable defense is a person who knows the system, knows what was actually asked for, and is equipped to tell the difference. No model removes that need, because judging whether the AI did what was intended is itself a judgment about intent.&lt;/p&gt;

&lt;p&gt;So the volume of code goes up and the value of engineering judgment goes up with it. Every gate exists to put a competent engineer in front of the right question at the right moment, with the evidence in hand to answer it. Gate 1 hands them a request worth building. Gate 2 hands them the proof and makes them rule on it. The walkthrough keeps the whole team sharp enough to keep doing that as the system grows.&lt;/p&gt;

&lt;p&gt;An org that cuts engineers to pay for AI ends up with more code and less capacity to judge it. That is the worst version of this. If your team gets faster, the win is that they take on work they couldn't before, not that there are fewer of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs
&lt;/h2&gt;

&lt;p&gt;This is the part vendors skip, so here it is up front: this process is slower than what the market currently expects from AI. Gate 1 adds a loop before refinement. The Implementation Plan adds a draft and an adversarial pass before code. Tests written from acceptance criteria are slower to write than tests written from finished code. Evidence takes real effort to produce, and the walkthrough takes real time from more than one person.&lt;/p&gt;

&lt;p&gt;The walkthrough is the line I expect the most pushback on, and the pushback is fair: multiple colleagues per change, out loud, does not scale like a CI job. I'm not going to argue it away, because it's the deliberate purchase. Quality of what ships and a team that understands what shipped are the two things this whole design exists to buy, and the walkthrough is where both get paid for. How deep it goes, and for which changes, is one of the things the current engagement is calibrating.&lt;/p&gt;

&lt;p&gt;It costs more tokens, too. Every adversarial pass is a second model reading work the first model already did. The context pack is a full codebase read before anyone has picked up the story. Attacking a spec, then attacking an implementation plan, then validating Evidence against the Requirements: none of that is free, and it is spend you would not have if you simply asked a model for the code and merged what came back.&lt;/p&gt;

&lt;p&gt;I think it's the best money in the budget. I called those the cheapest tokens I spent all year and I meant it literally: you are buying rework you never have to do, at a price you can see up front. But it's a real line item, it's larger than a single-agent workflow, and a client should agree to it going in rather than discover it on the first invoice. Spend ceilings and usage alerts go in on day one for exactly that reason. Adoption shouldn't come with an open tab.&lt;/p&gt;

&lt;p&gt;The middle is genuinely faster. An engineer with AI leverage and a clear spec outruns the same engineer without it, and it isn't close. But that gain is smaller than the number on the slide, because we are deliberately spending part of it on tests and proof.&lt;/p&gt;

&lt;p&gt;That said, the paragraphs above make this sound slower than it is. Most of what this process standardizes should already be standard operating procedure in most organizations: tests tied to the requirements, review that checks intent, proof that the work does what was asked. I'm not piling new work on top of delivery. What I'm mostly doing is refusing to let AI's speed become the excuse to skip work that was always owed. The velocity this trims is the false kind: the perceived gains of merging plausible code fast and paying it back later as rework.&lt;/p&gt;

&lt;p&gt;So what I'm claiming is confidence. Fewer sprints spent building the wrong thing, fewer changes that pass review and fail intent, and a team that understands what it shipped. If the number you're optimizing is lines per day, I'm the wrong call. If it's the ratio of shipped work that actually did what was asked, this is the trade I'd make.&lt;/p&gt;

&lt;h2&gt;
  
  
  What holds it up
&lt;/h2&gt;

&lt;p&gt;The gates only work if what they're checking is real. Two supporting pieces do that work.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The gauntlet.&lt;/strong&gt; Tests come from the acceptance criteria, not from the finished code, so they verify what was asked for instead of confirming whatever got built. The gauntlet's job is to prove that testing is real: every changed line covered, and planted faults confirming the suite would catch a genuine mistake. A suite that can't catch a deliberate mistake wasn't going to catch an accidental one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adversarial review.&lt;/strong&gt; An independent AI reviewer, carrying none of the author's assumptions, is briefed to break the work: find the input that makes it fail. Confirmed findings get fixed and re-checked. This is where the whole thing started for me, and it's still the piece I trust most. Self-review doesn't work. The author always finds a reason the work is right. One caveat: two frontier models share training data and failure modes, so this independence is a design goal with limits. The reviewer has none of the author's context, and that catches a lot, but it can still share the author's blind spots.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this doesn't fit
&lt;/h2&gt;

&lt;p&gt;This process assumes you know what you're building. Exploratory work, spikes, R&amp;amp;D where the spec is the thing you're trying to discover: gating that behind Requirements is backwards, and we don't try. The honest artifact there is a timebox and a writeup, and the process starts after.&lt;/p&gt;

&lt;p&gt;It also assumes changes worth the ceremony. A dependency bump or a typo fix doesn't need an Implementation Plan and a walkthrough, and a process that pretends it does will get routed around within a month. Part of what we're calibrating with the current client is exactly that tiering: which changes get the full treatment, which get the gauntlet alone, and who decides.&lt;/p&gt;

&lt;p&gt;And a three-person team probably shouldn't run this as written. The walkthrough is priced for teams big enough that knowledge silos are a real risk. Below that size everyone already knows every change, and the gates worth keeping are the two adversarial reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it lands in your org
&lt;/h2&gt;

&lt;p&gt;We customize the gates to fit, and the fit runs in two directions. The gates can hook onto the states your team already has: no new methodology, nothing replaced, no migration. Or they can anchor a leaner workflow, because once agents build the context, draft the plan, and prove the work, some of your ceremonies stop earning their place on the calendar, and a team that notices is free to act on it. Which direction to take is your call. The gates hold either way.&lt;/p&gt;

&lt;p&gt;And it's built to end. We lead the first run, coach the second, and step back to review only for the third. If your team still needs us on run four, we designed it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to ask the next vendor, including us
&lt;/h2&gt;

&lt;p&gt;If you're evaluating AI in your delivery process, the autonomy percentage on the slide tells you almost nothing about whether your delivery will improve. Three better questions, and I'd expect to answer all of them myself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does the human judgment live, and what protects it?&lt;/strong&gt; If the answer is that the AI fills in whatever product left vague, you're buying a machine that converts unclear thinking into shipped code at high speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you prove the work meets the intent?&lt;/strong&gt; If the answer is "code review," ask what question code review actually answers. Then ask who on the team will be able to explain the change six months from now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does this slow down?&lt;/strong&gt; Any honest process has a cost. A vendor who reports only acceleration is either not measuring or not telling you.&lt;/p&gt;

&lt;p&gt;And ask what the vendor thinks happens to your engineers. If the pitch is a headcount line, walk. We didn't get here by asking how much of the job the AI could take. We asked where delivery breaks, and the answer was the same two places it's always broken. The machines are genuinely useful there. They just aren't in charge there, and they need better engineers around them than they did before, not fewer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is what I think about most days right now. We're building it with a client as I write this, shaping it to their process rather than handing them ours, and the parts that survive contact with a real team are the parts worth keeping. If you're wrestling with the same question of where AI belongs in your delivery process, and how you'd prove it worked, I'd genuinely like to hear how you're approaching it, whether or not you ever talk to us. Find me on &lt;a href="https://github.com/drmikecrowe" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or wherever you found this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>sdlc</category>
      <category>agents</category>
    </item>
    <item>
      <title>Context Binoculars: Understanding LLM Context Windows</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sat, 13 Sep 2025 04:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/context-binoculars-understanding-llm-context-windows-2h95</link>
      <guid>https://dev.to/drmikecrowe/context-binoculars-understanding-llm-context-windows-2h95</guid>
      <description>&lt;p&gt;Remember as a kid (or in my case as an adult) when you pulled out binoculars and gazed around the room? It’s a bit disorienting for sure, but also fun. And I guess if you’ve ever wondered about what I do for fun, now you know.&lt;/p&gt;

&lt;p&gt;It struck me the other day that this was the ideal representation for how LLM context works. While my demonstration you’re going to see below is extreme, this is the reality of what we’re facing when we’re trying to get LLMs to do big projects. The context window is everything, and you’re hearing more and more about context engineering in online articles. Ensuring that the LLM has exactly the right context greatly improves the accuracy of the results that you get out the other end.&lt;/p&gt;

&lt;p&gt;There's not much more to this article. All I'm really trying to do is viscerally explain what the context window is and give you a feeling of why managing your context is so important.&lt;/p&gt;

&lt;p&gt;Check out the visual demonstration here: &lt;a href="https://drmikecrowe.github.io/context-binoculars/" rel="noopener noreferrer"&gt;context-binoculars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>The Ultimate Design Review: Orchestrating AI with Task-Based Workflows - Part 6 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/the-ultimate-design-review-orchestrating-ai-with-task-based-workflows-part-6-of-6-1km0</link>
      <guid>https://dev.to/drmikecrowe/the-ultimate-design-review-orchestrating-ai-with-task-based-workflows-part-6-of-6-1km0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 6 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the final installment in our six-part series on building an AI-assisted development workflow. We’ve set up our infrastructure, taught the AI our coding standards, and established a robust “memory bank” of project rules. Now, it’s time to put it all to the test with the ultimate challenge: a comprehensive, end-to-end design review of the entire codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Want to see what we're building? I've created two example posts that show the actual output from this automated design review system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://mikesshinyobjects.tech/posts/2025/2025-09-06-1-design-review-feedback/" rel="noopener noreferrer"&gt;Design Review Feedback Example&lt;/a&gt;&lt;/strong&gt; - See the comprehensive analysis that the AI generates, including architectural strengths, critical issues, and detailed findings across all layers of the codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://mikesshinyobjects.tech/posts/2025/2025-09-06-2-design-review-action-items/" rel="noopener noreferrer"&gt;Design Review Action Items Example&lt;/a&gt;&lt;/strong&gt; - See how the AI transforms the analysis into a structured, actionable backlog with 18 major tasks and 72 subtasks, each with clear implementation steps and acceptance criteria.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These examples show the real output from running this system on an actual codebase. The AI identified critical architectural violations, generated specific improvement tasks, and created a comprehensive project plan - all automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series Overview:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/mcp-servers-ports-and-sharing-part-1-of-6-4agk"&gt;MCP Servers, Ports, and Sharing - Setting up the foundation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/my-eslint-config-was-a-mess-i-asked-an-ai-to-fix-it-part-2-of-6-4g47"&gt;ESLint Configuration Refactoring - Cleaning up tooling with AI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/how-i-taught-my-ai-pair-programmer-to-be-our-teams-tailwind-css-cop-part-3-of-6-b0o"&gt;Custom Architectural Rules - Teaching AI to enforce design patterns&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/from-linter-chaos-to-orchestrated-tasks-part-4-of-6-23ah"&gt;Task Orchestration - Managing complex refactoring workflows&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 5:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/project-rules-for-ai-part-5-of-6-560c"&gt;Project Rules for AI - Creating effective memory banks and guidelines&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 6:&lt;/strong&gt; &lt;a href="https://dev.to/drmikecrowe/the-ultimate-design-review-orchestrating-ai-with-task-based-workflows-part-6-of-6-1km0"&gt;The Ultimate Design Review - Putting it all together&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Challenge: From “Codebase” to “Action Plan”
&lt;/h2&gt;

&lt;p&gt;How do you review an entire &lt;code&gt;src/&lt;/code&gt; directory? You can’t just feed it to an AI and say, “find problems.” The context is too large, the potential feedback is too broad, and the output would be a wall of text, not an actionable plan. To understand the issue, grab a pair of binoculars and use them in your office: Have a coworker toss an index card somewhere in your office and then search for that with the binoculars. If you have a really large code base, have your coworker put it in the office somewhere. That’s what your fighting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The binoculars symbolize the AI context. It’s a fixed width window of information the AI can maintain&lt;/li&gt;
&lt;li&gt;The index card is what you are asking the AI to find&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My goal was to create a system that could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Systematically analyze the entire codebase in manageable chunks.&lt;/li&gt;
&lt;li&gt;Evaluate each chunk against multiple, specific architectural rules.&lt;/li&gt;
&lt;li&gt;Produce a structured, prioritized, and actionable list of tasks.&lt;/li&gt;
&lt;li&gt;Be entirely automated and repeatable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To achieve this, I created a multi-layered prompting strategy that uses the &lt;a href="https://github.com/pinkpixel-dev/taskflow-mcp" rel="noopener noreferrer"&gt;TaskFlow MCP server&lt;/a&gt; to orchestrate the entire workflow. The process is driven by two key prompt files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Master Plan: &lt;code&gt;new-design-review.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first file, &lt;code&gt;@memory-bank/prompts/design-review/new-design-review.md&lt;/code&gt;, is the high-level orchestrator. It doesn’t perform the analysis itself; it instructs the AI on how to set up the entire project plan within TaskFlow tasks.&lt;/p&gt;

&lt;p&gt;Here’s the strategy it lays out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create Two Buckets:&lt;/strong&gt; The first thing it does is create two separate requests in the task server: one for the &lt;em&gt;analysis process&lt;/em&gt; and one for the &lt;em&gt;action items&lt;/em&gt;. This is a critical separation of concerns. The analysis tasks will be marked as “done” once the review is complete, but the action items will form a living backlog of work to be done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Divide and Conquer:&lt;/strong&gt; It then breaks the entire &lt;code&gt;src/&lt;/code&gt; directory into logical chunks: Core App Files, Components, Services, Machines, Hooks, etc. For each chunk, it creates a parent task in the “analysis” request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Mini-Review Subtasks:&lt;/strong&gt; This is where the granularity comes in. For each parent task (e.g., &lt;code&gt;DR Part 3 - Components (Dashboard)&lt;/code&gt;), the prompt instructs the AI to create four specific subtasks:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Instruction Manual: &lt;code&gt;request-2-instructions.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If the first prompt is the “what,” then &lt;code&gt;@memory-bank/prompts/design-review/request-2-instructions.md&lt;/code&gt; is the “how.” This file is a set of instructions for the AI on how to behave &lt;em&gt;while it is executing&lt;/em&gt; the analysis subtasks.&lt;/p&gt;

&lt;p&gt;Its most critical directive is this: &lt;strong&gt;You MUST create new tasks in the “Action Items” request for every issue you find.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It strictly forbids the AI from simply summarizing its findings. Instead, it must translate every identified improvement into a well-formed task, complete with a title, description, and severity, and add it to the other request bucket. This is what turns a qualitative review into a quantitative project plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results: An Actionable Backlog
&lt;/h2&gt;

&lt;p&gt;After running the AI through this workflow, the &lt;code&gt;taskflow-mcp&lt;/code&gt; server populates a YAML file with the results. The file from my design review, &lt;code&gt;@DESIGN-REVIEWS/2025-08-19/tasks.yaml&lt;/code&gt;, is over 800 lines long, but here are a few snippets that show the power of this system.&lt;/p&gt;

&lt;p&gt;First, you can see the completed analysis task for the “Core App Files,” with its four subtasks all marked as done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- id: task-62
  title: DR Part 1 - Core App Files
  description: |-
    Analyze the following files for design patterns, code quality, and architectural consistency:nn- src/App.tsxn- src/appMachineContext.tsn- src/config.tsn- src/context/DashboardDataContext.tsx
    ...
  done: true
  subtasks:
    - id: subtask-71
      title: Overall Design Analysis
      done: true
    - id: subtask-72
      title: Effect Pattern Analysis
      done: true
    - id: subtask-73
      title: Component Styling Analysis
      done: true
    - id: subtask-74
      title: State Machine Analysis
      done: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More importantly, the &lt;em&gt;real&lt;/em&gt; output is the list of actionable tasks generated in the other request. The AI identified dozens of specific, granular improvements, each perfectly formatted according to the instruction prompt.&lt;/p&gt;

&lt;p&gt;Here are a few examples of the generated tasks:&lt;/p&gt;

&lt;p&gt;A high-priority refactoring task based on our Effect pattern rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- id: task-98
  title: Add Effect-based validation to ExcelService
  description: ExcelService.ts contains complex validation logic in generateExpenseReport function. Add Effect-based validation using E.succeed().pipe(E.filterOrFail()) pattern for report and expense data validation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A task to fix a clear violation of our Tailwind CSS architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- id: task-139
  title: Fix EmptyState.tsx UI component margin violations
  description: Remove margin classes from EmptyState.tsx component. The component contains margin classes mb-4, mb-2, mb-6 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A task for a significant architectural improvement to break up a monolithic file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- id: task-99
  title: Extract ExcelService helper functions to separate modules
  description: ExcelService.ts is a 485-line monolith with many helper functions. Extract helper functions into separate modules (excel-formatting.ts, excel-data.ts, excel-styles.ts) to improve maintainability and testability.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion: The Sum of the Parts
&lt;/h2&gt;

&lt;p&gt;This automated design review is the culmination of our entire AI-assisted workflow. It seamlessly integrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Agent Protocol (&lt;code&gt;AGENTS.md&lt;/code&gt;):&lt;/strong&gt; Provides the core principles for the AI’s analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Project Rules (&lt;code&gt;project-rules/&lt;/code&gt;):&lt;/strong&gt; Act as the specific, enforceable standards for the review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Prompting Strategy (&lt;code&gt;prompts/&lt;/code&gt;):&lt;/strong&gt; Orchestrates the complex workflow, breaking it down into manageable steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Task Server (&lt;code&gt;taskflow-mcp&lt;/code&gt;):&lt;/strong&gt; Captures the output, transforming a review into an actionable project backlog.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By investing the time to teach the AI &lt;em&gt;how&lt;/em&gt; we build software, we’ve unlocked the ability to automate one of the most time-consuming and critical parts of the development lifecycle. We’ve moved beyond simple code generation to a world where our AI partner can actively help us manage quality, reduce technical debt, and enforce architectural consistency at scale. It’s a powerful glimpse into the future of software development.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Project Rules for AI - Part 5 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/project-rules-for-ai-part-5-of-6-560c</link>
      <guid>https://dev.to/drmikecrowe/project-rules-for-ai-part-5-of-6-560c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 5 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the fifth installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of building an AI-assisted development environment, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;In the previous posts, we’ve taught our AI assistant &lt;em&gt;how&lt;/em&gt; to understand and enforce specific architectural rules. We’ve built a robust system for linting, testing, and managing complex tasks. But how do we ensure the AI behaves consistently and predictably over the long term? How do we give it a “personality” and a “memory” that aligns with our project’s philosophy?&lt;/p&gt;

&lt;p&gt;The answer lies in creating a comprehensive set of guidelines that the AI can consult before every action. In my setup, this is split into two key parts: the &lt;strong&gt;Agent Protocol&lt;/strong&gt; and the &lt;strong&gt;Project Rules&lt;/strong&gt;. Together, they form the “brain” of my AI development partner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agent Protocol: Defining the Personality
&lt;/h2&gt;

&lt;p&gt;The first piece of the puzzle is the &lt;code&gt;memory-bank/AGENTS.md&lt;/code&gt; file. I think of this as the AI’s constitution or its core programming. It doesn’t contain project-specific code rules; instead, it defines the AI’s high-level behavior, its interaction style, and its core principles.&lt;/p&gt;

&lt;p&gt;You can see some of the key sections from my &lt;code&gt;AGENTS.md&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core Philosophy &amp;amp; Attitude&lt;/strong&gt; : This sets the tone. I want an “Expert Peer,” not a subservient assistant. It should be direct, proactive, and value clean code principles above all else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interaction Protocol&lt;/strong&gt; : This defines how we communicate. No lectures, no fluff. It must clarify ambiguity and warn me if I’m asking it to do something that violates a convention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Generation &amp;amp; Modification&lt;/strong&gt; : This governs how it writes code. It should edit files directly, respect formatting, and, most importantly, follow a specific code review process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context &amp;amp; Memory Bank&lt;/strong&gt; : This establishes the hierarchy of knowledge. The &lt;code&gt;memory-bank/&lt;/code&gt; is the single source of truth, more important than the codebase, conversation history, or its own external knowledge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This file establishes a baseline of behavior. It ensures that no matter what the specific task is, the AI approaches it with the same professional, efficient, and principle-driven mindset.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project Rules: The Letter of the Law
&lt;/h2&gt;

&lt;p&gt;If the &lt;code&gt;AGENTS.md&lt;/code&gt; is the constitution, the files in &lt;code&gt;memory-bank/project-rules/&lt;/code&gt; are the specific, legally-binding statutes of the project. These are granular, often file-specific rules that the AI &lt;em&gt;must&lt;/em&gt; follow.&lt;/p&gt;

&lt;p&gt;These rules cover everything from technology usage to coding patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;00-pnpm.md&lt;/code&gt;: “Always use pnpm for package management. Period.”&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;02-effect-pattern.md&lt;/code&gt;: A detailed, multi-point guide on how to use the Effect library, including import styles, composition patterns, and where &lt;code&gt;E.runPromise&lt;/code&gt; is allowed to be called (hint: only in XState machines).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;08-tailwind.md&lt;/code&gt;: A treatise on our Tailwind CSS architecture, enforcing the strict separation of layout and style components.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;09-state-machine-patterns.md&lt;/code&gt;: Defines the actor-based hierarchy for our XState implementation, ensuring actions are routed correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These rules are not suggestions; they are directives. The &lt;code&gt;AGENTS.md&lt;/code&gt; file instructs the AI to treat the &lt;code&gt;memory-bank/&lt;/code&gt; as its authoritative source of truth, and these files are the content of that memory. When I ask the AI to write a new component, it knows it must use CVA, it must not include margins, and it must get its available actions from a state machine actor—not because I told it to in the prompt, but because the rules demand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the Rules into Practice: Prompting with Precision
&lt;/h2&gt;

&lt;p&gt;Having this well-documented set of rules is powerful, but the real leverage comes from how I use them to guide the AI’s work. The rules aren’t just a passive library; they are an active part of my prompting strategy.&lt;/p&gt;

&lt;p&gt;A perfect example is my process for a comprehensive design review, which will be the topic of the next blog post. I don’t just ask the AI to “review the code.” I give it a highly structured plan that explicitly references the project rules at each step.&lt;/p&gt;

&lt;p&gt;Using a task planning tool, I create a master “Design Review” request, which is then broken down into smaller, logical chunks (e.g., “DR Part 1 - Core App Files”, “DR Part 2 - Components”). For each of these chunks, I create a series of subtasks, and this is where the magic happens. The description for each subtask points the AI to the exact rule it needs to enforce.&lt;/p&gt;

&lt;p&gt;For example, the subtask for analyzing Effect patterns looks like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Title&lt;/strong&gt; : “Effect Pattern Analysis”&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Description&lt;/strong&gt; : “Analyze Effect usage against &lt;code&gt;memory-bank/project-rules/02-effect-pattern.md&lt;/code&gt;. Identify pattern violations, missing Effect implementations, and improvement opportunities. Generate specific action items.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I do the same for other concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Component Styling Analysis&lt;/strong&gt; : “Review component styling against &lt;code&gt;memory-bank/project-rules/08-tailwind.md&lt;/code&gt;…”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Machine Analysis&lt;/strong&gt; : “Assess state machine implementations against &lt;code&gt;memory-bank/project-rules/09-state-machine-patterns.md&lt;/code&gt;…”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach transforms the memory bank from a simple knowledge base into a set of precise instruments. I can direct the AI’s focus with surgical accuracy, ensuring that each part of the codebase is evaluated against the correct, pre-defined standards. It’s a clear, explicit contract: “For this task, these are the rules that matter.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The Killer Feature: The AI Code Review
&lt;/h2&gt;

&lt;p&gt;This brings me to the single most valuable instruction in the entire system. Buried in the &lt;code&gt;AGENTS.md&lt;/code&gt; file is a simple-sounding, yet profound, directive:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Code Review Process&lt;/strong&gt; : After making ANY significant code changes, conduct an internal round table discussion with David Thomas (The Pragmatic Programmer), Andrew Hunt (The Pragmatic Programmer), and Uncle Bob (Clean Code). If they would approve, proceed; if not, iterate until they would approve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where the magic happens.&lt;/p&gt;

&lt;p&gt;This instruction forces the AI to pause and reflect. It takes its generated code, which already conforms to the strict &lt;strong&gt;Project Rules&lt;/strong&gt; I’ve pointed it to, and then evaluates it against the higher-level principles of clean, pragmatic, and maintainable code embodied by these three legends of software engineering.&lt;/p&gt;

&lt;p&gt;It’s a two-layer filter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the code follow the rules?&lt;/strong&gt; (e.g., Is the Effect &lt;code&gt;pipe()&lt;/code&gt; structured correctly? Are the Tailwind classes semantic?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the code &lt;em&gt;good&lt;/em&gt;?&lt;/strong&gt; (e.g., Is it simple? Is it maintainable? Is there a better way to do this?)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This internal dialogue has been a game-changer. The AI doesn’t just produce code that works; it produces code that is thought-out, clean, and aligned with the project’s deepest architectural and philosophical goals. It catches things I would miss in a manual code review, and it does it instantly. It’s like having a senior architect constantly looking over your shoulder, providing expert feedback in real-time.&lt;/p&gt;

&lt;p&gt;By combining a high-level “personality” with a set of explicit, enforceable project rules, and then actively using those rules in our daily prompts, we create an AI assistant that is not just a tool, but a true partner in the development process.&lt;/p&gt;

&lt;p&gt;In the final post of this series, we’ll put all these pieces together and see how this system performs in a full-scale, complex design review.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>How I Taught My AI Pair Programmer to Be Our Team's Tailwind CSS Cop - Part 3 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/how-i-taught-my-ai-pair-programmer-to-be-our-teams-tailwind-css-cop-part-3-of-6-b0o</link>
      <guid>https://dev.to/drmikecrowe/how-i-taught-my-ai-pair-programmer-to-be-our-teams-tailwind-css-cop-part-3-of-6-b0o</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 3 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the third installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of building an AI-assisted development environment, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;We’ve all been there. You start a new project with Tailwind CSS, and everything is beautiful. The utility-first approach is fast, flexible, and keeps you right in your HTML. But as the project grows and the team expands, the CSS landscape can start to feel like the Wild West. Utility classes get sprinkled everywhere, components start to blur the lines between structure and style, and soon you’re overriding margins and fighting for specificity.&lt;/p&gt;

&lt;p&gt;I love Tailwind, but I knew we needed to introduce some architectural discipline before things got out of hand. The problem is, architectural rules are only as good as their enforcement. Documentation gets stale, and nagging in code reviews doesn’t scale.&lt;/p&gt;

&lt;p&gt;So, I had a thought. What if I could teach our team’s AI pair programmer to be our automated style cop? What if it could not only understand our rules but actively enforce them?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Convention: A Three-Tier System for Sanity
&lt;/h2&gt;

&lt;p&gt;First, we needed a clear, simple convention. We decided on a three-tier component architecture designed to enforce a strict separation of concerns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Layout Components (&lt;code&gt;src/components/layout/&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI Components (&lt;code&gt;src/components/ui/&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature &amp;amp; Page Components (&lt;code&gt;src/pages/&lt;/code&gt;, etc.)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This system ensures that our UI components are truly reusable and that our layout logic is centralized and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Hiring an AI for Architectural Enforcement
&lt;/h2&gt;

&lt;p&gt;With the convention defined, I turned to my AI assistant. I described the three-tier system and asked, “Can you build a custom ESLint rule to enforce this?”&lt;/p&gt;

&lt;p&gt;Working with the AI was like having a hyper-competent junior developer who knew the ESLint AST (Abstract Syntax Tree) better than I ever will. I provided the high-level architectural goals, and it handled the implementation details.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Custom Plugin Works
&lt;/h3&gt;

&lt;p&gt;The resulting ESLint rule is a simple but powerful detective. For every component file, it performs three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Location Check&lt;/strong&gt; : First, it looks at the component’s file path. Does it live in &lt;code&gt;src/components/layout/&lt;/code&gt;, &lt;code&gt;src/components/ui/&lt;/code&gt;, or somewhere else? This determines which rulebook to apply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class Inspection&lt;/strong&gt; : Next, it parses the JSX and extracts all the Tailwind classes from the &lt;code&gt;className&lt;/code&gt; prop. It’s smart enough to handle string literals and basic template literals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule Application&lt;/strong&gt; : Finally, it applies the logic:

&lt;ul&gt;
&lt;li&gt;If it’s a &lt;strong&gt;UI Component&lt;/strong&gt; , it scans the class list for any margin patterns (&lt;code&gt;m-*&lt;/code&gt;, &lt;code&gt;mx-*&lt;/code&gt;, &lt;code&gt;mt-*&lt;/code&gt;, etc.) and flags them as errors.&lt;/li&gt;
&lt;li&gt;If it’s a &lt;strong&gt;Layout Component&lt;/strong&gt; , it checks for any non-layout classes (like &lt;code&gt;bg-red-500&lt;/code&gt;, &lt;code&gt;font-bold&lt;/code&gt;, etc.) and flags those.&lt;/li&gt;
&lt;li&gt;If it’s a &lt;strong&gt;Feature/Page Component&lt;/strong&gt; , it does nothing. They are free to compose as they see fit.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI generated the rule file, a utility file to hold the class definitions, and even modified our main &lt;code&gt;eslint.config.js&lt;/code&gt; to wire it all up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results: The Moment of Truth
&lt;/h2&gt;

&lt;p&gt;I ran the linter across our codebase, and it worked perfectly. It immediately flagged several components that were violating our new convention. Here’s a summary of what it caught:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AppLayout.tsx &amp;amp; Sidebar.tsx&lt;/strong&gt; : These core layout components were caught using styling classes like &lt;code&gt;bg-gray-100&lt;/code&gt;, &lt;code&gt;text-purple-700&lt;/code&gt;, and &lt;code&gt;font-bold&lt;/code&gt;. The linter correctly identified these as style concerns that should not be in a layout component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EmptyState.tsx &amp;amp; ErrorState.tsx&lt;/strong&gt; : These UI components had &lt;code&gt;mb-4&lt;/code&gt; and &lt;code&gt;mt-2&lt;/code&gt; margins, violating our “marginless UI” rule. The parent component should be responsible for this spacing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FileDropzone.tsx &amp;amp; MainContent.tsx&lt;/strong&gt; : These UI components were using &lt;code&gt;mx-auto&lt;/code&gt;, another margin-related property that is the responsibility of a layout container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StatCard.tsx&lt;/strong&gt; : This UI component was using &lt;code&gt;mt-1&lt;/code&gt; and &lt;code&gt;ml-1&lt;/code&gt;, which are small but important violations of the architectural pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these warnings was a clear, actionable task for us to clean up the codebase and align it with our new, more maintainable architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: More Than Just Code Generation
&lt;/h2&gt;

&lt;p&gt;This experience was a powerful demonstration of how to use LLMs for more than just generating boilerplate or fixing simple bugs. By teaching the AI our team’s specific architectural patterns, we were able to turn it into a partner for enforcing code quality and consistency at scale. It automated the tedious work of code validation, freeing us up to focus on building features.&lt;br&gt;&lt;br&gt;
tures.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>From Linter Chaos to Orchestrated Tasks - Part 4 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/from-linter-chaos-to-orchestrated-tasks-part-4-of-6-23ah</link>
      <guid>https://dev.to/drmikecrowe/from-linter-chaos-to-orchestrated-tasks-part-4-of-6-23ah</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 4 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the fourth installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of building an AI-assisted development environment, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;In my last post, I talked about how my AI pair programmer and I created a custom ESLint rule to enforce our new frontend architecture. It was a huge success. The linter, now armed with our specific rules, scanned the codebase and… gave us a giant list of things to fix.&lt;/p&gt;

&lt;p&gt;This is a classic “good news, bad news” scenario. The good news? We had a precise, automated way to detect architectural drift. The bad news? We had a long, intimidating wall of terminal output listing every single violation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/home/mcrowe/.../AlertDialog.tsx
  19:8 warning Unregistered class detected: data-[state=open]:animate-in
  19:37 warning Unregistered class detected: data-[state=closed]:animate-out
...
/home/mcrowe/.../EmptyState.tsx
  25:21 error UI Component contains a forbidden margin class: 'mb-4'
...
✖ 60 problems (2 errors, 58 warnings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A raw list of errors is a starting point, but it’s not a plan. How do you manage this work? How do you distribute it? How do you ensure each fix is made with the right context? Just handing a developer (or an AI) a raw log is inefficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool: TaskFlow MCP for Orchestration
&lt;/h2&gt;

&lt;p&gt;This is where another tool in our AI-assisted workflow comes in: &lt;a href="https://github.com/pinkpixel-dev/taskflow-mcp" rel="noopener noreferrer"&gt;TaskFlow MCP&lt;/a&gt;. It’s an open-source task orchestration server designed specifically for managing development work between humans and AI agents.&lt;/p&gt;

&lt;p&gt;Instead of just working from a simple prompt, TaskFlow allows us to break down a high-level request (like “Fix all these linter warnings”) into a structured plan of discrete, trackable tasks.&lt;/p&gt;

&lt;p&gt;Crucially, as the user, I can insist that &lt;strong&gt;each task be configured with specific context and instructions&lt;/strong&gt;. This is the killer feature. It turns a simple “fix this” command into a rich, context-aware work order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process: Turning Chaos into a Plan
&lt;/h2&gt;

&lt;p&gt;I fed the messy ESLint output to my AI assistant and gave it a new prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"For each of the UI warnings below, use &lt;code&gt;plan_task&lt;/code&gt; to create a ‘Fix UI layout vs. styling overlap’ request. For each violation, create a sub-task. Ensure that each task specifically includes these instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read &lt;code&gt;memory-bank/AGENTS.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Review &lt;code&gt;memory-bank/project-rules/*&lt;/code&gt;"&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI, using the TaskFlow MCP tool, did exactly that. It parsed the linter output, grouped the violations by file, and created a formal plan. Each task was not just a file path and a line number; it was a complete work package containing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A Clear Title&lt;/strong&gt; : e.g., “Fix AppLayout.tsx layout component styling violations”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Detailed Description&lt;/strong&gt; : Explaining exactly which classes were violating our architectural rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded Context&lt;/strong&gt; : The prerequisite instructions to review our project’s agent protocols and architectural rules before starting work.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Result: An Actionable, Context-Aware Plan
&lt;/h2&gt;

&lt;p&gt;Tasks are maintained locally&lt;/p&gt;

&lt;p&gt;The output wasn’t a log file; it was a professional task report, ready for any developer—human or AI—to pick up and execute. It transformed a chaotic list of problems into a manageable, parallelizable set of tasks.&lt;/p&gt;

&lt;p&gt;Here is the exact markdown file that the tool generated. It’s clear, organized, and ensures that whoever does the work has the full context required to do it right.&lt;/p&gt;




&lt;h1&gt;
  
  
  Task Status Report: Fix UI layout vs. styling overlap issues identified in ESLint warnings
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Generated on: 2025-08-17&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overall Progress: 0%
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total Tasks:&lt;/strong&gt; 9&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completed Tasks:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approved Tasks:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remaining Tasks:&lt;/strong&gt; 9&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Task Status
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Fix AppLayout.tsx layout component styling violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove non-layout classes from AppLayout.tsx component. The component contains styling classes like bg-gray-100, rounded-md, hover:bg-gray-100, focus:ring-2, text-purple-700, ml-2, text-lg, font-bold, text-gray-900, bg-opacity-30, bg-black that should be moved to separate styling components or variants. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fix Sidebar.tsx layout component styling violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove non-layout classes from Sidebar.tsx component. The component contains styling classes like ml-2, text-lg, font-bold, tracking-tight, text-gray-900, mt-4, text-sm, text-gray-500, hover:text-gray-700, rounded-md, font-medium, text-gray-600, transition-colors, hover:bg-blue-50, hover:text-blue-700, text-gray-400, mt-2 that should be moved to separate styling components or variants. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Fix EmptyState.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from EmptyState.tsx component. The component contains margin classes mb-4, mb-2, mb-6 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Fix ErrorState.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from ErrorState.tsx component. The component contains margin classes mt-4, mt-2 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Fix FileDropzone.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from FileDropzone.tsx component. The component contains margin classes mx-auto, mr-2 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Fix MainContent.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from MainContent.tsx component. The component contains margin class mx-auto that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Fix StatCard.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from StatCard.tsx component. The component contains margin classes mt-1, mt-2, ml-1 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Fix ClearSearchButton.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from ClearSearchButton.tsx component. The component contains margin class my-auto that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Fix PageHeader.tsx UI component margin violations (🔄 In Progress)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt; Remove margin classes from PageHeader.tsx component. The component contains margin class mt-1 that should be handled by parent layout components. Read memory-bank/AGENTS.md and review memory-bank/project-rules/* before starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; 🔄 In Progress&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Approval:&lt;/strong&gt; ⏳ Not Ready&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Must read</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Sat, 06 Sep 2025 21:59:25 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/must-read-1m1o</link>
      <guid>https://dev.to/drmikecrowe/must-read-1m1o</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp" class="crayons-story__hidden-navigation-link"&gt;Getting Started with Multi-MCP Using Hatago MCP Hub — One Config to Connect Them All&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/himorishige" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3473639%2F7a720bf9-6781-43fa-a22a-997fd3002667.jpg" alt="himorishige profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/himorishige" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hi MORISHIGE
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hi MORISHIGE
                
                
              
              &lt;div id="story-author-preview-content-2813109" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/himorishige" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3473639%2F7a720bf9-6781-43fa-a22a-997fd3002667.jpg" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hi MORISHIGE&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 1 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp" id="article-link-2813109"&gt;
          Getting Started with Multi-MCP Using Hatago MCP Hub — One Config to Connect Them All
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloudflare"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloudflare&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hono"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hono&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/himorishige/getting-started-with-multi-mcp-using-hatago-mcp-hub-one-config-to-connect-them-all-2bjp#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              2&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>mcp</category>
      <category>ai</category>
      <category>cloudflare</category>
      <category>hono</category>
    </item>
    <item>
      <title>MCP Servers, Ports, and Sharing - Part 1 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/mcp-servers-ports-and-sharing-part-1-of-6-4agk</link>
      <guid>https://dev.to/drmikecrowe/mcp-servers-ports-and-sharing-part-1-of-6-4agk</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 1 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the first installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of using an AI-assisted development environment effectively, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;I’ve been spending a lot of time lately in the world of AI agents… wait, no, that’s not it. The &lt;em&gt;Model Context Protocol&lt;/em&gt;. MCP. It’s a fancy way of saying “a way for AI models to talk to tools,” and it’s pretty powerful. But like any new toy, it comes with its own set of “some assembly required” headaches. Today, I want to talk about one of those: managing MCP servers, avoiding port conflicts, and generally keeping your digital workspace from turning into a tangled mess of wires.&lt;/p&gt;

&lt;h2&gt;
  
  
  What in the World is an MCP Server?
&lt;/h2&gt;

&lt;p&gt;Think of an MCP server as a translator. Your AI model speaks “I want to do something,” and the MCP server translates that into “Okay, computer, run this specific command.” It’s a bridge between the high-level thinking of the AI and the low-level reality of your machine.&lt;/p&gt;

&lt;p&gt;You need to know about them because, suddenly, you’re not just running a single AI model; you’re running a whole suite of tiny little helper applications. Each one of these helpers, or “tools,” might need its own MCP server. Want to browse the web? That’s a server. Want to read and write files? That’s another server. Before you know it, you’ve got a whole digital office full of very specialized, very chatty interns.&lt;/p&gt;

&lt;p&gt;So, when do you start one? The simple answer is: when you want to use a tool. The more complicated answer is: when you want to use a tool that needs a dedicated process to listen for instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nomenclature
&lt;/h2&gt;

&lt;p&gt;Before we dive deeper, let’s clarify some terminology that might seem counterintuitive at first. In the MCP ecosystem, what we call an “MCP server” is actually a &lt;strong&gt;client&lt;/strong&gt; from the perspective of your editor or CLI tool.&lt;/p&gt;

&lt;p&gt;Here’s the mental model: Your editor (like VS Code, Cursor, Windsurf) or CLI tool is the “server” that coordinates everything. It’s the central hub that manages the conversation with the AI model and decides which tools to invoke. The MCP servers are specialized “clients” that connect to this central hub to provide specific capabilities.&lt;/p&gt;

&lt;p&gt;Think of it like a restaurant: Your editor is the head chef who takes orders and coordinates the kitchen. The MCP servers are like specialized sous chefs - each one is an expert at a particular type of cooking (browsing the web, reading files, querying databases, etc.). The head chef doesn’t know how to do everything, but they know which sous chef to call for each specific task.&lt;/p&gt;

&lt;p&gt;So when we say “MCP server,” we’re really talking about a specialized client that serves a particular function to the main AI coordination system. This naming convention can be confusing, but it’s become standard in the MCP community.&lt;/p&gt;

&lt;p&gt;Where this can get really confusing is if the tool you are wanting to use also requires communicating with a server. I really like &lt;a href="https://browsertools.agentdesk.ai/" rel="noopener noreferrer"&gt;BrowserTools MCP&lt;/a&gt;. This tool exposes your console logs to your browser, giving it access to your frontend development logs as you make changes. It’s a complex tool, though, which requires:&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%2Fawfpxmiag4mwhkmymexp.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%2Fawfpxmiag4mwhkmymexp.png" alt=" " width="799" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So when you say “BrowserTools MCP,” you’re actually talking about three separate components working together - the browser extension, the MCP server, and the BrowserTools server. This is a perfect example of why port management becomes crucial in AI-assisted development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of MCP Servers
&lt;/h2&gt;

&lt;p&gt;MCP servers come in two main flavors, and understanding the difference is crucial for managing your development environment effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  stdio-based Servers
&lt;/h3&gt;

&lt;p&gt;These are the simplest type of MCP server. They communicate through standard input/output streams, which means they’re designed to be short-lived processes that start when needed and exit when the work is done. Think of them like command-line tools that your AI can invoke.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Characteristics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No port management needed&lt;/strong&gt; - They don’t bind to any network ports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateless by design&lt;/strong&gt; - Each invocation is independent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect for one-off tasks&lt;/strong&gt; - File operations, code generation, data processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to debug&lt;/strong&gt; - You can run them directly from the command line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example use cases:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File system operations (reading, writing, searching files)&lt;/li&gt;
&lt;li&gt;Code formatting and linting&lt;/li&gt;
&lt;li&gt;Data transformation and analysis&lt;/li&gt;
&lt;li&gt;Simple API calls and data fetching&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Port-based Servers
&lt;/h3&gt;

&lt;p&gt;These are the more complex beasts that we’ll focus on in this post. They run as persistent HTTP servers that bind to specific ports on your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Characteristics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistent processes&lt;/strong&gt; - They stay running and maintain state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port binding required&lt;/strong&gt; - Each server needs its own port&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateful operations&lt;/strong&gt; - Can maintain context across multiple requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network communication&lt;/strong&gt; - Use HTTP/WebSocket protocols&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource intensive&lt;/strong&gt; - They consume memory and CPU while running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example use cases:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connections and queries&lt;/li&gt;
&lt;li&gt;Web browsing and page interaction&lt;/li&gt;
&lt;li&gt;Complex tool integrations (like GitHub, Slack, etc.)&lt;/li&gt;
&lt;li&gt;Real-time data streams and monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why This Matters for Your Workflow
&lt;/h3&gt;

&lt;p&gt;The distinction becomes important when you’re building an AI-assisted development environment. stdio-based servers are great for simple, stateless operations, but you need to understand which type of server you are utilizing.&lt;/p&gt;

&lt;p&gt;I tend to have multiple windows open of Cursor, or might be running Cursor and gemini in a terminal shell. What if I want to share an MCP server (like we will discuss in part 4) across my different windows/sessions?&lt;/p&gt;

&lt;p&gt;This is where the port management challenges come in. To make it even more complicated, you might have several port-based servers running simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A browser automation server on port 3000&lt;/li&gt;
&lt;li&gt;A database connection server on port 3001&lt;/li&gt;
&lt;li&gt;A file system watcher on port 3002&lt;/li&gt;
&lt;li&gt;A custom tool integration on port 3003&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Managing these ports, ensuring they don’t conflict, and keeping track of which server is doing what becomes a real challenge. That’s exactly the problem we’ll solve with our tmux-based orchestration system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Port Authority is Now in Session
&lt;/h2&gt;

&lt;p&gt;Many of these MCP servers use HTTP to communicate. That means they need to “bind” to a specific port on your computer. A port is like a numbered door on your computer. Only one application can be listening at a specific door at a time. If you try to start two servers on the same port, the second one will crash and burn, complaining about the port being “already in use.”&lt;/p&gt;

&lt;p&gt;This is a classic problem in software development, and it’s now a problem for those of us building with AI. But here’s the cool part: if an MCP server is just a simple HTTP server, you can often &lt;em&gt;share&lt;/em&gt; it between different AI models. As long as they’re all speaking the same language (i.e., the same MCP specification), they can all talk to the same server on the same port. No need to spin up a new server for each model.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Not-So-Secret Weapon: &lt;code&gt;tmux&lt;/code&gt; and &lt;code&gt;servers.mcp&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As I started collecting more and more of these MCP servers, I realized I needed a better way to manage them. I’m a big fan of &lt;code&gt;tmux&lt;/code&gt;, a terminal multiplexer that lets you have multiple terminal sessions running in the same window. It’s like having a whole bunch of little command-line windows open at once, but without the clutter.&lt;/p&gt;

&lt;p&gt;So, I came up with a little convention for myself. I created a file called &lt;code&gt;servers.mcp&lt;/code&gt; that lists all the servers I want to run. It’s a simple text file, with each line containing a name for the server and the command to start it, separated by an equals sign.&lt;/p&gt;

&lt;p&gt;Here’s what my &lt;code&gt;servers.mcp&lt;/code&gt; file looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;browser-tools=npx -y @agentdeskai/browser-tools-server@latest
mpc-tasks=TRANSPORT=http PORT=4680 npx mcp-tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I wrote a little shell script called &lt;code&gt;start-mcp-servers.sh&lt;/code&gt; that reads this file and starts up a new &lt;code&gt;tmux&lt;/code&gt; session with a separate window for each server.&lt;/p&gt;

&lt;p&gt;Here’s the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# MCP Server Launcher Script
# Reads servers.mcp and creates tmux windows for each server

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Check if tmux is installed
if ! command -v tmux &amp;amp;&amp;gt;/dev/null; then
    echo -e "${RED}Error: tmux is not installed${NC}"
    echo "Please install tmux first:"
    echo " Ubuntu/Debian: sudo apt install tmux"
    echo " macOS: brew install tmux"
    echo " Arch: sudo pacman -S tmux"
    exit 1
fi

# Change to the script's directory
cd "$(dirname "$0")"

# Check if servers.mcp exists
if [! -f "servers.mcp"]; then
    echo -e "${RED}Error: servers.mcp file not found${NC}"
    exit 1
fi

# Check if tmux session already exists
if tmux has-session -t mcp-servers 2&amp;gt;/dev/null; then
    echo -e "${YELLOW}MCP servers session already exists. Attaching...${NC}"
    tmux attach-session -t mcp-servers
    exit 0
fi

# Function to clean up tmux session if script is interrupted
cleanup() {
    echo -e "\n${YELLOW}Cleaning up...${NC}"
    tmux kill-session -t mcp-servers 2&amp;gt;/dev/null
    exit 1
}

# Set up signal handlers
trap cleanup SIGINT SIGTERM

echo -e "${BLUE}Starting MCP servers in tmux session...${NC}"

# Create new tmux session
tmux new-session -d -s mcp-servers -n "mcp-servers"

# Read servers.mcp file and create windows
window_count=0
while IFS='=' read -r title command;
do
    # Skip empty lines and comments
    if [[-z "$title" || "$title" =~ ^[[:space:]]*# ]]; then
        continue
    fi

    # Trim whitespace
    title=$(echo "$title" | xargs)
    command=$(echo "$command" | xargs)

    if [-n "$title"] &amp;amp;&amp;amp; [-n "$command"]; then
        echo -e "${GREEN}Creating window: $title${NC}"
        echo -e " Command: $command"

        if [$window_count -eq 0]; then
            # First window - rename the default window
            tmux rename-window -t mcp-servers:0 "$title"
            tmux send-keys -t mcp-servers:0 "$command &amp;amp;" C-m
        else
            # Create new window
            tmux new-window -t mcp-servers -n "$title" "$command"
        fi

        window_count=$((window_count + 1))
    fi
done &amp;lt;servers.mcp

if [$window_count -eq 0]; then
    echo -e "${RED}No valid servers found in servers.mcp${NC}"
    tmux kill-session -t mcp-servers 2&amp;gt;/dev/null
    exit 1
fi

echo -e "${GREEN}Created $window_count MCP server windows${NC}"
echo -e "${BLUE}Attaching to tmux session...${NC}"
echo -e "${YELLOW}Use Ctrl+B then D to detach from the session${NC}"
echo -e "${YELLOW}Use 'tmux attach -t mcp-servers' to reattach later${NC}"

# Attach to the session
tmux attach-session -t mcp-servers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when I want to start up my MCP environment, I just run &lt;code&gt;./start-mcp-servers.sh&lt;/code&gt;, and I get a nice, clean &lt;code&gt;tmux&lt;/code&gt; session with all my servers running in their own windows.&lt;/p&gt;

&lt;p&gt;Here’s what it looks like:&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%2Ffaxxedi2uq095v00bvbz.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%2Ffaxxedi2uq095v00bvbz.png" alt="My tmux session with MCP servers running" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setup makes it easy to see what’s going on with each server, and I can easily restart a server if it crashes. It’s a simple solution, but it’s made my life a lot easier. And, most importantly, it keeps me from tripping over my own digital feet.&lt;br&gt;&lt;br&gt;
m tripping over my own digital feet.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>My ESLint Config Was a Mess. I Asked an AI to Fix It. - Part 2 of 6</title>
      <dc:creator>drmikecrowe</dc:creator>
      <pubDate>Tue, 19 Aug 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/drmikecrowe/my-eslint-config-was-a-mess-i-asked-an-ai-to-fix-it-part-2-of-6-4g47</link>
      <guid>https://dev.to/drmikecrowe/my-eslint-config-was-a-mess-i-asked-an-ai-to-fix-it-part-2-of-6-4g47</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 2 of 6: The AI-Assisted Development Workflow Series&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the second installment in a six-part series exploring how AI is transforming modern development workflows. In this series, I’ll walk through my journey of building an AI-assisted development environment, from basic infrastructure setup to advanced architectural enforcement and task orchestration.&lt;/p&gt;




&lt;p&gt;If you’re a frontend developer, you know the love-hate relationship we have with ESLint. We love that it keeps our code clean and consistent. We hate spending hours wrestling with config files, trying to get plugins to play nicely with each other, especially in a modern TypeScript and Tailwind CSS v4 world.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;eslint.config.js&lt;/code&gt; was starting to feel like a house of cards. I wanted to add better Tailwind CSS linting, but my first attempt with a popular plugin led to a cascade of peer dependency warnings and configuration errors. After a few failed attempts, I realized I was spending more time configuring the linter than writing code. This is not the way.&lt;/p&gt;

&lt;p&gt;So, I turned to my AI pair programmer with a simple plea: “Help me fix this mess.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is eslint locking up?
&lt;/h2&gt;

&lt;p&gt;The first issue I needed help with was that ESLint was trying to use type-aware rules with multiple TypeScript config files, which can cause it to hang when processing large codebases. Type-aware rules like &lt;code&gt;@typescript-eslint/no-unused-vars&lt;/code&gt; and &lt;code&gt;@typescript-eslint/no-floating-promises&lt;/code&gt; require ESLint to perform complex type checking across your entire project, and when you have multiple &lt;code&gt;tsconfig.json&lt;/code&gt; files or conflicting TypeScript configurations, this can lead to infinite loops or excessive memory usage.&lt;/p&gt;

&lt;p&gt;The solution was to create a &lt;strong&gt;dual-config approach&lt;/strong&gt; that separates type-aware and non-type-aware rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App files&lt;/strong&gt; use the fast, non-type-aware config for quick feedback during development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test files&lt;/strong&gt; use the comprehensive type-aware config for thorough validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared configuration&lt;/strong&gt; provides common rules and plugins across both configs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation allows ESLint to run quickly during development while still providing comprehensive type checking where it matters most.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mission: Clean, Refactored ESLint Config
&lt;/h2&gt;

&lt;p&gt;My goal was to not just get a new Tailwind plugin working, but to clean up the entire ESLint configuration. The process, guided by the AI, was a masterclass in untangling complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Result
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Refactoring for Readability and DRYness
&lt;/h3&gt;

&lt;p&gt;The first thing it did was apply the Don’t Repeat Yourself (DRY) principle to my config. Instead of having separate, nearly identical objects for different TypeScript configurations, it created common, reusable building blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COMMON_PLUGINS&lt;/code&gt;: A single object defining all the ESLint plugins we use, like &lt;code&gt;@typescript-eslint&lt;/code&gt;, &lt;code&gt;prettier&lt;/code&gt;, &lt;code&gt;react-hooks&lt;/code&gt;, and the new Tailwind plugins.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COMMON_SETTINGS&lt;/code&gt;: A shared object for settings, like the import resolver paths.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COMMON_RULES&lt;/code&gt;: A comprehensive object containing all the rules that apply across the entire project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This immediately made the config file shorter, cleaner, and much easier to understand. If we need to add a new rule everywhere, we now only have to add it in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Installing and Configuring the &lt;em&gt;Right&lt;/em&gt; Plugins
&lt;/h3&gt;

&lt;p&gt;The initial journey involved some trial and error. We first tried &lt;code&gt;eslint-plugin-tailwindcss&lt;/code&gt;, but it had issues with our Tailwind v4 setup. The AI helped diagnose this and suggested a better alternative. The final commit shows the installation of two key packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eslint-plugin-better-tailwindcss&lt;/code&gt;: A fantastic plugin for sorting classes and catching duplicates and conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@poupe/eslint-plugin-tailwindcss&lt;/code&gt;: Another great plugin that adds more advanced checks, like preferring theme tokens and validating CSS modifiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Dialing in the Rules
&lt;/h3&gt;

&lt;p&gt;This was the most crucial part. The AI configured the new plugins with a sensible set of rules, turning some on as warnings and others as errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Tailwind CSS rules
'better-tailwindcss/sort-classes': 'warn',
'better-tailwindcss/no-duplicate-classes': 'error',
'better-tailwindcss/no-conflicting-classes': 'error',
'better-tailwindcss/no-unregistered-classes': 'off', // We turned this off because we use custom design tokens
'better-tailwindcss/enforce-shorthand-classes': 'warn',

// Poupe Tailwind CSS rules
'tailwindcss/no-conflicting-utilities': 'error',
'tailwindcss/prefer-theme-tokens': 'warn',
'tailwindcss/valid-modifier-syntax': 'error',
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;no-unregistered-classes&lt;/code&gt; rule is off. The AI correctly identified from the linter output that our custom design tokens (like &lt;code&gt;bg-primary&lt;/code&gt;) were being flagged, and recommended disabling this rule to avoid noise, which was exactly the right call.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Automatically Fixing What It Could
&lt;/h3&gt;

&lt;p&gt;The best part? The new &lt;code&gt;sort-classes&lt;/code&gt; rule didn’t just flag inconsistent class ordering—it fixed it. The git commit is full of small, satisfying changes where messy class strings were automatically reordered into a logical, consistent format:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&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;&amp;lt;div className='flex justify-center items-center h-32'&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&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;&amp;lt;div className='flex h-32 items-center justify-center'&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happened across dozens of files. It’s a small change, but it adds up to a huge improvement in code quality and developer experience, and it was all done automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result: Effortless Code Quality
&lt;/h2&gt;

&lt;p&gt;After the AI finished its work, our ESLint setup was transformed. It’s now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Easy to read and maintain.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using modern, effective plugins&lt;/strong&gt; for our specific stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatically enforcing&lt;/strong&gt; a consistent style for our utility classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This experience was a powerful reminder that AI assistants are more than just code generators. They can be expert systems administrators, helping to configure and maintain the complex tooling that modern development relies on. It took a task that was a source of frustration and turned it into a clean, automated part of our workflow. And for that, I am very grateful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Architecture: Dual-Config with Shared Rules
&lt;/h2&gt;

&lt;p&gt;The AI helped me implement a sophisticated dual-config architecture that solves the performance issues while maintaining comprehensive linting coverage. Here’s how it works:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Shared Configuration (&lt;code&gt;eslint.shared.config.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The shared config provides the foundation with common plugins, settings, and rules that apply across the entire project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Common plugins used across all TypeScript configs
const COMMON_PLUGINS = {
    '@typescript-eslint': tseslint.plugin,
    'react-hooks': reactHooks,
    'react-refresh': reactRefresh,
    import: importPlugin,
    prettier: prettierPlugin,
    'react-dom': reactDom,
    perfectionist,
    'react-x': reactX,
    'unused-imports': unusedImports,
    visual: visualComplexity,
    'better-tailwindcss': betterTailwindcss,
    tailwindcss: poupeTailwindcssPlugin,
}

// Factory function to create TypeScript configs with different rule sets
const createTypeScriptConfig = (...tsRuleSets) =&amp;gt; ({
    languageOptions: {
        parser: tseslint.parser,
        parserOptions: {
            ecmaFeatures: {
                jsx: true,
            },
        },
    },
    plugins: COMMON_PLUGINS,
    rules: {
        ...COMMON_RULES,
        // Merge all provided TypeScript rule sets
        ...Object.assign({}, ...tsRuleSets),
    },
    settings: COMMON_SETTINGS,
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This factory pattern allows us to create different TypeScript configurations with varying levels of strictness.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. App Configuration (&lt;code&gt;eslint.app.config.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The app config implements the performance-optimized approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default [
    // Global ignores
    {
        ignores: [...sharedIgnores, 'shared/**'],
    },
    // App TypeScript files WITHOUT type-aware rules (for speed)
    {
        files: appFiles,
        languageOptions: {
            ...typescriptConfig.languageOptions,
            globals: browserGlobals,
        },
        plugins: typescriptConfig.plugins,
        rules: typescriptConfig.rules,
        settings: typescriptConfig.settings,
    },
    // Test files override WITH type-aware rules
    {
        files: ['**/*.test.{js,jsx,ts,tsx}'],
        languageOptions: {
            ...typescriptConfig.languageOptions,
            parserOptions: {
                ...typescriptConfig.languageOptions.parserOptions,
                project: './tsconfig.test.json',
                tsconfigRootDir: import.meta.dirname,
            },
            globals: {
                ...globals.jest,
                ...browserGlobals,
            },
        },
        plugins: typescriptConfig.plugins,
        rules: {
            ...typescriptConfig.rules,
            ...testConfig.rules,
        },
        settings: typescriptConfig.settings,
    },
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The Performance Solution
&lt;/h3&gt;

&lt;p&gt;The key insight was creating two distinct TypeScript configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;typescriptConfig&lt;/code&gt;&lt;/strong&gt; : Uses &lt;code&gt;tseslint.configs.recommended&lt;/code&gt; and &lt;code&gt;tseslint.configs.stylistic&lt;/code&gt; - fast, syntax-based rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;typeAwareConfig&lt;/code&gt;&lt;/strong&gt; : Uses &lt;code&gt;tseslint.configs.recommendedTypeChecked&lt;/code&gt;, &lt;code&gt;tseslint.configs.strictTypeChecked&lt;/code&gt;, and &lt;code&gt;tseslint.configs.stylisticTypeChecked&lt;/code&gt; - comprehensive type-aware rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By applying the fast config to app files and the comprehensive config only to test files, we get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast development feedback&lt;/strong&gt; - ESLint runs quickly during coding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thorough validation&lt;/strong&gt; - Type-aware rules catch issues in tests where they matter most&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainable architecture&lt;/strong&gt; - Shared configuration keeps everything DRY&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture demonstrates how AI can help design sophisticated solutions that balance performance with functionality, turning a frustrating configuration problem into an elegant, scalable system.  &lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
