A year of AI coding at team scale
A year ago I would have told you that Cursor was the right answer for a team’s AI-coding setup. Editor integration was the load-bearing affordance, the @-mention loop was how the work got done, and any tool that took you out of your editor was paying a tax on every keystroke. I had reasons to believe it; I’d watched how people worked, and the ones who used Cursor heavily were genuinely faster than the ones who hadn’t picked it up. I’d been arguing for more aggressive Cursor adoption, not less.
We migrated onto Claude Code about three months ago. Not because Cursor got worse (it’s still a fine product) or because Claude Code is flawless (it has its own rough edges). What changed was my view of which affordance actually carries a team rather than an individual.
The friction showed up in code review, repeatedly, in the same shape. Code that passed every check: tidy signatures, docstrings present, tests green, and still subtly off. A naming choice that didn’t match the rest of the codebase, a typing convention that differed from two other recent PRs. The information needed to get it right existed, written down in the standards doc. It just hadn’t been in the model’s context when the code was generated, because whoever was driving had @-mentioned a handful of files and the model generalised over those. Consistent with what it could see, inconsistent with the convention it couldn’t.
I missed the shape of it for a couple of months. My first fix was to drop the standards doc into a .cursorrules file. Then someone starting a new repo copied that file across. Then the next repo did the same. Within a couple of months several repos carried drifted versions of the same standards file, some subtly out of date, and a freshly spun-up repo had none at all. The standards weren’t enforced so much as copy-pasted, and copy-pasted things drift.
The model was never the problem. It could read whatever you put in its context window. The problem was that what went into the context window was a per-engineer, per-session decision, made under the time pressure of wanting to ship a PR. I’d been treating “what should the model know” as something each engineer answers ad hoc in their editor. At team scale that is the wrong abstraction. The team’s accumulated knowledge has to be in front of the model on every session, regardless of who’s at the keyboard or what they remember to attach.
Around then I started taking Claude Code’s plugin system more seriously. The part that caught me wasn’t the bigger context window (helpful, not decisive). It was the layering. A per-repo CLAUDE.md for things true of one codebase, a team plugin for things true across all of them, and a hook system that fires on session events. Standards live in the plugin, instances live in the per-repo file, and the rule for which layer something belongs in is simply whether it applies to one repo or to many.
Drafting what the plugin should contain, against the standards we already had, was its own lesson. Almost everything in .cursorrules belonged in the plugin; almost nothing belonged in a per-repo file. And sorting the existing content into layers turned up perhaps a third of it that was already wrong: stale language-version references, ticket conventions that contradicted each other, defaults left over from an infrastructure change that had already happened. I’d been carrying that drift in plain sight for months.
The reason to put all of this in a plugin rather than a shared document is the thing I’d underrated at the start. A document is tribal knowledge with better formatting; it helps the people who remember it exists and read the current version. A plugin is distributable: new capability lands as a change in one repo, ships on the next update, and every engineer has it without being told. That’s the difference between “the team is AI-native” and “one person worked out how to use Claude well”, and the second doesn’t survive that person being on leave, let alone leaving. If the programme reduces to one engineer’s habits, it isn’t a programme.
Migrating took roughly three weeks of part-time work between me and one other engineer, then another month for everyone to settle. The mechanical work was small; most people had a .cursorrules and a few muscle-memory habits to unlearn. The structural work was the interesting part.
One thing I didn’t expect was how much of the standards ended up living in skills rather than documents. A skill, in the plugin model, is a markdown file with a description that says when it should fire and a body that says what to do. Skills load when the intent matches, so a code-review skill fires whenever someone asks for a review, and it pulls in the coding-standards reference itself. Nobody has to remember to point at the standard; the standard finds the conversation. My first attempts were clumsy, the descriptions too narrow to fire reliably, but once I’d rewritten them the standards started enforcing themselves. Drift stopped. Review feedback got more consistent. People stopped asking what the convention for X was, because by the time the question would have come up the convention was already in the room.
The deeper rule, which I’d guessed at but not seen demonstrated, is that hooks and skills enforce standards and prompts don’t. If a rule has to be remembered to be applied, it will be forgotten. If it’s wired into the entry point (a session-start hook that scaffolds a missing CLAUDE.md, a pre-commit hook that formats, a skill that fires on review intent) it’s enforced. Every time I catch myself writing “remember to do X” in a doc, I’m really admitting the standard isn’t enforced, and the honest fix is to move it into a hook or a skill, or to accept it’ll be skipped half the time.
Code review got sharper when I split one reviewer into several. A single “review this PR” prompt gives you a generalist, and asking one pass to simultaneously check breaking changes, scope against the ticket, test gaps, silent failures, type design, comment accuracy and regressions means each check gets a sliver of attention. The findings come back vague and people learn to ignore them. Splitting it into a handful of narrow-lens agents, each with one job and the relevant standard loaded, produces findings specific enough to act on. They run in parallel and an orchestrating skill collates the output. The cost is context budget; the benefit is a finding that cites the exact standard and line rather than a vibe.
That citation is the actual adoption lever. People stop arguing with the reviewer when it points at their own team’s documented standard. Without the citation every finding is contestable; with it the conversation just moves on. We’d chased that consistency through review checklists for a long time with the diminishing returns you’d expect, and the plugin shape got most of the way there in a quarter.
The step up from there, and the piece I’m most interested in now, is routines. A skill still has to be invoked; someone has to ask. A routine runs on a schedule and invokes itself. It closes the loop between “we have a capability” and “the capability actually runs”, which in my experience is where most good tooling quietly dies, on the day everyone’s too busy to remember to use it.
The clearest case study is a PR-review routine. Rather than waiting for someone to ask for a review, it wakes on a cadence, finds the open PRs that need eyes, runs the same review pass the skill would, and posts its findings in a neutral voice. It’s idempotent, so it’s safe to run often.1 The one rule it never breaks is that it defers approval and merge to a human. Automate the invocation, not the decision. That line is the whole game: the routine removes the friction of remembering to review without touching the judgment of whether to merge. It maps onto a spectrum I keep coming back to, AI-assisted where the human drives, AI-driven where the human still gates. A review routine is AI-driven with a human gate, and the gate is the load-bearing part.
A few things broke along the way, in rough order of how much time they cost.
The first was choosing between the GitHub CLI and a GitHub MCP for repo operations. The MCP looked principled: typed tool calls, no shelling out. In practice every engineer already had gh installed and authenticated with the right org context, whereas the MCP wanted its own auth and broke whenever the OS keychain rotated. We moved everything to gh. Less typed, and it actually works. Prefer the thing already on the machine over the thing that’s architecturally cleaner.
The second was release automation. A wrong commit-message prefix silently miscategorises a release, and the breaking-change footer matters as much as the prefix. The fix was to spell the format out in one place and validate it with a commit hook before push, rather than hoping everyone remembered the convention. Before that, every release had a quiet “why didn’t this version bump” conversation.
The third was muscle memory, which I mention because it’s the one I’d warn the next team about. People who’d built habits around Cursor’s @-mention loop kept reaching for it in Claude Code and missing it. The honest answer was patience. The new model is better at team scale and worse-feeling for a few weeks, because the affordance you’re used to has gone, and after three or four weeks nobody noticed any more.
If I were doing it again I’d ship the plugin scaffold before migrating any repos. We did it the other way, migrating repos while building the plugin in parallel, which meant the first month had no team-wide standards loaded and consistency was briefly worse than under Cursor. Plugin first would have made the migration a strict improvement from day one. I’d also be more ruthless about what goes in a per-repo CLAUDE.md; the first ones ran to a couple of hundred lines, most of which belonged in a skill, and the rule “only what the plugin cannot know” arrived too late.
One prerequisite is worth saying out loud, because it decides whether any of this helps: the artefact flow has to already exist. AI spec-drafting only helps a team that produces specs. Ticket generation only helps if tickets are more than a Slack message. The plugin makes each link in the chain (meeting notes, spec, ticket, branch, PR, release, docs) cheaper to produce while keeping acceptance in human hands, but it can’t create the chain. Half the work of going AI-native on a team that wasn’t already doc-centric is just building the artefact flow first, and that part has nothing to do with AI.
A year ago I’d have said AI coding at team scale was about which model was best, or which editor felt smoothest. What it actually comes down to, looking back, is whether the structure around the model is something the team controls, layers, and enforces. The model is the cheapest thing in the system. The structure is where the work is.
Footnotes
-
Idempotent here means it records what it reviewed against which commit SHA and skips anything it has already seen at that SHA. Without that, a routine running hourly re-reviews the same untouched PR every hour and becomes noise the team mutes, which is a worse failure than not running it at all. The marker is cheap and it’s the difference between a routine people trust and one they turn off. ↩