<?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: Luc B. Perussault-Diallo</title>
    <description>The latest articles on DEV Community by Luc B. Perussault-Diallo (@luuuc).</description>
    <link>https://dev.to/luuuc</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%2F3784850%2Fd6117002-ae1c-457a-8b55-34f37642a1e5.jpeg</url>
      <title>DEV Community: Luc B. Perussault-Diallo</title>
      <link>https://dev.to/luuuc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/luuuc"/>
    <language>en</language>
    <item>
      <title>How would you test whether an AI understands your codebase?</title>
      <dc:creator>Luc B. Perussault-Diallo</dc:creator>
      <pubDate>Sun, 12 Jul 2026 10:54:18 +0000</pubDate>
      <link>https://dev.to/luuuc/how-would-you-test-whether-an-ai-understands-your-codebase-1c9l</link>
      <guid>https://dev.to/luuuc/how-would-you-test-whether-an-ai-understands-your-codebase-1c9l</guid>
      <description>&lt;p&gt;A question I can't stop chewing on, and I want your answer more than I want your agreement.&lt;/p&gt;

&lt;p&gt;How would you test whether an AI actually &lt;em&gt;understands&lt;/em&gt; your codebase?&lt;/p&gt;

&lt;p&gt;Not whether it writes good code. That's mostly settled, and benchmarks for it are everywhere. I mean understanding in the sense a senior engineer has it: knowing what depends on what, what breaks if you change this, where the bodies are buried. The thing you'd want before letting an agent touch a load-bearing model.&lt;/p&gt;

&lt;p&gt;It's a slippery thing to measure, and I want to lay out why, show you the test I built, point at where I think it's wrong, and then hand it to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "understands" is hard to pin down
&lt;/h2&gt;

&lt;p&gt;The obvious tests don't work.&lt;/p&gt;

&lt;p&gt;→ Ask it to explain a file? It'll explain it beautifully. Reading a single file is local, the answer is in the text, and models are great at it. Proves nothing about structure.&lt;br&gt;
→ Ask it trivia about the architecture? It'll recite plausible-sounding answers, and on a famous repo it'll recite &lt;em&gt;memorized&lt;/em&gt; ones that may not match how the code is wired today. You're testing recall, not comprehension.&lt;br&gt;
→ Ask it to make a change and see if tests pass? Now you're testing the test suite as much as the model, and green tests miss exactly the silent breaks you care about.&lt;/p&gt;

&lt;p&gt;The hard part is that a confident, fluent, wrong answer looks identical to a correct one until production tells you otherwise. Any test worth running has to defeat that. It has to catch the answer that &lt;em&gt;reads&lt;/em&gt; as understanding but isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  My attempt: the change-impact teardown audit
&lt;/h2&gt;

&lt;p&gt;What I landed on, described so you can attack it.&lt;/p&gt;

&lt;p&gt;Pick the hub model of a real app, the one half the system leans on. Give the agent a real maintainer task:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before you change how this model is torn down, find every place in the codebase that depends on it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a pure structure question. It has no answer inside any one file. To get it right you have to know the edges, the dependents that reach the model through a concern, a polymorphic association, a worker that loads it by id three calls deep, a config-string registry. The ones with no shared token to grep for.&lt;/p&gt;

&lt;p&gt;Three design choices do the real work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A hand-built answer key.&lt;/strong&gt; Before any run, the real dependents get pinned by hand against the source, to an exact &lt;code&gt;file:line&lt;/code&gt;, weighted toward the scattered non-obvious ones. The agent never sees it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grade against the key, not the prose.&lt;/strong&gt; A reference-aware judge checks each cited dependent against the key. No credit for a vague gesture at the right neighborhood. The citation lands on the line or it doesn't count. (I learned this one the hard way, an earlier reference-blind judge called a 44%-complete audit "exhaustive" because it had nothing to compare against.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it twice per arm.&lt;/strong&gt; Because inference isn't deterministic, and a single run hides the variance. One repo went 2 then 0 on the identical prompt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I ran it across thirteen real Ruby codebases, with and without a structural map the agent could query. The map swung the big apps hard and tied on small colocated ones, which felt like a real signal: it's tracking structure that outgrows what the model can read, not repo size or luck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I think it's flawed
&lt;/h2&gt;

&lt;p&gt;This is the part I actually want help with. I can see holes:&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;One model archetype.&lt;/strong&gt; The "find dependents before a teardown" task is one shape of understanding. Is it representative, or did I pick the shape that flatters a graph-based answer? What's the task that would &lt;em&gt;break&lt;/em&gt; my test?&lt;br&gt;
→ &lt;strong&gt;Static structure only.&lt;/strong&gt; It catches call-and-dependency edges. It misses a pub-sub event bus, dynamic dispatch, runtime data flow. An agent could ace my test and still not understand a heavily event-driven system. How would you test understanding of the dynamic wiring?&lt;br&gt;
→ &lt;strong&gt;The answer key is mine.&lt;/strong&gt; I built it by hand, which means it inherits my blind spots. If I missed a dependent, the bench can't know. How do you build a ground truth you don't trust yourself to have gotten complete?&lt;br&gt;
→ &lt;strong&gt;Recall, not judgment.&lt;/strong&gt; I measure how much of the dependent set it finds. I don't measure whether it knows &lt;em&gt;which&lt;/em&gt; dependents are risky. Finding everything and prioritizing nothing is its own failure.&lt;/p&gt;

&lt;p&gt;I think the test is good enough to have taught me something real. I don't think it's the last word, and I'd rather hear why than defend it.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, genuinely, how would you do it?
&lt;/h2&gt;

&lt;p&gt;A few specific forks I'd love comments on:&lt;/p&gt;

&lt;p&gt;→ What's the task you'd give an agent to prove it understands &lt;em&gt;your&lt;/em&gt; codebase? Not a toy, the real one you ship.&lt;br&gt;
→ How do you build a ground truth for "understanding" without it just being your own opinion written down?&lt;br&gt;
→ Has an agent ever given you a confident answer about your architecture that was flat wrong? What was it, and how did you find out?&lt;br&gt;
→ Is "find what breaks if I change this" even the right proxy for understanding, or is there a better one?&lt;/p&gt;

&lt;p&gt;I'll be in the comments, and I'll publish whatever I learn from yours. If your test is better than mine I'll say so and steal it for the next round.&lt;/p&gt;

&lt;h2&gt;
  
  
  And if you'd rather just run mine
&lt;/h2&gt;

&lt;p&gt;The cheapest way to have an opinion here is to run the experiment on a repo you know cold. Pick the model you'd schedule a careful afternoon for. Ask your agent the teardown question above and read the audit. Then map the codebase and ask again:&lt;/p&gt;

&lt;p&gt;→ &lt;code&gt;curl -fsSL https://luuuc.github.io/sense/install.sh | sh&lt;/code&gt;&lt;br&gt;
→ &lt;code&gt;sense scan&lt;/code&gt; in the repo you know best&lt;br&gt;
→ &lt;code&gt;sense setup&lt;/code&gt; to connect your agent&lt;/p&gt;

