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.
Half of that claim I'll concede without a fight. 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.
The half that's true: writing Ruby is solved
Start with the part that holds up, because it really does.
A model that has seen ten thousand Rails apps knows where the model lives, where the job goes, what a concern does, what has_many 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.
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.
I'm here to point out it answers a question nobody dangerous is asking.
The half that isn't: navigating Ruby at scale
"Can AI write Ruby" is settled. The question that ships broken deploys is different: can AI navigate Ruby? What breaks if this model changes, who depends on it, where the blast radius ends.
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.
So I ran the structural question on all thirteen repos. Same task each time: take the hub model, the Inbox, the MergeRequest, the Spree::Order, 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 file:line.
Now look at the shape of the board. Baseline recall, then with the map:
→ Chatwoot, Inbox → 0.29 → 0.97
→ Mastodon, Status → 0.28 → 0.83
→ GitLab, MergeRequest → 0.26 → 0.67
→ Discourse, Upload → 0.35 → 0.75
→ Solidus, Spree::Order → 0.40 → 0.68
→ Forem, Article → 0.35 → 0.63
→ Rails, ActiveRecord::Relation → 0.67 → 0.92
Then the gap collapses:
→ Lobsters, Story → 0.68 → 0.79
→ Redmine, Issue → 0.67 → 0.78
→ RubyLLM, provider/Chat → 0.76 → 0.80
→ raix → 0.60 → 0.73
→ llm.rb, provider → 0.48 → 0.48
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 app/models 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.
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.
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.
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.
Two things on that board I didn't expect
The smallest model got the second-biggest lift. 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.
The most famous repo was one of the hardest. 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 today, and you can't tell which you're getting.
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.
The exit you're reaching for, and why it's closed
Everyone reading a piece like this quietly exempts themselves. My code is clean. My app is well factored. I'd know if something hid. I'm in the bottom group.
Maybe you are. Today. The line isn't fixed.
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.
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.
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.
You can't reason your way to which half you're on. Test it.
It takes a few minutes on your own code.
Pick the model in your own app you'd schedule a careful afternoon for. Ask your agent cold, "before I change how this model is torn down, find every place that depends on it." Watch it grep and guess. Count what it found.
Then give it the map.
→ curl -fsSL https://luuuc.github.io/sense/install.sh | sh
→ sense scan in the root of the app you know best
→ sense setup to connect your agent
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.
The whole board, the methodology, the raw data for all thirteen.
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.
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.
Top comments (1)
The Ruby point makes sense if "AI-friendly" means readable domain code and fast iteration, not just model ecosystem size. The best stack for agents is often the one where the boundaries are easiest to explain.