[{"data":1,"prerenderedAt":379},["ShallowReactive",2],{"case-study:ai-development-infrastructure":3},{"id":4,"title":5,"body":6,"description":341,"draft":342,"extension":343,"industry":344,"meta":345,"metrics":346,"navigation":356,"order":279,"path":357,"role":358,"seo":359,"stack":360,"stem":371,"tags":372,"year":377,"__hash__":378},"caseStudies\u002Fcase-studies\u002Fai-development-infrastructure.md","Making AI safe for a team: skills, MCP servers, and hard gates",{"type":7,"value":8,"toc":331},"minimark",[9,14,18,22,25,28,35,50,53,57,64,90,106,113,117,124,127,136,140,148,184,187,194,198,201,216,223,231,235,246,249,288,299,303,309,315,321,327],[10,11,13],"h2",{"id":12},"in-one-line","In one line",[15,16,17],"p",{},"I built the tooling that makes AI assistance safe and repeatable for a whole\nteam - executable skills, four MCP servers, and hooks that block the dangerous\ncommand rather than trusting anyone to avoid it - and packaged it as a plugin\nanyone installs with two commands.",[10,19,21],{"id":20},"the-problem","The problem",[15,23,24],{},"The migration meant moving business logic out of four legacy codebases into a\nnew stack with strict conventions. That work is a bad fit for a human on a\nTuesday afternoon: the logic for one feature is often split across a SOAP\nservice, a JSON-RPC webservice, a BFF layer and an AngularJS UI, and the\nconventions on the receiving end are unforgiving.",[15,26,27],{},"It is a good fit for an AI assistant, which can hold four legacy repositories in\nview at once and apply a convention consistently across hundreds of files. But\nthat only holds if two problems are solved first, and neither is solved by\nprompting.",[15,29,30,34],{},[31,32,33],"strong",{},"The output has to be trustworthy."," An assistant that produces plausible code\nwhich quietly violates the architecture is worse than no assistant, because the\nviolation arrives faster than review can catch it.",[15,36,37,40,41,45,46,49],{},[31,38,39],{},"The blast radius has to be bounded."," The moment you give a tool the ability to\nrun commands, you have given it the ability to run ",[42,43,44],"code",{},"git push --force",", to drop a\ntable, or to \"fix\" a failing test by editing ",[42,47,48],{},"vendor\u002F",". One careless session can\nundo a week.",[15,51,52],{},"So I built the infrastructure before scaling the AI work out to the team.",[10,54,56],{"id":55},"guardrails-first","Guardrails first",[15,58,59,60,63],{},"The hooks came before anything else, because everything else is only safe once\nthey exist. A ",[42,61,62],{},"PreToolUse"," guard runs in every session, and the rules are\ncommand-pattern gated so each one fires only when relevant - a guard that\ninterrupts constantly gets disabled, which defeats the point.",[15,65,66,69,70,73,74,77,78,81,82,85,86,89],{},[42,67,68],{},"guard-bash.sh"," blocks nine classes of command: force-pushes and hard resets,\nblanket ",[42,71,72],{},"SKIP_PRECOMMIT",", committing pnpm-link artifacts, manual pushes (the\nsanctioned path is the ",[42,75,76],{},"publish"," skill), recursive style fixes across ",[42,79,80],{},"src\u002F",",\ndatabase-modifying SQL, and - my favourite - any ",[42,83,84],{},"SELECT"," without a ",[42,87,88],{},"LIMIT",".",[15,91,92,95,96,98,99,101,102,105],{},[42,93,94],{},"guard-files.sh"," blocks edits to ",[42,97,48],{},", and blocks adding ",[42,100,48],{}," to\n",[42,103,104],{},".gitignore",", which is the move an assistant reaches for when it wants the\nfirst block to go away.",[15,107,108,109,112],{},"The important property is that these are enforced ",[31,110,111],{},"outside the model",". They are\nnot instructions the assistant is asked to respect; they are a process that\nreturns a non-zero exit code. No amount of confident reasoning gets around them.",[10,114,116],{"id":115},"skills-the-workflow-as-an-executable-artifact","Skills: the workflow as an executable artifact",[15,118,119,120,123],{},"A skill is a workflow written down in a form the assistant executes rather than\nimprovises. The one that mattered most is ",[42,121,122],{},"migrate-feature",": seven phases and a\nroughly thirty-item checklist spanning three repositories, from legacy analysis\nthrough backend implementation, SDK regeneration, middleware routing, frontend\nwork, verification and publication.",[15,125,126],{},"Before it existed, every migration was a fresh negotiation about what \"done\"\nmeant. After it, the fifth migration runs like the first.",[15,128,129,130,135],{},"Thirteen more cover the rest of the cycle - endpoint and service scaffolding,\nPHP and Vue review rules, per-repo quality checks, the publish flow, and the JIRA\nlifecycle. They encode the same conventions the\n",[131,132,134],"a",{"href":133},"\u002Fcase-studies\u002Fengineering-standards-and-tooling","static-analysis rules"," enforce,\nwhich means the assistant is aiming at the target the CI is measuring.",[10,137,139],{"id":138},"mcp-servers-giving-the-assistant-real-access-narrowly","MCP servers: giving the assistant real access, narrowly",[15,141,142,143,147],{},"Four Model Context Protocol servers, written from scratch in Node, exposing 43\ntools. The design principle throughout was to expose the ",[144,145,146],"em",{},"specific"," capability\nrather than a general one, because a narrow tool cannot be misused the way a\nshell can.",[149,150,151,160,168,176],"ul",{},[152,153,154,159],"li",{},[31,155,156],{},[42,157,158],{},"quality"," (15 tools) - PHPStan, Psalm, Deptrac, ECS, test runs,\nchanged files, commit, push, PR create\u002Fedit\u002Fdiff\u002Fcomment\u002Flist, JIRA transition.",[152,161,162,167],{},[31,163,164],{},[42,165,166],{},"workflow"," (16 tools) - migration checklists, phased context\nloading, cross-repository tracking, document validators.",[152,169,170,175],{},[31,171,172],{},[42,173,174],{},"database"," (6 tools) - schema exploration: connections, tables,\ndescribe, foreign keys, indexes, and a query tool that is read-only with an\nautomatic row limit.",[152,177,178,183],{},[31,179,180],{},[42,181,182],{},"logs"," (6 tools) - remote API and webserver logs over SSH: list,\ntail, grep, since, context, errors.",[15,185,186],{},"The database server is the clearest illustration. The assistant genuinely needs\nto inspect schemas - guessing at column names produces confidently wrong code.\nBut it does not need write access, and it does not need to pull a million rows\ninto context. So the tool grants exactly the useful half of the capability, and\nthe dangerous half is not reachable.",[15,188,189,190,193],{},"The workflow server is the unusual one, because it holds ",[31,191,192],{},"gates",". A migration\ncannot be marked publishable until its checklist items are genuinely satisfied.\nThe assistant cannot skip a step by asserting it was done, because the gate is\nstate held outside the conversation.",[10,195,197],{"id":196},"the-loop-that-makes-it-work","The loop that makes it work",[15,199,200],{},"The pieces compose into something none of them achieve alone:",[202,203,204,207,210,213],"ol",{},[152,205,206],{},"The assistant writes code, aiming at conventions encoded in a skill.",[152,208,209],{},"The pre-commit hook runs the same PHPStan, Psalm and Deptrac checks a human\ncommit faces.",[152,211,212],{},"A check fails. The assistant reads the actual error, fixes it, and retries.",[152,214,215],{},"Two or three iterations later, it converges.",[15,217,218,219,222],{},"This is the whole thesis in one loop. The assistant is not trusted to be correct;\nit is placed in a system where being incorrect is ",[144,220,221],{},"detected mechanically and\nimmediately",", and where the feedback is specific enough to act on. The quality\ngates I built for human developers turned out to be exactly the infrastructure\nthat makes machine output trustworthy - the same rules, doing double duty.",[15,224,225,226,230],{},"The ",[131,227,229],{"href":228},"\u002Fcase-studies\u002Feliminating-api-schema-drift","generated SDK pipeline"," closes\nthe same loop across the language boundary: a PHP field change becomes a\nTypeScript compile error, so an assistant working on the frontend cannot drift\nfrom what the backend actually returns.",[10,232,234],{"id":233},"from-personal-setup-to-team-platform","From personal setup to team platform",[15,236,237,238,241,242,245],{},"For a while this was per-repository configuration, which meant it was really just\nmy setup. Claude Code loads configuration from your user config and the ",[144,239,240],{},"one","\nproject you launched in - repositories attached as extra working directories get\nfile access but not their ",[42,243,244],{},".claude\u002F"," config. During full-stack work, where a\nsingle ticket touches three repositories, a skill defined in the API repo was\nsimply invisible from the frontend.",[15,247,248],{},"I resolved it by packaging everything - skills, hooks, MCP servers - as a\nuser-level plugin with its own on-premise marketplace:",[250,251,256],"pre",{"className":252,"code":253,"language":254,"meta":255,"style":255},"language-bash shiki shiki-themes one-dark-pro","\u002Fplugin marketplace add git@github.example.com:your-org\u002Fdev-suite.git\n\u002Fplugin install dev-suite@dev-suite\n","bash","",[42,257,258,277],{"__ignoreMap":255},[259,260,263,267,271,274],"span",{"class":261,"line":262},"line",1,[259,264,266],{"class":265},"sVbv2","\u002Fplugin",[259,268,270],{"class":269},"subq3"," marketplace",[259,272,273],{"class":269}," add",[259,275,276],{"class":269}," git@github.example.com:your-org\u002Fdev-suite.git\n",[259,278,280,282,285],{"class":261,"line":279},2,[259,281,266],{"class":265},[259,283,284],{"class":269}," install",[259,286,287],{"class":269}," dev-suite@dev-suite\n",[15,289,290,291,294,295,298],{},"Two commands, works in every repository, versioned like any other dependency.\nThat packaging step is what turned it from ",[144,292,293],{},"my tooling"," into ",[144,296,297],{},"the team's\nplatform"," - the difference between a colleague admiring a setup and a colleague\nrunning it.",[10,300,302],{"id":301},"what-i-would-tell-someone-starting-this","What I would tell someone starting this",[15,304,305,308],{},[31,306,307],{},"Build the guardrails first."," They are what let you say yes to giving the whole\nteam these tools, rather than keeping them to the one person trusted to be\ncareful.",[15,310,311,314],{},[31,312,313],{},"Expose narrow tools, not broad ones."," Read-only with a row limit beats database\naccess plus a warning in the prompt, every time.",[15,316,317,320],{},[31,318,319],{},"Put the gates outside the model."," Anything enforced only by instruction is\nenforced only by luck.",[15,322,323,326],{},[31,324,325],{},"Your existing quality gates are the AI infrastructure."," The most useful thing I\nhad already built was the pre-commit toolchain. It is what makes the loop\nconverge - without it there is no signal, and the assistant is just producing\nconfident text.",[328,329,330],"style",{},"html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":255,"searchDepth":279,"depth":279,"links":332},[333,334,335,336,337,338,339,340],{"id":12,"depth":279,"text":13},{"id":20,"depth":279,"text":21},{"id":55,"depth":279,"text":56},{"id":115,"depth":279,"text":116},{"id":138,"depth":279,"text":139},{"id":196,"depth":279,"text":197},{"id":233,"depth":279,"text":234},{"id":301,"depth":279,"text":302},"AI assistance that is fast for one careful developer is a liability for a team of five. I built the infrastructure that makes it the other way round - executable workflow skills, four MCP servers, and guardrail hooks that block the destructive command before it runs - packaged as an installable plugin.",false,"md","Payments \u002F Fintech",{},[347,350,353],{"value":348,"label":349},"43","tools exposed across 4 purpose-built MCP servers",{"value":351,"label":352},"14","executable workflow skills, installable in any repo",{"value":354,"label":355},"9","guardrail rules that block destructive commands before they run",true,"\u002Fcase-studies\u002Fai-development-infrastructure","Senior Full-Stack Developer",{"title":5,"description":341},[361,362,363,364,365,366,367,368,369,370],"Claude Code","Model Context Protocol","Node.js","Bash","PHPStan","Psalm","Deptrac","Doctrine DBAL","JIRA","GitHub","case-studies\u002Fai-development-infrastructure",[373,374,375,376],"AI-Augmented Dev","Developer Experience","Tooling","MCP","2026","z4dobBfB37xf8w_cV8x1xCEhSVNI0_VryqFLR1WwThI",1787789108518]