[{"data":1,"prerenderedAt":362},["ShallowReactive",2],{"case-study:engineering-standards-and-tooling":3},{"id":4,"title":5,"body":6,"description":332,"draft":333,"extension":334,"industry":335,"meta":336,"metrics":337,"navigation":333,"order":134,"path":347,"role":348,"seo":349,"stack":350,"stem":354,"tags":355,"year":360,"__hash__":361},"caseStudies\u002Fcase-studies\u002Fengineering-standards-and-tooling.md","Making architecture enforce itself: standards, tests, and AI-augmented delivery",{"type":7,"value":8,"toc":323},"minimark",[9,14,18,22,30,33,37,40,200,204,227,231,243,254,258,289,297,304,308,316,319],[10,11,13],"h2",{"id":12},"in-one-line","In one line",[15,16,17],"p",{},"I built the guardrails - custom static-analysis rules, layer enforcement, a\n1,200+ test suite, a one-command environment, and an AI workflow that encodes the\narchitecture - so that a five-developer team stays on a clean design they'd never\nworked in, without a senior policing every pull request.",[10,19,21],{"id":20},"the-problem","The problem",[15,23,24,25,29],{},"Designing a clean architecture is the easy half. The hard half is keeping a\nfive-developer team ",[26,27,28],"em",{},"on"," it - on a stack (Mezzio, Doctrine, Nuxt 4, a generated\nSDK) none of them had worked in before, while migrating features out of four\ntangled legacy systems whose every shortcut is right there to copy.",[15,31,32],{},"Without a system around the code, three things go wrong fast: decisions lose\ntheir memory, \"quality\" becomes whatever a reviewer happens to notice, and the\narchitecture erodes one shortcut at a time. So I built the guardrails before the\nwork scaled out - to make the right way the path of least resistance, not a thing\nsomeone has to police.",[10,34,36],{"id":35},"standards-that-enforce-themselves","Standards that enforce themselves",[15,38,39],{},"I refused to rely on humans remembering rules. The rules are executable.",[41,42,43,59,93,177,186],"ul",{},[44,45,46,50,51,54,55,58],"li",{},[47,48,49],"strong",{},"Static analysis at the gate."," PHPStan (level 5) and Psalm run in CI and in\npre-commit hooks. ",[47,52,53],{},"ECS"," and ",[47,56,57],{},"Rector"," handle style and PHP modernization\nautomatically, so nobody argues about formatting in review.",[44,60,61,64,65,68,69,72,73],{},[47,62,63],{},"Custom architecture rules."," Beyond the off-the-shelf checks, I wrote\n",[47,66,67],{},"custom PHPStan and Rector rules"," that enforce ",[26,70,71],{},"this platform's"," conventions",[41,74,75],{},[44,76,77,78,82,83,86,87,92],{},"handlers must be complete, classes ",[79,80,81],"code",{},"final"," by default, endpoints must carry\nthe API annotations (including the ",[79,84,85],{},"operationId"," the\n",[88,89,91],"a",{"href":90},"\u002Fcase-studies\u002Feliminating-api-schema-drift","SDK pipeline"," depends on). The\narchitecture polices itself instead of depending on me catching violations in\nreview.",[44,94,95,98,99,102,103],{},[47,96,97],{},"Layer boundaries, enforced."," ",[47,100,101],{},"Deptrac"," asserts the dependency direction so\na Repository can never reach up into a Handler. The modular structure I designed\ncan't quietly collapse:",[104,105,110],"pre",{"className":106,"code":107,"language":108,"meta":109,"style":109},"language-yaml shiki shiki-themes one-dark-pro","# Deptrac: dependencies may only point downward through the layers\nruleset:\n  Handler: [Service]\n  Service: [Repository]\n  Repository: ~ # depends on nothing above it\n","yaml","",[79,111,112,121,132,148,161],{"__ignoreMap":109},[113,114,117],"span",{"class":115,"line":116},"line",1,[113,118,120],{"class":119},"sV9Aq","# Deptrac: dependencies may only point downward through the layers\n",[113,122,124,128],{"class":115,"line":123},2,[113,125,127],{"class":126},"sVyAn","ruleset",[113,129,131],{"class":130},"sn6KH",":\n",[113,133,135,138,141,145],{"class":115,"line":134},3,[113,136,137],{"class":126},"  Handler",[113,139,140],{"class":130},": [",[113,142,144],{"class":143},"subq3","Service",[113,146,147],{"class":130},"]\n",[113,149,151,154,156,159],{"class":115,"line":150},4,[113,152,153],{"class":126},"  Service",[113,155,140],{"class":130},[113,157,158],{"class":143},"Repository",[113,160,147],{"class":130},[113,162,164,167,170,174],{"class":115,"line":163},5,[113,165,166],{"class":126},"  Repository",[113,168,169],{"class":130},": ",[113,171,173],{"class":172},"sVC51","~",[113,175,176],{"class":119}," # depends on nothing above it\n",[44,178,179,98,182,185],{},[47,180,181],{},"A linted API contract.",[47,183,184],{},"Spectral"," validates the OpenAPI spec against a\ncustom ruleset before it can generate a client.",[44,187,188,191,192,195,196,199],{},[47,189,190],{},"A quality gate on top."," Conventional-commit enforcement, a pre-push test\nrun, and ",[47,193,194],{},"SonarQube"," ingesting the PHPStan\u002FPsalm reports - so every change\nclears the same bar regardless of who reviewed it. In practice, ",[47,197,198],{},"100% of\ncommits pass through these gates","; the build is the source of truth for \"is\nthis up to standard?\", not a person's memory.",[10,201,203],{"id":202},"a-test-suite-the-team-can-trust","A test suite the team can trust",[15,205,206,207,210,211,214,215,218,219,222,223,226],{},"QA on the project is manual, so the automated suite ",[26,208,209],{},"is"," the regression safety\nnet - it has to catch what a person otherwise would. On the backend, tests are\nwritten with ",[47,212,213],{},"Pest",": unit tests for services, handlers, input filters and DTOs;\nintegration smoke tests for the DI container, Doctrine metadata, and route\nwiring; and ",[47,216,217],{},"ACL tests covering every non-public route",", so a permissions\nregression fails the build rather than shipping. The frontend is covered by\n",[47,220,221],{},"Vitest"," unit tests (stores, composables, components) and ",[47,224,225],{},"Playwright","\nend-to-end tests for the workflows that matter most. Well over a thousand backend\ntest cases run as part of normal delivery, with a unit\u002Fintegration split tuned\nfor fast feedback.",[10,228,230],{"id":229},"one-command-to-a-running-stack","One command to a running stack",[15,232,233,234,237,238,242],{},"The old onboarding ritual was a manual, multi-tool local setup that taught a new\ndeveloper four legacy architectures before they could run anything. I replaced it\nwith a ",[47,235,236],{},"DDEV"," environment that brings the whole stack up - nginx-fpm, Redis,\nSwagger UI, the right PHP extensions, Xdebug - in essentially one command, with\nautomated certificate handling for the corporate proxy environment. (More on the\nenvironment shift in ",[88,239,241],{"href":240},"\u002Fblog\u002Ffrom-xampp-to-ddev","From XAMPP to DDEV",".)",[15,244,245,246,249,250,253],{},"I also built a ",[47,247,248],{},"scaffolding CLI"," that generates a new module's boilerplate to\nthe project's conventions - entity, repository, service, DTO, handler, and test\nstubs - so a new domain starts ",[26,251,252],{},"correct"," instead of copy-pasted from whatever was\nnearest. The fastest way to keep code consistent is to make the consistent\nversion the easiest one to produce.",[10,255,257],{"id":256},"teaching-the-machine-the-conventions","Teaching the machine the conventions",[15,259,260,261,264,265,268,269,272,273,272,276,272,279,268,282,272,285,288],{},"The most forward-looking piece: I encoded the architecture into an\n",[47,262,263],{},"AI-augmented migration workflow",". Repo-local agent skills - ",[79,266,267],{},"migrate-feature",",\n",[79,270,271],{},"api-endpoint-scaffold",", ",[79,274,275],{},"create-service",[79,277,278],{},"register-route",[79,280,281],{},"quality-check",[79,283,284],{},"code-review",[79,286,287],{},"publish"," - carry the project's own rules and drive a fixed\nseven-phase flow:",[104,290,295],{"className":291,"code":293,"language":294,"meta":109},[292],"language-text","1. Analyze legacy code → migration doc\n2. Backend: entity, repository, service, DTO, handlers, tests\n3. Regenerate the typed SDK from the OpenAPI spec\n4. Middleware: register proxy routes\n5. Frontend: Pinia stores, Nuxt pages, Vuetify components, tests\n6. Verify: cross-layer testing + legacy-parity check\n7. Publish: quality checks, PR, ticket transition\n","text",[79,296,293],{"__ignoreMap":109},[15,298,299,300,303],{},"The point isn't \"we use AI.\" It's that the conventions I designed are captured\n",[26,301,302],{},"where the work happens",", so an assisted migration comes out shaped like the rest\nof the platform instead of freestyling - and a developer reviews each phase\nbefore the next begins. In planning, that workflow was the difference between a\nmulti-year manual estimate and a timeline roughly half as long.",[10,305,307],{"id":306},"what-changed","What changed",[15,309,310,311,315],{},"The architecture is now documented in ADRs (why that habit compounds is in\n",[88,312,314],{"href":313},"\u002Fblog\u002Farchitecture-decision-records","Architecture Decision Records","), the\nstandards are automated rather than tribal, and a five-developer team ships\nconfidently on a stack they'd never touched - because the system catches the\nmistakes that used to need a senior reviewer to catch.",[15,317,318],{},"The throughline across everything I build: I'd rather spend a day on the rule or\nthe tool that prevents a whole class of problems than a week cleaning up after it.",[320,321,322],"style",{},"html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}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":109,"searchDepth":123,"depth":123,"links":324},[325,326,327,328,329,330,331],{"id":12,"depth":123,"text":13},{"id":20,"depth":123,"text":21},{"id":35,"depth":123,"text":36},{"id":202,"depth":123,"text":203},{"id":229,"depth":123,"text":230},{"id":256,"depth":123,"text":257},{"id":306,"depth":123,"text":307},"A greenfield platform is only as good as the system around it. I built the standards, quality gates, and tooling that keep a five-developer team on the architecture on a stack none of them had used - including custom static-analysis rules and an AI workflow that encodes the architecture itself.",true,"md","Payments \u002F Fintech",{},[338,341,344],{"value":339,"label":340},"1,200+","backend test cases (Pest), plus frontend unit & Playwright e2e",{"value":342,"label":343},"Custom","static-analysis rules that enforce the architecture",{"value":345,"label":346},"100%","of commits run through automated quality gates","\u002Fcase-studies\u002Fengineering-standards-and-tooling","Senior Full-Stack Developer",{"title":5,"description":332},[351,352,57,101,53,184,213,221,225,194,236,353],"PHPStan","Psalm","GitHub Actions","case-studies\u002Fengineering-standards-and-tooling",[356,357,358,359],"Engineering Culture","Developer Experience","Testing","AI-Augmented Dev","2024–present","7sTknzuRSUvrUZoVZJK7n5uJuOPsG6jhPAWRM6v1r80",1781646389931]