In one line
I built the tooling that makes AI assistance safe and repeatable for a whole team - executable skills, four MCP servers, and hooks that block the dangerous command rather than trusting anyone to avoid it - and packaged it as a plugin anyone installs with two commands.
The problem
The migration meant moving business logic out of four legacy codebases into a new stack with strict conventions. That work is a bad fit for a human on a Tuesday afternoon: the logic for one feature is often split across a SOAP service, a JSON-RPC webservice, a BFF layer and an AngularJS UI, and the conventions on the receiving end are unforgiving.
It is a good fit for an AI assistant, which can hold four legacy repositories in view at once and apply a convention consistently across hundreds of files. But that only holds if two problems are solved first, and neither is solved by prompting.
The output has to be trustworthy. An assistant that produces plausible code which quietly violates the architecture is worse than no assistant, because the violation arrives faster than review can catch it.
The blast radius has to be bounded. The moment you give a tool the ability to
run commands, you have given it the ability to run git push --force, to drop a
table, or to "fix" a failing test by editing vendor/. One careless session can
undo a week.
So I built the infrastructure before scaling the AI work out to the team.
Guardrails first
The hooks came before anything else, because everything else is only safe once
they exist. A PreToolUse guard runs in every session, and the rules are
command-pattern gated so each one fires only when relevant - a guard that
interrupts constantly gets disabled, which defeats the point.
guard-bash.sh blocks nine classes of command: force-pushes and hard resets,
blanket SKIP_PRECOMMIT, committing pnpm-link artifacts, manual pushes (the
sanctioned path is the publish skill), recursive style fixes across src/,
database-modifying SQL, and - my favourite - any SELECT without a LIMIT.
guard-files.sh blocks edits to vendor/, and blocks adding vendor/ to
.gitignore, which is the move an assistant reaches for when it wants the
first block to go away.
The important property is that these are enforced outside the model. They are not instructions the assistant is asked to respect; they are a process that returns a non-zero exit code. No amount of confident reasoning gets around them.
Skills: the workflow as an executable artifact
A skill is a workflow written down in a form the assistant executes rather than
improvises. The one that mattered most is migrate-feature: seven phases and a
roughly thirty-item checklist spanning three repositories, from legacy analysis
through backend implementation, SDK regeneration, middleware routing, frontend
work, verification and publication.
Before it existed, every migration was a fresh negotiation about what "done" meant. After it, the fifth migration runs like the first.
Thirteen more cover the rest of the cycle - endpoint and service scaffolding, PHP and Vue review rules, per-repo quality checks, the publish flow, and the JIRA lifecycle. They encode the same conventions the static-analysis rules enforce, which means the assistant is aiming at the target the CI is measuring.
MCP servers: giving the assistant real access, narrowly
Four Model Context Protocol servers, written from scratch in Node, exposing 43 tools. The design principle throughout was to expose the specific capability rather than a general one, because a narrow tool cannot be misused the way a shell can.
quality(15 tools) - PHPStan, Psalm, Deptrac, ECS, test runs, changed files, commit, push, PR create/edit/diff/comment/list, JIRA transition.workflow(16 tools) - migration checklists, phased context loading, cross-repository tracking, document validators.database(6 tools) - schema exploration: connections, tables, describe, foreign keys, indexes, and a query tool that is read-only with an automatic row limit.logs(6 tools) - remote API and webserver logs over SSH: list, tail, grep, since, context, errors.
The database server is the clearest illustration. The assistant genuinely needs to inspect schemas - guessing at column names produces confidently wrong code. But it does not need write access, and it does not need to pull a million rows into context. So the tool grants exactly the useful half of the capability, and the dangerous half is not reachable.
The workflow server is the unusual one, because it holds gates. A migration cannot be marked publishable until its checklist items are genuinely satisfied. The assistant cannot skip a step by asserting it was done, because the gate is state held outside the conversation.
The loop that makes it work
The pieces compose into something none of them achieve alone:
- The assistant writes code, aiming at conventions encoded in a skill.
- The pre-commit hook runs the same PHPStan, Psalm and Deptrac checks a human commit faces.
- A check fails. The assistant reads the actual error, fixes it, and retries.
- Two or three iterations later, it converges.
This is the whole thesis in one loop. The assistant is not trusted to be correct; it is placed in a system where being incorrect is detected mechanically and immediately, and where the feedback is specific enough to act on. The quality gates I built for human developers turned out to be exactly the infrastructure that makes machine output trustworthy - the same rules, doing double duty.
The generated SDK pipeline closes the same loop across the language boundary: a PHP field change becomes a TypeScript compile error, so an assistant working on the frontend cannot drift from what the backend actually returns.
From personal setup to team platform
For a while this was per-repository configuration, which meant it was really just
my setup. Claude Code loads configuration from your user config and the one
project you launched in - repositories attached as extra working directories get
file access but not their .claude/ config. During full-stack work, where a
single ticket touches three repositories, a skill defined in the API repo was
simply invisible from the frontend.
I resolved it by packaging everything - skills, hooks, MCP servers - as a user-level plugin with its own on-premise marketplace:
/plugin marketplace add [email protected]:your-org/dev-suite.git
/plugin install dev-suite@dev-suite
Two commands, works in every repository, versioned like any other dependency. That packaging step is what turned it from my tooling into the team's platform - the difference between a colleague admiring a setup and a colleague running it.
What I would tell someone starting this
Build the guardrails first. They are what let you say yes to giving the whole team these tools, rather than keeping them to the one person trusted to be careful.
Expose narrow tools, not broad ones. Read-only with a row limit beats database access plus a warning in the prompt, every time.
Put the gates outside the model. Anything enforced only by instruction is enforced only by luck.
Your existing quality gates are the AI infrastructure. The most useful thing I had already built was the pre-commit toolchain. It is what makes the loop converge - without it there is no signal, and the assistant is just producing confident text.