&lt;p&gt;Diff the two answers. The gap is what your agent currently can't see, and it'll sharpen whatever you come back to argue in the comments. (The map's a structural index, not a model, so it's measuring your code's structure, not Claude's mood, which is most of why I trust the diff.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/luuuc/sense/blob/main/bench/verticals/ruby-rails/results/report.md" rel="noopener noreferrer"&gt;My full setup, the answer keys, the judge prompts, every transcript.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I build that map, so I'm not a neutral party, which is exactly why I want your test design and not just my own. Everything is open to pick apart.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;PS. The best benchmark is the one that embarrasses the person who built it. Mine has, twice. Tell me how to make it embarrass me a third time. That's the comment I most want.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>ruby</category>
      <category>coding</category>
    </item>
    <item>
      <title>Your next model upgrade won't close this gap</title>
      <dc:creator>Luc B. Perussault-Diallo</dc:creator>
      <pubDate>Thu, 09 Jul 2026 06:31:34 +0000</pubDate>
      <link>https://dev.to/luuuc/your-next-model-upgrade-wont-close-this-gap-4oi5</link>
      <guid>https://dev.to/luuuc/your-next-model-upgrade-wont-close-this-gap-4oi5</guid>
      <description>&lt;p&gt;There's a comfortable thing people say when they see an AI agent query a code map.&lt;/p&gt;

&lt;p&gt;"Nice crutch. For now."&lt;/p&gt;

&lt;p&gt;The logic underneath it is reasonable. Coding agents are young. Context windows are small and getting bigger. Models are dumb today and will be smart tomorrow. So a structural index, the thing that hands the agent a dependency graph it would otherwise have to reconstruct, looks like a patch over a temporary weakness. Wait two releases. The model will just hold the whole repo in its head and the map becomes a quaint workaround, like a spellchecker for someone who learned to spell.&lt;/p&gt;

&lt;p&gt;I build one of those maps &lt;a href="https://github.com/luuuc/sense" rel="noopener noreferrer"&gt;Sense&lt;/a&gt;. I went looking for the data that would kill it.&lt;/p&gt;

&lt;p&gt;I didn't find it. I found the opposite.&lt;/p&gt;

&lt;p&gt;What a map hands an agent is a &lt;em&gt;computed&lt;/em&gt; fact. What a better model hands you is a more confident &lt;em&gt;guess&lt;/em&gt;. No amount of model progress turns the second into the first, because the difference between them isn't a quality gap that closes with scale. It's a difference of kind. The rest of this piece is the two findings that forced me there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The belief, stated fairly
&lt;/h2&gt;

&lt;p&gt;The claim at full strength, because a weak version is easy to knock over.&lt;/p&gt;

&lt;p&gt;A code map exists to compensate for what the model can't do yet. Today's agent greps, samples, and guesses at structure because it can't read the whole codebase at once. Tomorrow's agent reads all of it, reasons over all of it, and the guessing stops. Bigger windows plus better weights equal no more blind spots. The map is scaffolding you'll tear down once the building stands.&lt;/p&gt;

&lt;p&gt;If that's true, the right move is to skip the tool and wait.&lt;/p&gt;

&lt;p&gt;Both findings, in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof one: the best model available was still blind
&lt;/h2&gt;

&lt;p&gt;The benchmark ran the same task on thirteen real Ruby repos. Pick the hub model of an app, the &lt;code&gt;Inbox&lt;/code&gt;, the &lt;code&gt;MergeRequest&lt;/code&gt;, the &lt;code&gt;Spree::Order&lt;/code&gt;, and ask the agent to find every place that depends on it before a teardown change. The non-obvious dependents, the ones scattered through concerns and workers and config-string registries, were pinned by hand against the source and kept hidden. The agent gets graded against that key, every citation checked to a real &lt;code&gt;file:line&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The headline arm ran on Claude Code with Opus 4.8. Not a budget model. The best coding model money could buy when this ran.&lt;/p&gt;

&lt;p&gt;Cold, on Chatwoot, it found 2 of the 11 scattered dependents. On a rerun of the identical prompt, 0. It missed nine that a maintainer has to check before touching that model. On GitLab, the largest open-source Rails monolith there is, it ground for five minutes per run and surfaced 2 of 16. The audit it wrote each time read as finished. It wasn't. It was two-sixteenths of the truth in a tidy, confident report.&lt;/p&gt;

&lt;p&gt;Handed the map, the same model, same prompt, same pinned commit, caught 11 of 11 on Chatwoot and 13 of 16 on GitLab.&lt;/p&gt;

&lt;p&gt;You can wave that off as one model having a bad day. The next part you can't. The same lift showed up on GPT-5.5, from a different lab, on different weights. Chatwoot baseline 0.47, with the map 0.82. Across the whole bench, every agent gained: Opus +0.26, Devstral +0.24, Qwen +0.18, Kimi +0.13, GPT-5.5 +0.13. Five models, three model families, US-frontier to China-open to EU-open. Same direction every time.&lt;/p&gt;

&lt;p&gt;This isn't a weak-model artifact. It isn't a single-lab artifact. The frontier needed the map too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof two: a computed fact and an inferred guess are different categories
&lt;/h2&gt;

&lt;p&gt;A better model can't fix this one.&lt;/p&gt;

&lt;p&gt;When the map answers "what depends on &lt;code&gt;MergeRequest&lt;/code&gt;," it isn't being clever. It walked the call-and-dependency graph and returned the resolved set. 932 symbols, the same 932 every time you ask. It's a computation over a closed world. The answer is deterministic, and dead-code reasoning works because the world is closed. If it isn't in the graph, it isn't a caller.&lt;/p&gt;

&lt;p&gt;When the LLM answers the same question, it's inferring. It reads some files, pattern-matches against every Rails app in its training set, and produces its best guess. Run it twice on Chatwoot and you get 2, then 0. Same prompt. Different answer. That's not a bug in the model. That's what inference is.&lt;/p&gt;

&lt;p&gt;Now turn a better model loose on each side.&lt;/p&gt;

&lt;p&gt;A better model computes nothing new, because it was never computing. It infers. And a stronger inference engine infers more &lt;em&gt;confidently&lt;/em&gt;, not more &lt;em&gt;correctly&lt;/em&gt;. Watch where that goes wrong. On Discourse, the plain agent found a dependent of &lt;code&gt;Upload&lt;/code&gt; and described its role backwards, named the class that listens for an upload as the one that renders it. On Solidus it called a promotion calculator the rule that decides eligibility. Both audits read beautifully. Both would send a maintainer to change the wrong thing.&lt;/p&gt;

&lt;p&gt;A smarter model states that kind of mistake with more authority, not less. The confident-wrong answer is the dangerous one, because you only catch it after you've trusted it. Scaling up the inference makes the prose better and the confidence higher. It does not turn a guess into a computation.&lt;/p&gt;

&lt;p&gt;That gap, between a thing computed and a thing inferred, does not shrink as inference improves. It's a category difference, not a quality difference. You don't close it by buying more inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scene that made this concrete for me
&lt;/h2&gt;

&lt;p&gt;The moment it landed: the best model I could pay for, pointed at a famous open-source Rails app, writing a clean audit of a model's teardown, and quietly blind to nine of the things that would break. No flailing. No "I'm not sure." Just a composed, wrong-by-omission report, the kind you'd ship.&lt;/p&gt;

&lt;p&gt;The model wasn't failing at reasoning. It was reasoning perfectly over the half of the structure it could see. The other half never lived in the files it read. It lived in the edges between them, and you can't reason your way to an edge you never loaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  So the map isn't a model bet. It's infrastructure.
&lt;/h2&gt;

&lt;p&gt;Whether this is worth adopting turns on one thing. If a code map were just a stopgap for weak models, you'd be betting on a losing horse and you should wait. It isn't, for three reasons that have nothing to do with which LLM is winning this quarter.&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;It's repo-specific.&lt;/strong&gt; The map is built from &lt;em&gt;your&lt;/em&gt; code at its current commit. No model memorized your private monolith. It can recite the public Rails API from training, sure, but the win on Rails sat entirely on the non-memorized query-compiler internals, the part that never made it into the weights. Your code is all non-memorized. The map is the only thing that has read it.&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;It's always current.&lt;/strong&gt; It re-indexes as files change, as you switch branches, as a &lt;code&gt;git pull&lt;/code&gt; lands. A model's knowledge is frozen at its training cutoff. The map's knowledge is frozen at your last save. One of those is months stale by definition. The other never is.&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;It's LLM-agnostic.&lt;/strong&gt; It serves the graph over MCP, the wire your editor already uses. One index, every agent. Switch from Opus to GPT-5.5 to whatever ships next quarter and the map doesn't care. You are never stranded by a model swap, because the thing that knows your codebase isn't the model.&lt;/p&gt;

&lt;p&gt;A bet rides on one outcome. Infrastructure outlasts the outcomes. This is the second kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the boundary actually is
&lt;/h2&gt;

&lt;p&gt;The honest version of all this: the map's value isn't fixed. On a small, readable, colocated gem, a strong model reads the whole dependent set in one pass and the map adds nothing. One repo in the bench was a flat tie for exactly that reason. The boundary is real, and the small-repo half of the board is a tie because models keep getting better at reading.&lt;/p&gt;

&lt;p&gt;But the apps you actually ship aren't gems. They grow. And they grow faster than context windows do. The dependents that matter spread further apart as the codebase gets larger, which is precisely when the model can no longer hold them all. The boundary moves, every model release nudges it. It never closes, because the codebase is always on the other side of it, getting bigger.&lt;/p&gt;

&lt;p&gt;That's why "wait for the next model" isn't a plan. The next model moves the line a little. Your codebase moved it back further while you were reading this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the claim on your own code
&lt;/h2&gt;

&lt;p&gt;Don't take the bench's word, or mine. The cheapest possible check is one you run yourself, and it takes a few minutes.&lt;/p&gt;

&lt;p&gt;Pick the model in your own app you'd schedule a careful afternoon for. Ask your agent cold, &lt;em&gt;"before I change how this model is torn down, find every place that depends on it."&lt;/em&gt; Watch it grep and guess. Note the count.&lt;/p&gt;

&lt;p&gt;Then give it the map.&lt;/p&gt;

&lt;p&gt;→ &lt;code&gt;curl -fsSL https://luuuc.github.io/sense/install.sh | sh&lt;/code&gt;&lt;br&gt;
→ &lt;code&gt;sense scan&lt;/code&gt; in the repo you know cold&lt;br&gt;
→ &lt;code&gt;sense setup&lt;/code&gt; to connect your agent&lt;/p&gt;

&lt;p&gt;Ask again. Diff the two answers. If your repo is small and clean, you'll watch a tie, and that's a real result you can trust. Point it at the biggest app in your org and watch the gap open instead. The gap is the part of your own codebase your model can't currently see, and no upgrade on the calendar closes it for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/luuuc/sense/blob/main/bench/verticals/ruby-rails/results/report.md" rel="noopener noreferrer"&gt;The benchmark, the methodology, the raw data for all thirteen.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I build Sense. That's exactly why I went hunting for the data that would sink it, and why all of it is open, the code, the harness, the pinned commits, the transcripts, the judge prompts. Disbelieve this and rerun it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;PS. If you think the next model release closes this, name the release and the repo and I'll run the teardown audit on it, cold then mapped, in public. I want to be wrong about this more than you want me to be. So far the data hasn't let me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rails</category>
      <category>ruby</category>
      <category>devtools</category>
    </item>
    <item>
      <title>"Ruby is the most AI-friendly stack" is half true</title>
      <dc:creator>Luc B. Perussault-Diallo</dc:creator>
      <pubDate>Mon, 06 Jul 2026 15:55:55 +0000</pubDate>
      <link>https://dev.to/luuuc/ruby-is-the-most-ai-friendly-stack-is-half-true-1ac4</link>
      <guid>https://dev.to/luuuc/ruby-is-the-most-ai-friendly-stack-is-half-true-1ac4</guid>
      <description>&lt;p&gt;You've seen the claim in every Ruby thread for the past year. Ruby and Rails are the most AI-friendly stack. Fewer tokens, less hallucination, the model just writes it cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Half of that claim I'll concede without a fight&lt;/strong&gt;. The other half I measured, across thirteen real Ruby codebases, and that's where a line shows up, sharp enough to put every repo on one side or the other. Including yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The half that's true: writing Ruby is solved
&lt;/h2&gt;

&lt;p&gt;Start with the part that holds up, because it really does.&lt;/p&gt;

&lt;p&gt;A model that has seen ten thousand Rails apps knows where the model lives, where the job goes, what a concern does, what &lt;code&gt;has_many&lt;/code&gt; implies, before it reads a line of yours. Convention over configuration was always written partly for the next human reading the code. It turns out the model is the next reader too, and the conventions answer half its questions before it asks them.&lt;/p&gt;

&lt;p&gt;So "write me a service object," "add a scope," "refactor this controller"? The stack carries the model. Fewer wrong guesses, tighter loops, less to hallucinate because the shape is already known. Anyone who builds on Rails has lived this, and the AI-friendly reputation earned it. I'm not here to take that away.&lt;/p&gt;

&lt;p&gt;I'm here to point out it answers a question nobody dangerous is asking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The half that isn't: navigating Ruby at scale
&lt;/h2&gt;

&lt;p&gt;"Can AI write Ruby" is settled. The question that ships broken deploys is different: &lt;strong&gt;can AI navigate Ruby?&lt;/strong&gt; What breaks if this model changes, who depends on it, where the blast radius ends.&lt;/p&gt;

&lt;p&gt;Reading and navigating feel like the same skill when you're fluent. They are not the same skill for an agent. Reading a file is local, the answer is right there in the text. Navigating is structural, the answer lives in the edges between files, what calls what, what breaks what, and no single file contains it.&lt;/p&gt;

&lt;p&gt;So I ran the structural question on all thirteen repos. Same task each time: take the hub model, the &lt;code&gt;Inbox&lt;/code&gt;, the &lt;code&gt;MergeRequest&lt;/code&gt;, the &lt;code&gt;Spree::Order&lt;/code&gt;, and find every dependent before a teardown change. The scattered, non-obvious dependents were pinned by hand and kept hidden. Two arms, same model, same commit: a plain agent that greps and infers, and the same agent handed a structural map it can query. Graded against the hidden key, every citation checked to a &lt;code&gt;file:line&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now look at the shape of the board. Baseline recall, then with the map:&lt;/p&gt;

&lt;p&gt;→ Chatwoot, &lt;code&gt;Inbox&lt;/code&gt; → 0.29 → 0.97&lt;br&gt;
→ Mastodon, &lt;code&gt;Status&lt;/code&gt; → 0.28 → 0.83&lt;br&gt;
→ GitLab, &lt;code&gt;MergeRequest&lt;/code&gt; → 0.26 → 0.67&lt;br&gt;
→ Discourse, &lt;code&gt;Upload&lt;/code&gt; → 0.35 → 0.75&lt;br&gt;
→ Solidus, &lt;code&gt;Spree::Order&lt;/code&gt; → 0.40 → 0.68&lt;br&gt;
→ Forem, &lt;code&gt;Article&lt;/code&gt; → 0.35 → 0.63&lt;br&gt;
→ Rails, &lt;code&gt;ActiveRecord::Relation&lt;/code&gt; → 0.67 → 0.92&lt;/p&gt;

&lt;p&gt;Then the gap collapses:&lt;/p&gt;

&lt;p&gt;→ Lobsters, &lt;code&gt;Story&lt;/code&gt; → 0.68 → 0.79&lt;br&gt;
→ Redmine, &lt;code&gt;Issue&lt;/code&gt; → 0.67 → 0.78&lt;br&gt;
→ RubyLLM, &lt;code&gt;provider/Chat&lt;/code&gt; → 0.76 → 0.80&lt;br&gt;
→ raix → 0.60 → 0.73&lt;br&gt;
→ llm.rb, &lt;code&gt;provider&lt;/code&gt; → 0.48 → 0.48&lt;/p&gt;

&lt;p&gt;That bottom group is the friendly reputation, intact. Small, readable, colocated repos where Ruby names its dependents literally and keeps implementations close. A strong agent reads &lt;code&gt;app/models&lt;/code&gt; whole, finds the set, and the map adds a dependent or two. On llm.rb it added nothing to the answer at all, it just reached the same one on a fifth fewer tokens. That half of the claim is true with no asterisk.&lt;/p&gt;

&lt;p&gt;One repo sits in neither group. langchainrb, 0.24 → 0.41: a small gem that scored like a monolith. The model "knows" langchainrb. It knows a version that no longer ships, because the gem moved faster than the training data. Hold that thought, it comes back.&lt;/p&gt;

&lt;p&gt;The top group is the same stack at a size where legibility stops scaling. The hub model's dependents reach it through associations, concerns, polymorphism, and config-string registries, scattered across directories that share no token to grep for. The plain agent finds a fraction and stops, confidently. It's not Chatwoot being messy. It's the opposite, it's decomposed the way the books tell you to decompose, and that's what spreads the dependents out of grep's reach.&lt;/p&gt;

&lt;p&gt;It's one problem at two scales. Below the line, the model reads the whole repo and the map is redundant. Above it, the model works from a sample of your codebase, and it doesn't tell you which sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things on that board I didn't expect
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The smallest model got the second-biggest lift.&lt;/strong&gt; I ran the bench across five models, two frontier, three open. The lifts: Opus +0.26 at the top, and right behind it Devstral, the smallest open model in the set, at +0.24, ahead of everything else I ran, including the other frontier model. That looks backwards until you see why. The map stands in for what the model can't hold in its own head. The less it holds, the more the map is worth. Model size and repo size are the same lever pulled from two ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most famous repo was one of the hardest.&lt;/strong&gt; Forem runs dev.to itself, one of the most-read Ruby codebases on the internet. You'd guess a model handles it best. It's the cautionary one. A frontier model has partly memorized it and can sometimes recite a hub model's dependents from training, with no tools at all. The win held, but the variance is the lesson: a model reciting a repo from memory can still miss how it's wired &lt;em&gt;today&lt;/em&gt;, and you can't tell which you're getting.&lt;/p&gt;

&lt;p&gt;Rails is the clean version of the same effect. Every model has the Rails source memorized, and the map's whole margin there sits on the query-compiler internals, the part that never made it into the weights. And langchainrb, the gem from earlier that scored like a monolith, is the stale version: the model recites a langchainrb that no longer ships. Fame makes a repo more recited, not better understood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exit you're reaching for, and why it's closed
&lt;/h2&gt;

&lt;p&gt;Everyone reading a piece like this quietly exempts themselves. &lt;em&gt;My code is clean. My app is well factored. I'd know if something hid. I'm in the bottom group.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Maybe you are. Today. The line isn't fixed.&lt;/p&gt;

&lt;p&gt;The day your repo grows past what the model can hold in one pass, you move from the half where grep is enough to the half where it isn't. There's no deprecation warning for outgrowing your agent. You find out the way you always do, from a diff that sailed through review, three weeks later, in production.&lt;/p&gt;

&lt;p&gt;And the line is moving the wrong way for the comforting story. Models keep getting better at reading, which is why the small-repo half is a tie and that tie only gets safer. But the apps you actually ship grow faster than context windows do, and their dependents spread further apart as they grow. The half of the board where the map wins is the half that describes real production work, and it's the half getting larger.&lt;/p&gt;

&lt;p&gt;This is also why "wait for a better model" doesn't resolve it. A better model reads more, so it pushes the line. Your codebase grows, so it pushes the line back further. The map isn't a bet on weak models being weak forever. It's repo-specific (built from your code at this commit, which no model memorized), it stays current as your code changes, and it serves any agent over MCP, so a model swap never strands you.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can't reason your way to which half you're on. Test it.
&lt;/h2&gt;

&lt;p&gt;It takes a few minutes on your own code.&lt;/p&gt;

&lt;p&gt;Pick the model in your own app you'd schedule a careful afternoon for. Ask your agent cold, &lt;em&gt;"before I change how this model is torn down, find every place that depends on it."&lt;/em&gt; Watch it grep and guess. Count what it found.&lt;/p&gt;

&lt;p&gt;Then give it the map.&lt;/p&gt;

&lt;p&gt;→ &lt;code&gt;curl -fsSL https://luuuc.github.io/sense/install.sh | sh&lt;/code&gt;&lt;br&gt;
→ &lt;code&gt;sense scan&lt;/code&gt; in the root of the app you know best&lt;br&gt;
→ &lt;code&gt;sense setup&lt;/code&gt; to connect your agent&lt;/p&gt;

&lt;p&gt;Ask again, and diff. Small and clean? You'll watch a tie, and that's a real, trustworthy answer, your repo is on the friendly half and you can stop worrying. Big app? Watch the gap open. Either way you walk out with the number the reputation can't give you: which half you're actually on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/luuuc/sense/blob/main/bench/verticals/ruby-rails/results/report.md" rel="noopener noreferrer"&gt;The whole board, the methodology, the raw data for all thirteen.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I build the map in this experiment, so I'm not a neutral narrator here. The code, the harness, the pinned commits, and the transcripts are all open, so rerun it and trust your own number over mine.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;PS. The engineers most certain their repo is on the clean half are right about half the time. The other half send me a screenshot a week later. Run it before you're sure which one you are.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>ai</category>
    </item>
    <item>
      <title>I recorded my agent auditing a 36k-file Rails app: the play-by-play</title>
      <dc:creator>Luc B. Perussault-Diallo</dc:creator>
      <pubDate>Sat, 04 Jul 2026 17:05:41 +0000</pubDate>
      <link>https://dev.to/luuuc/i-recorded-my-agent-auditing-a-36k-file-rails-app-the-play-by-play-10h3</link>
      <guid>https://dev.to/luuuc/i-recorded-my-agent-auditing-a-36k-file-rails-app-the-play-by-play-10h3</guid>
      <description>&lt;p&gt;I gave a coding agent a real maintainer chore on the largest open-source Rails monolith there is, and recorded the whole session. Then I gave it the same chore with a map of the codebase and recorded that one too.&lt;/p&gt;

&lt;p&gt;What follows is the play-by-play. The agent's moves are real, lightly trimmed for length; my commentary is the indented notes between them. Watch where the first run stops, and why it doesn't know it stopped.&lt;/p&gt;

&lt;p&gt;The repo is GitLab, indexed at a pinned commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gitlabhq @ 1f9c256f0
tracked files / ruby       68,289 / 29,784
files indexed (coverage)   36,829 (100%)
symbols / graph edges      177,929 / 1,121,147
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A million edges in one Rails app. Nobody holds that in their head. The question is whether the agent can rebuild enough of it from files to do the job.&lt;/p&gt;

&lt;p&gt;The task, identical on both runs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are about to rework how a &lt;code&gt;MergeRequest&lt;/code&gt;, and everything attached to it, behaves when it changes or is torn down. Before touching the model, audit every place that depends on the &lt;code&gt;MergeRequest&lt;/code&gt; contract.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The gold set was built by hand against the source first: 16 scattered dependents, planted across &lt;code&gt;app/services&lt;/code&gt;, &lt;code&gt;app/workers&lt;/code&gt;, &lt;code&gt;app/models&lt;/code&gt;, &lt;code&gt;app/graphql&lt;/code&gt;, &lt;code&gt;app/serializers&lt;/code&gt;, and &lt;code&gt;lib/&lt;/code&gt;, so no single grep rounds them all up. The agent never sees the list. It gets graded against it.&lt;/p&gt;

&lt;p&gt;Same model both runs (Claude Code, Opus 4.8). Same prompt. Same commit. The only variable is the map.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run 1, cold. The grep grind.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[00:00]  me → audit every dependent of MergeRequest before the rework
[00:06]  agent → grep -rin "merge_request" app/ lib/ ee/ | wc -l
&lt;/span&gt;&lt;span class="gp"&gt;         =&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;41,800+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;First move, the only move it has without structure: search the token. Forty-one thousand hits. That's not a list of dependents, it's a second codebase. The agent has a token budget, not a weekend.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[00:31]  agent → grep -rinE "belongs_to :merge_request|has_many :merge_requests"
&lt;/span&gt;&lt;span class="gp"&gt;         =&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;380+ named associations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This part it does well. The named associations come back fast, and the Rails conventions earn their reputation here, a service in app/services, a worker in app/workers, named for what it is. The agent reads these correctly. The easy third of the answer.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[01:10]  agent → grep -rinE "merge_request_id|\.merge_request\b|MergeRequest\." | wc -l
&lt;/span&gt;&lt;span class="gp"&gt;         =&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;9,200+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Now the real work, and the dead end. Nine thousand hits, and the agent has to decide which are dependents and which are an incidental local variable or a log line. It samples. It can't read all of them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It walks straight past things like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Issuable is mixed into both Issue and MergeRequest.&lt;/span&gt;
&lt;span class="c1"&gt;# Code that resolves notes on "the issuable" depends on MergeRequest&lt;/span&gt;
&lt;span class="c1"&gt;# without ever typing the string a grep could match.&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Issuable&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolvable_discussions&lt;/span&gt;
    &lt;span class="vi"&gt;@resolvable_discussions&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;discussion_notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolvable&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The notes-resolution service depends on &lt;code&gt;MergeRequest&lt;/code&gt; through the &lt;code&gt;Issuable&lt;/code&gt; concern. It never types the class name. No grep for &lt;code&gt;MergeRequest&lt;/code&gt; reaches it. The dependents that matter are exactly the ones with no shared token to match on.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[04:50]  agent → [writes the audit]
         "Audited the dependents of MergeRequest. Found: the
          named associations, the diff serializer, the pipeline
          link. The contract is depended on primarily through..."
[05:12]  agent → done.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Five minutes, near the harness ceiling. It ground honestly the whole way and produced a calm, finished-looking report. It never invented a citation, every dependent it named is real. It just found 2 of the 16 that matter. On a rerun, 1. It does not know it stopped at two. Nothing in the output could tell it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Cold result: 2 then 1 of 16. Scattered recall 0.09. The audit looked complete.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That last sentence is the whole danger. On a small repo you'd notice two-sixteenths. On a monolith you ship it, and the fourteen you missed are the ones that page someone at 3am.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run 2, same task, with the map.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[00:00]  me → [identical prompt]
[00:03]  agent → sense_blast MergeRequest

         MergeRequest  (app/models/merge_request.rb)
           932 symbols in blast radius
           app/workers/merge_requests/process_auto_merge_worker.rb
           app/services/discussions/resolve_service.rb
           app/serializers/merge_request_diff_entity.rb
           app/graphql/types/issuable_type.rb
           ee/app/services/ee/milestones/promote_service.rb
           lib/gitlab/url_builder.rb
           ... 926 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;One call. 932 symbols, the resolved set, returned in a single shot instead of reconstructed from 41,800 grep hits. The notes-resolution service that the &lt;code&gt;Issuable&lt;/code&gt; concern hid? It's right there in the list. The map walked the edge the grep couldn't see.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[00:18]  agent → [reads each candidate, pins to file:line]
[03:40]  agent → [writes the audit]
         "16-item dependency audit of MergeRequest, each pinned
          to file:line: auto-merge worker, notes-resolution
          service, cycle-analytics builder, API discussions,
          GraphQL issuable, Jira integration, milestone
          promotion, ghost-user handler, timelog..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Same model. Same budget, spent differently, on reading and pinning instead of hunting. It caught 13 of 16 on its best run, a floor of 10. Twelve of the dependents it found are ones the cold runs reached in &lt;em&gt;no&lt;/em&gt; attempt:&lt;/p&gt;

&lt;p&gt;auto-merge worker · notes-resolution service · cycle-analytics builder · API discussions · GraphQL issuable · Jira integration · milestone promotion · ghost-user handler · timelog · timeline event · URL builder · enterprise discussion&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Mapped result: 10 then 13 of 16. Scattered recall 0.72, full audit 0.26 → 0.67.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Two things the recording taught me that the scores don't
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The map cost more here, and I'm not hiding it.&lt;/strong&gt; On this run the map billed about 9% &lt;em&gt;more&lt;/em&gt; tokens, not fewer (27,604 → 30,128). On other repos it came in cheaper. Token cost is task-dependent and I'd never compare it across agents. What didn't move is reach: 2.6x more of the real dependent set, plus the twelve silent breaks. Nine percent more tokens to go from two of sixteen to thirteen is a rounding error against the incident you didn't have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first time I ran this, the map lost, and that's why I trust it now.&lt;/strong&gt; Early runs scored 12, then 8, then 1, all over the place. The lazy read was to blame the scenario. The transcripts said otherwise: &lt;code&gt;sense_blast&lt;/code&gt; was returning a different set of callers each call. On a hub this size almost every dependency is a plain method call sharing one confidence score, and the index capped that tied list with an unstable sort, even evicting &lt;em&gt;direct&lt;/em&gt; callers for distant ones. A non-reproducible impact analysis, handed to any large-repo user, silently. The fix made the cap deterministic, ties broken by confidence then direct-over-indirect, and it ships for everyone now. The benchmark was supposed to score the tool. It kept fixing it instead.&lt;/p&gt;

&lt;p&gt;That determinism is also the quiet argument for why this is structure, not a model trick. The map computes the same 932 every time. A model infers a different answer every run, you watched it go 2 then 1. A better model infers more confidently, not more reproducibly. The map reads this repo at this commit, not a training snapshot, and any agent can call it over MCP. None of that rides on which model you run next quarter. It's the part that doesn't change when the model does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record your own
&lt;/h2&gt;

&lt;p&gt;This is worth watching on your own code, because your monolith has a &lt;code&gt;MergeRequest&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;Pick it, the model half your services reach into and nobody fully tracks. Ask your agent cold, &lt;em&gt;"before I change how this model is torn down, find every place that depends on it."&lt;/em&gt; Watch the grep grind. Count the answer.&lt;/p&gt;

&lt;p&gt;Then give it the map.&lt;/p&gt;

&lt;p&gt;→ &lt;code&gt;curl -fsSL https://luuuc.github.io/sense/install.sh | sh&lt;/code&gt;&lt;br&gt;
→ &lt;code&gt;sense scan&lt;/code&gt; in the root of the app that pages your team at night&lt;br&gt;
→ &lt;code&gt;sense setup&lt;/code&gt; to connect your agent&lt;/p&gt;

&lt;p&gt;Ask again and diff the two transcripts. On a tree this size, the dependents you couldn't find cold are exactly the ones the change would have broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/luuuc/sense/blob/main/bench/verticals/ruby-rails/results/report.md" rel="noopener noreferrer"&gt;The full session logs, the answer key, every transcript for thirteen repos.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I build the map in this recording. Everything you'd need to call me wrong is public, the transcripts, the harness, the pinned commit, the judge, so check the session instead of taking my read of it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;PS. The scariest frame in that whole recording is the cold run writing a composed, confident audit of two dependents out of sixteen and signing off. No flailing. That composure is the thing to be afraid of on a big repo.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rails</category>
      <category>ruby</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Audit your AI agent's blind spots in 4 commands</title>
      <dc:creator>Luc B. Perussault-Diallo</dc:creator>
      <pubDate>Thu, 02 Jul 2026 16:01:06 +0000</pubDate>
      <link>https://dev.to/luuuc/audit-your-ai-agents-blind-spots-in-4-commands-51nb</link>
      <guid>https://dev.to/luuuc/audit-your-ai-agents-blind-spots-in-4-commands-51nb</guid>
      <description>&lt;p&gt;Don't take my word for any of it. Run this on a repo you know cold and read the diff yourself. Four commands, about five minutes, and you'll know exactly what your coding agent can and can't see in your own codebase.&lt;/p&gt;

&lt;p&gt;No theory below this line. Just the steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick the model first
&lt;/h2&gt;

&lt;p&gt;Open the app you know best. Pick the one model you'd schedule a careful afternoon to refactor, the hub half the app leans on. &lt;code&gt;Inbox&lt;/code&gt;, &lt;code&gt;Order&lt;/code&gt;, &lt;code&gt;Account&lt;/code&gt;, &lt;code&gt;Booking&lt;/code&gt;, &lt;code&gt;MergeRequest&lt;/code&gt;. The one nobody volunteers to touch on a Friday because nobody's sure what depends on it.&lt;/p&gt;

&lt;p&gt;That's your test subject. Hold it in mind for all four steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1, ask your agent cold
&lt;/h2&gt;

&lt;p&gt;In your normal agent (Claude Code, Cursor, Copilot, whatever you drive), ask the structural question with no tools beyond what it has today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;before I change how the Inbox model is torn down,
find every place in the codebase that depends on it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;Inbox&lt;/code&gt; for your model. Then watch &lt;em&gt;how&lt;/em&gt; it answers, not just what it says. It'll grep a token, open what it lands in, sample a few neighbors, and rebuild the relationships in its head. You'll see a chain that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rin&lt;/span&gt; &lt;span class="s2"&gt;"inbox"&lt;/span&gt; app/ lib/        &lt;span class="c"&gt;# hundreds of hits, most irrelevant&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rinE&lt;/span&gt; &lt;span class="s2"&gt;"belongs_to :inbox|has_many :inboxes"&lt;/span&gt;   &lt;span class="c"&gt;# the named associations, found fast&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rin&lt;/span&gt; &lt;span class="s2"&gt;"inbox_id"&lt;/span&gt;               &lt;span class="c"&gt;# and now the guessing starts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Save the answer it gives you.&lt;/strong&gt; Count the dependents it lists. It'll read as complete, confident, well-structured. That feeling is the thing we're about to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2, install the map
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://luuuc.github.io/sense/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One binary. It builds a local index of your codebase's call-and-dependency graph and serves it to your agent over MCP, the same wire your editor already uses to reach the model. Nothing leaves the machine. It never edits a line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3, scan the repo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sense scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the root of your app. This is the one-time build. When it finishes it'll print the shape of your codebase, files, symbols, edges between them, the graph your agent was trying to reconstruct from greps a moment ago. On a large app that's a number no human holds in their head, which is the whole reason the cold run missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4, connect your agent and ask again
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sense setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wires the structural tools into your agent so it reaches for them on its own. (That last part matters, an agent that never calls the map scores like one that never had it.)&lt;/p&gt;

&lt;p&gt;Now ask the identical question from Step 1 again. This time the agent makes one structural call instead of a hundred greps. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sense_blast Inbox
&lt;span class="go"&gt;Inbox  (app/models/inbox.rb)
  110 symbols in blast radius
  app/services/...    app/workers/...    lib/...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It gets the resolved dependent set back in one shot and spends its budget reading and pinning each file instead of hunting for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now diff the two answers
&lt;/h2&gt;

&lt;p&gt;Put the cold audit from Step 1 next to the mapped one. The gap between them is the answer.&lt;/p&gt;

&lt;p&gt;It won't be the obvious associations, the agent found those cold, the named ones grep catches. It'll be the dependents that reach your model sideways, through a concern, a service, a worker that loads it by id three calls deep, a config-string registry. The ones with no shared token to search for. On the benchmark that produced this experiment, a frontier agent found 2 of 11 of those cold on one well-built Rails app and 11 of 11 with the map. Your numbers will be your own. The shape will be familiar.&lt;/p&gt;

&lt;p&gt;Two things to notice in your own diff:&lt;/p&gt;

&lt;p&gt;→ The cold agent almost certainly didn't &lt;em&gt;invent&lt;/em&gt; anything. Everything it listed was real. Its failure is omission, not invention. Nothing in the output warns you it stopped early.&lt;/p&gt;

&lt;p&gt;→ If your repo is small and colocated, the diff might be tiny or zero. That's not a failed experiment. That's a real, trustworthy answer: your agent can already see your whole structure, and you can stop wondering. The gap opens on the big apps, which is exactly where you couldn't afford it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you just proved
&lt;/h2&gt;

&lt;p&gt;The map didn't make your model smarter. It gave it something to read that was never in any single file, the edges between them. And it'll keep doing that across model upgrades, because it's built from &lt;em&gt;your&lt;/em&gt; code at this commit (which no model memorized), it re-indexes as your code changes, and it serves whatever agent you point at it. It's not a bet on today's model. It's the structure your agent was missing under any model.&lt;/p&gt;

&lt;p&gt;The diff you just ran is the part of your codebase your agent has been quietly guessing at. Now you've seen the number.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/luuuc/sense/blob/main/bench/verticals/ruby-rails/results/report.md" rel="noopener noreferrer"&gt;The benchmark behind this, the methodology, the raw data for thirteen real repos.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: I build the map (Sense). It's all open, so check it instead of trusting me. But the only check that matters here is the one you just ran on your own code.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>ai</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The AI judge that called a half-finished audit 'exhaustive'</title>
      <dc:creator>Luc B. Perussault-Diallo</dc:creator>
      <pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/luuuc/the-ai-judge-that-called-a-half-finished-audit-exhaustive-45n7</link>
      <guid>https://dev.to/luuuc/the-ai-judge-that-called-a-half-finished-audit-exhaustive-45n7</guid>
      <description>&lt;p&gt;If you're building anything with an LLM judge in the loop, this is the failure mode that will get you, and you won't see it happen. I didn't, until I went looking for the opposite.&lt;/p&gt;

&lt;p&gt;The story, in the order it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing I was building
&lt;/h2&gt;

&lt;p&gt;I wanted to measure something specific: can an AI coding agent navigate a real codebase, not just read one file, but answer "what depends on this model, everywhere, before I change it." That's the question a maintainer answers in their head before a risky refactor, and it's the one an agent tends to get confidently wrong.&lt;/p&gt;

&lt;p&gt;So I built a benchmark. Pick the hub model of a real app, the &lt;code&gt;Inbox&lt;/code&gt; in Chatwoot, and ask the agent to find every dependent before a teardown change. Run it two ways: a plain agent that greps and infers, and the same agent handed a structural map of the codebase it can query. Same model, same prompt, same pinned commit. Measure the difference.&lt;/p&gt;

&lt;p&gt;The catch with any benchmark like this is the grading. You can't eyeball a hundred audits. So, like everyone does now, I reached for an AI judge. Hand the model the agent's audit, ask it to score how complete and correct the analysis is. Cheap, fast, scales.&lt;/p&gt;

&lt;p&gt;That decision is the one that nearly sank the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The run that looked like a tie
&lt;/h2&gt;

&lt;p&gt;First real scenario, Chatwoot. Two arms ran. The judge scored them.&lt;/p&gt;

&lt;p&gt;It called them a tie.&lt;/p&gt;

&lt;p&gt;That stopped me, because I'd watched both transcripts. The plain agent had found a couple of the scattered dependents and stopped. The mapped agent had walked the whole set. They were not the same audit. One was clearly more complete than the other. The judge couldn't tell.&lt;/p&gt;

&lt;p&gt;I pulled up what the judge actually wrote about the plain run. The word it used was "exhaustive."&lt;/p&gt;

&lt;p&gt;The audit it called exhaustive was 44% complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it got fooled
&lt;/h2&gt;

&lt;p&gt;The mechanic is worth sitting with, because it generalizes to every eval you'll ever build.&lt;/p&gt;

&lt;p&gt;The judge was grading each answer on its own merits. It read the plain agent's audit and found it well-written, internally consistent, confident, structured like a thorough piece of work. Every dependent it listed was real. Nothing was fabricated. By every signal available &lt;em&gt;inside the text&lt;/em&gt;, it looked like a complete job.&lt;/p&gt;

&lt;p&gt;The judge had no idea what a complete job contained. It was never told. It had nothing to compare against, so "looks thorough" was the only axis it could score on, and the plain agent's audit looked extremely thorough. It was a beautifully written half.&lt;/p&gt;

&lt;p&gt;That's the trap under every AI-grades-AI setup. The judge inherits the same blind spot as the thing it's grading. Both are reading the same text. Neither knows what isn't there. A confident, fluent, half-finished answer doesn't read as half-finished. It reads as done. And "reads as done" is the exact signal a judge with no reference latches onto.&lt;/p&gt;

&lt;p&gt;This is the same failure I was building the whole benchmark to expose in coding agents, and it had crept into my own scoring. The bench was about to flatter itself, and I'd have published the tie with a straight face.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that flipped it
&lt;/h2&gt;

&lt;p&gt;The fix wasn't a better judge model. A smarter model with no reference makes the same mistake more eloquently.&lt;/p&gt;

&lt;p&gt;The fix was a reference.&lt;/p&gt;

&lt;p&gt;Before any run, I'd already built an answer key by hand: every real dependent of the &lt;code&gt;Inbox&lt;/code&gt;, pinned to an exact &lt;code&gt;file:line&lt;/code&gt;, assembled from the source. The scattered ones a grep sweep slides past, eleven of them, kept hidden from the agents. I had the list of right answers the whole time. I just wasn't giving it to the judge.&lt;/p&gt;

&lt;p&gt;So I changed the judge's job. Stop assessing whether this reads as complete. Take this verified list of what an honest audit must contain, and check each item off. Found it, pinned to the right line, or didn't.&lt;/p&gt;

&lt;p&gt;The tie evaporated on the spot. The mapped agent covered everything. The plain one covered half. Same audits, same judge model. The only thing that changed was that the judge now knew what the answer was supposed to be.&lt;/p&gt;

&lt;p&gt;The reference-blind judge was retired for the entire series after that run. Every score I've published since is reference-aware, graded against a fixed key, with no credit for a vague gesture at the right neighborhood. The citation lands on the line or it doesn't count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule, for anyone building evals
&lt;/h2&gt;

&lt;p&gt;If you take one thing from this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An LLM judge with no reference doesn't measure correctness. It measures confidence and fluency, and then it rewards them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's fine if confidence is what you want to measure. It's a disaster if you think you're measuring whether the answer is &lt;em&gt;right&lt;/em&gt;. The two come apart exactly when the answer is wrong but well-written, which is the case you most need to catch.&lt;/p&gt;

&lt;p&gt;Concretely, the things that made my scoring trustworthy after this:&lt;/p&gt;

&lt;p&gt;→ A hand-built answer key, made before the runs, so the bench can't quietly redefine "correct" to match what the tool produced.&lt;br&gt;
→ The judge grades against that key, not against the vibe of the prose.&lt;br&gt;
→ Every claim checked to a real &lt;code&gt;file:line&lt;/code&gt;. No partial credit for "mentions the right area."&lt;br&gt;
→ The key stays hidden from the agents and only the judge sees it, so it discriminates instead of leaking.&lt;/p&gt;

&lt;p&gt;None of that is exotic. It's just the difference between an eval that can embarrass you and one that can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters past my benchmark
&lt;/h2&gt;

&lt;p&gt;The reason I keep telling this story isn't the benchmark. It's that the same blindness shows up wherever you let a model judge work without a ground truth, and that's a lot of places now: AI grading support tickets, AI reviewing AI-written code, AI scoring its own RAG answers. In every one of them, "the output reads as good" is doing more of the work than anyone admits, and a fluent wrong answer scores like a correct one.&lt;/p&gt;

&lt;p&gt;It's also, not by accident, the exact thing the benchmark went on to measure in coding agents. A plain agent's audit reads as finished and is missing half. A structural map is what gives the agent, and the judge, a ground truth to check against instead of a story to believe. The map computes what depends on what. It doesn't infer it and hope. That's the whole reason it's worth wiring into your agent and not just waiting for a smarter model: a smarter model is a more convincing storyteller, and convincing is precisely the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the version that caught me
&lt;/h2&gt;

&lt;p&gt;The cleanest way to feel this is to reproduce the half I was almost fooled by.&lt;/p&gt;

&lt;p&gt;Pick a model in your own codebase you'd be nervous to refactor. Ask your agent cold, &lt;em&gt;"before I change how this model is torn down, find every place that depends on it."&lt;/em&gt; Read the audit it gives you. It'll read as complete. That's the trap.&lt;/p&gt;

&lt;p&gt;Then give it a real reference to check against.&lt;/p&gt;

&lt;p&gt;→ &lt;code&gt;curl -fsSL https://luuuc.github.io/sense/install.sh | sh&lt;/code&gt;&lt;br&gt;
→ &lt;code&gt;sense scan&lt;/code&gt; in the repo you know cold&lt;br&gt;
→ &lt;code&gt;sense setup&lt;/code&gt; to connect your agent&lt;/p&gt;

&lt;p&gt;Ask again, and diff the two. The gap between them is the part the first audit was confidently silent about, the same gap my judge scored as a tie. Counting it yourself is more convincing than any benchmark I could show you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/luuuc/sense/blob/main/bench/verticals/ruby-rails/results/report.md" rel="noopener noreferrer"&gt;The benchmark, the answer keys, the judge prompts, every transcript.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: I build the map in that experiment. All of it is open, including the judge that embarrassed me, so you can check the work instead of trusting me.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;PS. I still have the log of the judge writing "exhaustive" under a 44%-complete audit. It's the most useful wrong answer I've ever been handed, and it's the reason every score I've published since has an answer key sitting behind it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How do you benchmark an MCP server you built?</title>
      <dc:creator>Luc B. Perussault-Diallo</dc:creator>
      <pubDate>Fri, 15 May 2026 13:34:59 +0000</pubDate>
      <link>https://dev.to/luuuc/how-do-you-benchmark-an-mcp-server-you-built-2e8j</link>
      <guid>https://dev.to/luuuc/how-do-you-benchmark-an-mcp-server-you-built-2e8j</guid>
      <description>&lt;p&gt;I built a code-intelligence MCP server. Then I built a benchmark for code-intelligence MCP servers. Then my tool placed first on every scenario.&lt;/p&gt;

&lt;p&gt;I didn't believe it.&lt;/p&gt;

&lt;p&gt;So I threw the harness out and rewrote it from scratch. Same result. I built three held-out scenarios with hand-graded reference scores, ran each iteration of the bench against them, and only trusted a number when it correlated above 0.85 with my own judgment.&lt;/p&gt;

&lt;p&gt;That's the part I want to write about. Not which tool won. The methodology decisions you have to make when you are &lt;em&gt;also&lt;/em&gt; one of the contestants.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Benchmarking AI tools is harder than benchmarking models because the variable is the tool, not the model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Disclosure first.&lt;/strong&gt; I am the author of &lt;a href="https://github.com/luuuc/sense" rel="noopener noreferrer"&gt;Sense&lt;/a&gt;, one of four MCP servers in the bench, scored alongside a no-MCP baseline (Claude Code with grep, find, and Read). Everything below is in the open: code, scenarios, rubrics, transcripts, judge prompts, and the analysis of where my own tool loses. &lt;a href="https://github.com/luuuc/sense/tree/main/bench" rel="noopener noreferrer"&gt;Repo here.&lt;/a&gt; Don't take my word for any of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Fairness&lt;/th&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#1&lt;/td&gt;
&lt;td&gt;sense&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;81.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;85.4%&lt;/td&gt;
&lt;td&gt;10,896&lt;/td&gt;
&lt;td&gt;141s&lt;/td&gt;
&lt;td&gt;$6.22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#2&lt;/td&gt;
&lt;td&gt;probe&lt;/td&gt;
&lt;td&gt;77.7%&lt;/td&gt;
&lt;td&gt;84.8%&lt;/td&gt;
&lt;td&gt;12,119&lt;/td&gt;
&lt;td&gt;162s&lt;/td&gt;
&lt;td&gt;$6.23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#3&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;77.2%&lt;/td&gt;
&lt;td&gt;84.2%&lt;/td&gt;
&lt;td&gt;12,716&lt;/td&gt;
&lt;td&gt;185s&lt;/td&gt;
&lt;td&gt;$7.57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#4&lt;/td&gt;
&lt;td&gt;serena&lt;/td&gt;
&lt;td&gt;75.2%&lt;/td&gt;
&lt;td&gt;83.4%&lt;/td&gt;
&lt;td&gt;14,800&lt;/td&gt;
&lt;td&gt;191s&lt;/td&gt;
&lt;td&gt;$7.57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#5&lt;/td&gt;
&lt;td&gt;gitnexus&lt;/td&gt;
&lt;td&gt;74.9%&lt;/td&gt;
&lt;td&gt;84.5%&lt;/td&gt;
&lt;td&gt;12,964&lt;/td&gt;
&lt;td&gt;173s&lt;/td&gt;
&lt;td&gt;$6.87&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of the four MCP servers scored below the no-MCP baseline. Serena and Gitnexus, by default, made the agent worse than just letting Claude use grep, find, and Read.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why an eval framework wouldn't cut it
&lt;/h2&gt;

&lt;p&gt;That result is the question worth defending. Two MCP servers below baseline is a claim I need to back up, which is why most of my time went into the bench's methodology, not the bench's tools.&lt;/p&gt;

&lt;p&gt;I looked at the obvious frameworks first (Inspect AI from UK AISI is the closest fit for agent evals; Promptfoo, DeepEval, OpenAI Evals all have their place). They handle maybe a third of the work: the run loop, the LLM-as-judge calls, the report rendering. The other two-thirds had to be custom.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What a framework would handle&lt;/th&gt;
&lt;th&gt;What I had to build&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Run loop, prompt construction&lt;/td&gt;
&lt;td&gt;MCP server orchestration per cell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM-as-judge calls&lt;/td&gt;
&lt;td&gt;Citation grounding vs pinned repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Report rendering&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;answer_text&lt;/code&gt; / &lt;code&gt;tool_input&lt;/code&gt; split&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Fairness vs adoption layering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Held-out + Spearman anchor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Judge variance characterization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The reason is shape. An eval framework is built around "model produces output, output gets graded against expected output." That's a single-turn paradigm. What I needed was: &lt;em&gt;given an MCP server attached to Claude Code, does an agent reach a useful answer faster, cheaper, and with fewer hallucinated file paths than the same agent with no MCP?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The variable isn't the model. The model is fixed (Opus 4.7, 1M context). The variable is the tool the model has access to. None of those custom pieces exist as off-the-shelf primitives.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Citation grounding against a pinned commit
&lt;/h2&gt;

&lt;p&gt;Most evals grade text against text. I needed to grade text against a filesystem.&lt;/p&gt;

&lt;p&gt;When an AI agent says "the dispatch logic lives at &lt;code&gt;app.py:1625&lt;/code&gt;," that claim is either true or it isn't. The number is either inside the file or beyond EOF. The file is either at that path or it isn't.&lt;/p&gt;

&lt;p&gt;So every &lt;code&gt;file:line&lt;/code&gt; and &lt;code&gt;file:Symbol&lt;/code&gt; reference in the answer gets extracted by regex, then verified against the repo at the pinned commit. Three buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounded&lt;/strong&gt;: the file exists and the line is in range, or the symbol resolves within ±5 lines of the cited line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unresolved&lt;/strong&gt;: the file is not at the cited path (usually a basename-only reference where the agent dropped the directory)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hallucinated&lt;/strong&gt;: the file exists, but the line is past EOF. Outright fabrication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hallucinated is the hard signal. When you flag a line that doesn't exist in a file that does, the agent isn't paraphrasing. It's inventing.&lt;/p&gt;

&lt;p&gt;The numbers across all five were stark:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Citation grounding&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sense&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89.2%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;80.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gitnexus&lt;/td&gt;
&lt;td&gt;76.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;probe&lt;/td&gt;
&lt;td&gt;72.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;serena&lt;/td&gt;
&lt;td&gt;61.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That gap between 89% and 62% is the difference between &lt;em&gt;trusting the answer&lt;/em&gt; and &lt;em&gt;manually verifying every line reference&lt;/em&gt;. The LLM-as-judge alone would never have surfaced this. The judge reads the answer; it doesn't crawl the repo.&lt;/p&gt;

&lt;p&gt;The naive extractor handles ~95% of cases at a fraction of the complexity. It misses some edge cases (basename-only paths it correctly cannot resolve, symbols at unusual offsets) but it doesn't lie.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The &lt;code&gt;answer_text&lt;/code&gt; vs &lt;code&gt;tool_input&lt;/code&gt; split
&lt;/h2&gt;

&lt;p&gt;This one almost killed the bench before it was real.&lt;/p&gt;

&lt;p&gt;A keyword check asks: did the answer mention &lt;code&gt;TopicCreator&lt;/code&gt;? Pre-fix, the scorer searched the &lt;em&gt;entire transcript&lt;/em&gt;, including tool calls. So when the agent ran &lt;code&gt;Grep("TopicCreator")&lt;/code&gt;, the keyword &lt;code&gt;TopicCreator&lt;/code&gt; got a "hit" inside the grep invocation. Even if grep returned nothing.&lt;/p&gt;

&lt;p&gt;That's not a measurement. That's a tax on tools that don't use grep. Sense, which uses semantic search instead of grep, would lose keyword points purely because its tool calls didn't contain English-language symbol names. Probe, baseline, anything grep-flavored would win keyword points just for grepping for the right string.&lt;/p&gt;

&lt;p&gt;The fix sounds simple: keyword checks search the assistant's &lt;em&gt;prose only&lt;/em&gt;. Tool inputs and results live in a separate &lt;code&gt;audit_text&lt;/code&gt; field, available for diagnostics but never scored against.&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: scorer searched the whole transcript
&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;count_keyword_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After: scorer searches assistant prose only
&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;count_keyword_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Tool calls live in audit_text, never scored
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A bench cannot be honest if its first instinct rewards using the tools the bench's author dislikes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Fairness vs adoption: two layers, never folded
&lt;/h2&gt;

&lt;p&gt;Two questions that look similar are not the same question:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Did the developer get a better answer?&lt;/em&gt; That's fairness.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Did the agent fluently use the MCP tools available?&lt;/em&gt; That's adoption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you measure how often the agent uses an MCP server, Sense looks great. But baseline (no MCP attached) gets a structural zero on that axis. Through no fault of its own. The agent literally cannot call a server that isn't there.&lt;/p&gt;

&lt;p&gt;So if "adoption" feeds into the headline score, the headline answers the second question, not the first. That's not a benchmark. That's an MCP-adoption survey.&lt;/p&gt;

&lt;p&gt;The two-layer model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fairness  = 0.10 * keyword_coverage
          + 0.55 * llm_quality
          + 0.15 * citation_grounding
          + 0.20 * efficiency

adoption  = 0.60 * tool_fluency
          + 0.40 * discoverability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adoption is computed, reported, and never folded into fairness. It's there for code-intel-vs-code-intel comparisons only. The headline number is fairness, and baseline can beat any MCP server on it.&lt;/p&gt;

&lt;p&gt;Two of the four MCP servers I benchmarked did score below baseline on fairness. They added friction without offsetting it with answer quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If adoption had been in the formula, that finding would have been invisible.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Held-out scenarios + Spearman correlation
&lt;/h2&gt;

&lt;p&gt;The anti-Goodhart move.&lt;/p&gt;

&lt;p&gt;A self-improving benchmark can drift away from human judgment. You tune the rubric, the rubric tunes the scores, the scores look better, and at some point you're optimizing the metric instead of the underlying capability. Goodhart's Law applies to your own measurement.&lt;/p&gt;

&lt;p&gt;The defense is an anchor the loop cannot touch.&lt;/p&gt;

&lt;p&gt;Three held-out scenarios, frozen. Hand-graded reference scores in &lt;code&gt;gold.json&lt;/code&gt;. SHA256-pinned in a lockfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bench/locked/held-out.lock&lt;/span&gt;
&lt;span class="na"&gt;flask-blueprints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;transcript_sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;7f3a...&lt;/span&gt;
  &lt;span class="na"&gt;rubric_sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;91e2...&lt;/span&gt;
  &lt;span class="na"&gt;gold_sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8d4c...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The improvement loop refuses to start if any hash has drifted. Each iteration re-judges the frozen transcripts against the current rubric, then computes Spearman correlation between the current &lt;code&gt;llm_quality&lt;/code&gt; and the gold scores.&lt;/p&gt;

&lt;p&gt;Drop below 0.85, convergence fails, the loop must stop or be re-anchored.&lt;/p&gt;

&lt;p&gt;It's the line between &lt;em&gt;we tuned the bench&lt;/em&gt; and &lt;em&gt;we tuned the bench until it agrees with our own grading&lt;/em&gt;. The smallest part of the codebase. Possibly the most important.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Judge variance: what's reliable, what isn't
&lt;/h2&gt;

&lt;p&gt;You don't get to claim the LLM-as-judge is consistent. You measure it.&lt;/p&gt;

&lt;p&gt;I ran the judge twice over the same 12 transcripts. Same prompt. Same model. Default sampling.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Max stdev&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per-criterion (raw 0–1 scores)&lt;/td&gt;
&lt;td&gt;0.071&lt;/td&gt;
&lt;td&gt;&amp;lt;0.05&lt;/td&gt;
&lt;td&gt;Fails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-step (4-criterion weighted sum)&lt;/td&gt;
&lt;td&gt;0.048&lt;/td&gt;
&lt;td&gt;&amp;lt;0.05&lt;/td&gt;
&lt;td&gt;Passes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-scenario (mean of 4 steps)&lt;/td&gt;
&lt;td&gt;max |Δ| 0.014&lt;/td&gt;
&lt;td&gt;&amp;lt;0.05&lt;/td&gt;
&lt;td&gt;Rock-solid&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The judge is jittery at the criterion level, especially on a fuzzy criterion like "uncertainty." It averages down at the step level. At the scenario level, the number that actually matters for ranking tools, it's stable enough.&lt;/p&gt;

&lt;p&gt;So I use &lt;code&gt;scenario_quality&lt;/code&gt; to rank. I don't gate decisions on a single criterion-level delta of 0.05 between two tools. That's inside the noise floor. Per-criterion rationales are commentary, not data.&lt;/p&gt;

&lt;p&gt;The win here isn't proving the judge is perfect. It's knowing &lt;em&gt;where it's reliable and where it isn't&lt;/em&gt;, so I know which numbers I can defend and which ones I shouldn't quote at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reproduce it yourself
&lt;/h2&gt;

&lt;p&gt;The bench is open. Adding a new MCP server to test is one shell script in &lt;code&gt;bench/tools/&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/luuuc/sense
&lt;span class="nb"&gt;cd &lt;/span&gt;sense/bench

&lt;span class="c"&gt;# Run the full bench (12 sessions = 5 tools × 6 repos, sense + baseline at minimum)&lt;/span&gt;
bash bench/bench.sh

&lt;span class="c"&gt;# Or run a single (tool, repo) cell&lt;/span&gt;
bash bench/run.sh &lt;span class="nt"&gt;--tool&lt;/span&gt; sense &lt;span class="nt"&gt;--repo&lt;/span&gt; flask
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost: ~$40 in Opus 4.7 tokens for the full 5×6 matrix. Time: ~20 minutes wall-clock. Every transcript ends up under &lt;code&gt;bench/results/&amp;lt;tool&amp;gt;/&amp;lt;repo&amp;gt;/&lt;/code&gt; with &lt;code&gt;transcript.json&lt;/code&gt;, &lt;code&gt;scored.json&lt;/code&gt;, &lt;code&gt;judged.json&lt;/code&gt;, and &lt;code&gt;run_meta.json&lt;/code&gt; pinned to the commit the agent worked against.&lt;/p&gt;

&lt;p&gt;To add a new MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Drop a config script&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; bench/tools/yourtool.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash
# MCP server invocation for yourtool
exec your-mcp-binary --your-flags
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x bench/tools/yourtool.sh

&lt;span class="c"&gt;# 2. Run it&lt;/span&gt;
bash bench/run.sh &lt;span class="nt"&gt;--tool&lt;/span&gt; yourtool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you find a methodology hole, the bench is open and replayable. I'll patch what's real.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the bench does &lt;em&gt;not&lt;/em&gt; claim
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not a benchmark of AI model quality. The model is fixed.&lt;/li&gt;
&lt;li&gt;Not a real-world end-to-end task benchmark. Each scenario is bounded, scripted, rubric-anchored.&lt;/li&gt;
&lt;li&gt;Not a cost benchmark in production terms. Cost is computed from public API pricing for comparability, not from anyone's actual invoice.&lt;/li&gt;
&lt;li&gt;Not a measure of MCP-tool fluency in isolation. Adoption is a secondary signal, not the headline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is, narrowly, this: &lt;em&gt;given a six-scenario, four-step exploration script across six real repos, how does each tool affect the agent's answer quality, citation grounding, and efficiency.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A smaller claim than "this is the best code-intel MCP." Also a more useful one.&lt;/p&gt;




&lt;p&gt;PS: The hardest part wasn't building the tool. It was building a benchmark I'd still trust after it favored me. Most benchmarks die at that step.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Bench (code, scenarios, rubrics, replay): &lt;a href="https://github.com/luuuc/sense/tree/main/bench" rel="noopener noreferrer"&gt;github.com/luuuc/sense/tree/main/bench&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full results report: &lt;a href="https://github.com/luuuc/sense/blob/main/bench/results/report-for-humans.md" rel="noopener noreferrer"&gt;report-for-humans.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sense: &lt;a href="https://github.com/luuuc/sense" rel="noopener noreferrer"&gt;github.com/luuuc/sense&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>claude</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>I built AI tools that roast your startup ideas so your investors don't</title>
      <dc:creator>Luc B. Perussault-Diallo</dc:creator>
      <pubDate>Sun, 22 Feb 2026 09:50:46 +0000</pubDate>
      <link>https://dev.to/luuuc/i-built-ai-tools-that-roast-your-startup-ideas-so-your-investors-dont-54di</link>
      <guid>https://dev.to/luuuc/i-built-ai-tools-that-roast-your-startup-ideas-so-your-investors-dont-54di</guid>
      <description>&lt;p&gt;I got tired of pitching bad ideas to polite friends.&lt;/p&gt;

&lt;p&gt;You know the drill, you share your latest "revolutionary" concept, and everyone nods. "Sounds cool." "Interesting." "You should totally build that." Then six months later you realize nobody ever told you it was a terrible idea.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://star.tupcheck.me?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;StartupCheck&lt;/a&gt; - a set of free AI tools that deliver the painfully honest feedback founders rarely get.&lt;/p&gt;

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

&lt;p&gt;Five tools, each targeting a different founder blind spot:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idea Funeral&lt;/strong&gt;&lt;br&gt;
Submit your startup idea. Get an honest eulogy instead of polite encouragement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corporate Translator&lt;/strong&gt; &lt;br&gt;
Paste your corporate speak. Find out what it actually means. ("Right-sizing the organization" = layoffs start next week.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup Therapist&lt;/strong&gt; &lt;br&gt;
Share your founder optimism. Get the reality check your co-founder is too nice to give you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pitch Without Slides&lt;/strong&gt; &lt;br&gt;
Deliver your pitch in plain text. See if it holds up without pretty graphs and stock photos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build or Die&lt;/strong&gt; &lt;br&gt;
Describe your project scope. Find out if your "2-week MVP" is actually a 6-month odyssey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Every founder has blind spots. The best feedback I've ever received was the most uncomfortable. But most people won't give you that feedback unprompted, it's socially awkward, it risks the relationship, and honestly, it's easier to just nod.&lt;/p&gt;

&lt;p&gt;AI has no social incentive to spare your feelings. That's a feature, not a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Free to start, no signup required. Builder plan at 7 EUR/month for higher limits&lt;/li&gt;
&lt;li&gt;Built with AI that has zero reason to be polite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check it out: &lt;a href="https://star.tupcheck.me?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;star.tupcheck.me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love to hear your thoughts, and yes, you're welcome to roast StartupCheck using its own tools. Fair game.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>ai</category>
      <category>sideprojects</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
