Stop Writing Better Prompts. Fix Your Codebase.
Why monorepos and Domain-Driven Design are the missing prerequisites for AI coding — and how six Claude Skills cut feature delivery from a day to two hours.
What this covers:
Why prompts and CLAUDE.md files don’t compound, and what does
The two architectural prerequisites for AI leverage that nobody talks about - monorepo and clean layered architecture
The six Claude Skills I built that turn a JIRA ticket into a merged-ready PR
Copy-pasteable snippets and full skill samples you can drop into your own codebase today
What actually changed at Fortius, with concrete numbers
TL;DR: AI-assisted coding tools are bottlenecked by what your codebase lets the agent see and how legible that codebase is. After migrating Fortius to a monorepo and applying strict Domain-Driven Design layering on both backend and frontend, I built six composable Claude Skills - small markdown runbooks - that encode our delivery workflow end-to-end. The orchestration skill takes a JIRA key, implements the change across all layers, runs the full test matrix, self-reviews against a 30-point checklist, and opens a PR. Cycle time on full-stack features dropped from a day to a couple of hours. The lesson: skills built on a tangled codebase are skills built on sand. Architecture is the prerequisite.
The problem
I’ve spent fifteen years building software for hedge funds and investment banks - Elliott Management, LMR Partners, Credit Suisse, Daiwa Capital Markets. Environments where a wrong number is a phone call from the Portfolio Manager and “the conventions exist for a reason” is the operating model. For the last eighteen months, alongside that, I’ve been Co-founder and CTO of Fortius Technology, a SaaS club management platform.
Every developer using Claude, Copilot, or Cursor on a real codebase hits the same wall. The model is brilliant in the abstract and oblivious to your conventions. It picks Entity Framework when your repo is hand-rolled Dapper. It writes class components when you’re functional-only. It forgets the multi-tenancy filter on every other query.
You can fight this with prompting. I did, for months. Longer system prompts, CLAUDE.md files, correcting the same five things on every PR. It works, but the leverage is poor - every conversation starts from zero, and the corrections don’t compound.
The thing that finally clicked: the conventions aren’t prompt engineering. They’re a runbook. And runbooks are what Skills are for.
But before I get to the skills, there are two structural decisions I made first that mattered more than any single skill in the library.
Prerequisite one: the monorepo
Fortius started the way most teams start - separate repositories for the backend API, the admin frontend, and the database. Clean boundaries, the textbook setup.
It was a disaster for working with Claude.
Every feature touches all three layers. So every feature meant three disconnected Claude sessions, each blind to the others. I was the human integration layer - copying API contracts from the backend session into the frontend session as context, then iterating in the frontend because the contract I’d pasted had drifted from what the backend session actually built. Three sessions, three sources of truth, all of them mine to reconcile.
I migrated everything to a single monorepo. Backend under apps/api, frontend under apps/admin-web, database under database/. The effect was immediate and larger than I expected. The agent could read the SQL migration, then the Dapper repository that queries it, then the C# display model the API returns, then the TypeScript interface on the frontend that consumes it - all in one context, without me explaining anything.
The agent can only reason about what it can see in a single context. If your codebase is fragmented across repositories, your agent’s reasoning is fragmented too - and you become the integration layer, by hand, on every change.
Prerequisite two: layered architecture the agent can navigate
Putting everything in one repo solves visibility. It doesn’t solve legibility. A monorepo full of tangled code is just a bigger surface for the agent to get lost in.
The second thing I did - and arguably the higher-leverage one - was apply Domain-Driven Design layering rigorously across both backend and frontend, with a parallel structure on each side.
On the backend, the codebase splits into clean layers: a pure domain model with no external dependencies, a display model that translates between domain and API with From{Entity}() factory methods, a repository layer that’s the only thing allowed to talk to the database, and a service layer that composes repositories and applies business rules.
On the frontend, I mirrored the same shape: a models/ folder with TypeScript interfaces matching the backend’s display models, a requests/ folder for mutation DTOs, a services/ folder as the only layer that talks to the backend, and components that consume services through a Provider + Hook pattern.
The two sides are deliberately symmetric. A Member on the backend has a Member.ts on the frontend. The agent doesn’t have to infer the mapping - it navigates a parallel structure where every file has exactly one correct location.
This matters as much for testing as for code generation. Unit tests target services with mocked repositories. Integration tests target repositories against Testcontainers. E2E tests target components through Playwright. The agent writes the right test at the right level because the layers are honest about what they do.
Clean code structure isn’t just for human readers anymore. It’s the substrate the agent reasons over. Every architectural shortcut you take - God objects, mixed concerns, business logic in controllers - degrades the agent’s ability to navigate, generate consistently, and test correctly. With a properly layered codebase, the agent inherits the discipline. Without one, it amplifies the mess.
What a Skill actually is
A Claude Skill is a markdown file (SKILL.md) that lives in your repo under .claude/skills/{name}/ and tells Claude how to perform a specific task in your codebase. When you type its slash-command - say /migration - Claude reads the file and follows the steps inside.
Every skill starts with a YAML frontmatter block. “Frontmatter” is just a small block of metadata at the top of a markdown file, fenced by --- lines, that tools read to know what the file is. In a Claude Skill, it carries three fields:
---
name: api-endpoint
description: Add a new backend API endpoint with service, repository, model, and tests
command: /api-endpoint
---
name- a unique identifierdescription- what Claude reads to decide when this skill applies; keep it crispcommand- the slash-command you type to invoke it
Everything below the frontmatter is the runbook. File paths, naming conventions, patterns to follow and avoid, code examples, test commands, the exact checklist before opening a PR. The format takes thirty seconds to learn. The leverage comes from what you put in it.
For context, Fortius runs C# .NET 8 with hand-rolled Dapper repositories, manual DI, multi-tenant SQL queries, a React + Ant Design admin frontend, Flyway migrations, and Playwright E2E tests in two modes (mock and against a real local backend). None of those are what an LLM defaults to.
The six skills, bottom-up
Each skill assumes the layered architecture is in place - they tell Claude which layer something belongs in, not what a layered architecture is.
/migration - Flyway database migrations. Enforces our schema conventions, checks the next version number, and keeps database/schema.sql in sync. Looks trivial; it’s the most-used skill in the set.
/api-endpoint - Walks Claude up the backend layers in order: SQL queries with mandatory multi-tenancy filtering, domain model, display model with factory methods, Dapper repository, service with error handling, endpoint registration with the right [Authorize] policy. Every step is a thing I would otherwise correct by hand on every PR.
/component - The frontend equivalent, walking the parallel structure. Functional components only, the service-pattern API client (no raw fetch), Service Provider + Context + Hook layering, Ant Design, no Redux. When this skill runs, Claude reads the C# display model from apps/api and produces the matching TypeScript interface - the contract stays consistent automatically.
/integration-test - Database integration tests with Testcontainers. Reads the existing test infrastructure first, follows the Method_Scenario_Expected naming convention, uses the right test category. Because the repository is the only thing that talks to the database, integration tests target it cleanly.
/feature - Composes the previous four for full-stack features. The longest skill in the library because it handles the cross-layer concerns: making sure the migration is applied before the API is wired, making sure the TypeScript model matches the C# display model, making sure the E2E test covers the actual user path.
/ticket - The orchestrator. Given a JIRA key like FORT-95, fetches the ticket via the Atlassian MCP integration, analyses it and presents its understanding back to me, syncs develop with main, creates a feature branch, implements the change by composing the skills above, runs the full test matrix, self-reviews against a 30-point checklist, commits, and opens a PR via gh with a fully populated PR template. What used to be half a day of context-switching is now a single command and a code review.
What changed at Fortius
Cycle time on small-to-medium tickets dropped sharply. A typical CRUD feature - new table, three endpoints, an admin screen, tests - that used to be a focused day’s work is now a couple of hours of guided execution and review. The bottleneck is now my ability to review thoughtfully, not to type.
Cross-layer consistency stopped being my job. The monorepo, the layered architecture, and the orchestration skills mean the agent has the full picture and knows where every piece goes.
Convention drift stopped. Patterns are reinforced on every feature, including the ones I’d otherwise be tempted to cut corners on.
Tests stopped being optional. The /ticket skill won’t open a PR until the full test matrix passes - including the slow Testcontainers and local-backend Playwright runs. Because the layered architecture gives each test type a clean target, the agent writes them at the right level the first time.
The PRs are reviewable. I’m reviewing the work, not reconstructing what was done.
Snippets you can steal
Three copy-pasteable patterns from my library, then a sample of all six skills so you can see the shape they take.
The “walk up the layers” pattern
This is the structural backbone of every implementation skill. Each section has the same four parts: what layer, what file, what code looks like, what the rules are. Predictability is the point - both for Claude reading the skill and for you maintaining it.
Here’s how the /api-endpoint skill walks Claude through the domain and display model layers:
## 3. Domain Model
Create or update the domain model record:
**File:** `apps/api/src/fortius-model/{Domain}/{Entity}.cs`
```csharp
namespace Fortius.Model.{Domain};
public record {Entity}(
long Id,
string Name,
DateTime CreatedAt
);
```
## 4. Display Model
Create the API response shape:
**File:** `apps/api/src/fortius-display-model/{Domain}/Display{Entity}.cs`
```csharp
namespace Fortius.DisplayModel.{Domain};
public record Display{Entity}(
long Id,
string Name,
DateTime CreatedAt
)
{
public static Display{Entity} From{Entity}({Entity} entity) =>
new(entity.Id, entity.Name, entity.CreatedAt);
}
```
The placeholders ({Entity}, {Domain}) are deliberate. Claude substitutes them based on the feature it’s building, and the parallel between domain and display model is enforced by the structure of the skill itself.
The service layer pattern
Same skill, a few sections later. This is what enforces error handling, logging, and ConfigureAwait(false) on every backend service we ship:
## 6. Service
```csharp
public class {Entity}Service(
I{Entity}Repository repository,
IErrorService errorService,
ILogger<{Entity}Service> logger) : I{Entity}Service
{
public async Task<{Entity}?> GetById(string clientName, long id)
{
try
{
logger.LogServiceOperationDebug(
"Getting {Entity} [{Id}] for client [{ClientName}]",
id, clientName);
return await repository.GetById(clientName, id)
.ConfigureAwait(false);
}
catch (Exception ex)
{
logger.LogServiceError(ex, "Failed to get {Entity} [{Id}]", id);
errorService.CaptureException(ex);
throw;
}
}
}
```
**Notes:**
- Use primary constructor for DI
- Wrap operations in try/catch + errorService.CaptureException()
- Use ConfigureAwait(false) on all awaits
- Log at debug level for operations, error level for exceptions
Three things worth noting. The primary constructor cuts boilerplate. The try/catch + errorService.CaptureException() pattern means every error is captured to our error tracking by default. The ConfigureAwait(false) line is the kind of thing you forget in code review on PR seventy and remember on PR seventy-one - encoding it in the skill removes the question.
The self-review checklist
The most copy-pasteable thing in my library. The full version in /ticket is 30 items across five categories. Here’s a generalised version you can adapt to any codebase, in any language, today.
## Self-Review
Before creating the PR, check every changed file for:
### Code Quality
- [ ] No commented-out code left behind
- [ ] No TODO/FIXME/HACK comments without corresponding tickets
- [ ] No debug logging (console.log, Console.WriteLine, print) left in
- [ ] No hardcoded values that should be configurable
- [ ] All new public methods/classes have meaningful names
### Architecture Compliance
- [ ] {Repository pattern enforced - e.g. Dapper, not EF Core}
- [ ] {DI strategy - e.g. Manual wiring in Program.cs, not AddScoped}
- [ ] {Display models with factory methods used for API responses}
- [ ] {Cross-cutting concern - e.g. Multi-tenancy: all queries scoped by tenant}
- [ ] {Async hygiene - e.g. ConfigureAwait(false) on all awaits}
- [ ] {Error handling - e.g. try/catch + capture to error tracker}
### Frontend Compliance
- [ ] Functional components only (no class components)
- [ ] Service-pattern API client used (not raw fetch/axios)
- [ ] Service Provider + Context + Hook pattern for shared state
- [ ] {UI library convention - e.g. Ant Design components only}
- [ ] No Redux, no react-query (or whatever your stack excludes)
### Security
- [ ] No secrets, tokens, or credentials in code
- [ ] No SQL injection vulnerabilities (parameterized queries only)
- [ ] All endpoints have authorization attributes
- [ ] Admin-only endpoints use the stricter authorization policy
### Testing
- [ ] Unit tests cover happy path and error scenarios
- [ ] Integration tests have appropriate category/tag
- [ ] Test naming follows {Method_Scenario_Expected} convention
- [ ] E2E tests added for the user-facing path
- [ ] E2E tests pass against the real backend, not just mocks
If any issues are found, fix them before proceeding to commit.
The checklist is the part that converts “Claude wrote some code” into “Claude wrote code that’s actually ready for review.” Every item maps to something I’ve personally caught in code review more than once. If you only adopt one thing from this post, adopt this.
What the six skills actually look like
Here’s the frontmatter, the full step list, and one concrete snippet from each skill in my library. Same shape, different scope - but you can see how the size of the skill scales with the size of the workflow it covers.
A note on what’s shown below: for each skill I’m showing the section headings (so you see the full scope of the workflow) plus one expanded section as a worked example. In my actual files, every numbered heading has a body underneath it - file paths, code examples, conventions, rules. To adapt these to your own codebase, you’d flesh out each heading with the equivalent for your stack. I’ve added a one-line description under each heading below to indicate what that section should contain.
/migration - 7 steps
---
name: migration
description: Create a new Flyway database migration with proper versioning and schema.sql sync
command: /migration
---
# Create Database Migration
## 1. Gather Requirements
> Ask the user what schema change is needed and which domain it belongs to.
## 2. Determine Next Version
> List existing migrations to find the next version number for the file name.
## 3. Create Migration File
> Write the `V{N}__{description}.sql` file using your project's SQL conventions.
## 4. Update schema.sql (CRITICAL)
> Apply the same DDL to the schema file used by Testcontainers in integration tests.
## 5. Add Reference File
> If creating a new table, add a reference DDL file under `database/{schema}/{table}/`.
## 6. Note Affected API Code
> Tell the user which API files (queries, models, repositories) may need updating.
## 7. Verify
> Run local Flyway validation to check the migration syntax.
The “CRITICAL” tag on step 4 isn’t decorative - database/schema.sql is what Testcontainers uses to spin up the test database. Skip it and integration tests fail immediately. A flavour of step 3:
## 3. Create Migration File
Create `database/flyway/migrations/V{N}__{snake_case_description}.sql`:
**Naming rules:**
- Double underscore `__` between version and description
- snake_case description (e.g., `add_member_notes`, `alter_booking_add_status`)
**SQL rules:**
- Always prefix objects with `dbo.` schema
- Use `BIGSERIAL` for primary keys
- Use `TIMESTAMP WITH TIME ZONE` for timestamps
- Add indexes for foreign keys and commonly queried columns
/api-endpoint - 11 steps
---
name: api-endpoint
description: Add a new backend API endpoint with service, repository, model, DTO, and tests
command: /api-endpoint
---
# Add API Endpoint
Work from the database layer up to the API layer.
## 1. Gather Requirements
> Ask what the endpoint does, which domain, what data it accepts and returns, and whether a migration is needed first.
## 2. Database Queries
> Add SQL queries as `const string`s in `Database/Queries/{Domain}Queries.cs`, with multi-tenancy filtering.
## 3. Domain Model
> Create the pure domain record in the model project - no external dependencies.
## 4. Display Model
> Create the API response shape with a `From{Entity}()` factory method.
## 5. Repository
> Create the repository interface and Dapper implementation, with `ConfigureAwait(false)` on every await.
## 6. Service
> Create the service interface and implementation, with try/catch + error tracking around every operation.
## 7. API Endpoint
> Add the endpoint registration in `ApiConfiguration.cs` with the right `[Authorize]` policy.
## 8. Program.cs Wiring
> Wire dependencies manually with `new` - explicitly not `AddScoped`.
## 9. Unit Tests
> Add NUnit + Moq + AutoFixture tests for the service layer, mocking the repository.
## 10. Integration Tests (Optional)
> If the repository has non-trivial SQL, add a `[Category("DbIntegration")]` test against Testcontainers.
## 11. Run Full Test Suite
> Run `dotnet build` and the full test suite before considering the work done.
Steps 2–7 are the “walk up the layers” pattern - each one names a layer, gives the file path, shows the code, lists the rules. Step 8 is where the manual DI shows up:
## 8. Program.cs Wiring
Wire up dependencies manually in `Program.cs`:
```csharp
var {entity}Repository = new {Entity}Repository(
dbConnectionFactory,
fortiusConfiguration,
loggerFactory.CreateLogger<{Entity}Repository>());
var {entity}Service = new {Entity}Service(
{entity}Repository,
errorService,
loggerFactory.CreateLogger<{Entity}Service>());
ApiConfiguration.Configure{Feature}(app, {entity}Service, apiLogger);
```
**IMPORTANT:** Use `new` for instantiation. Do NOT use
`builder.Services.AddScoped<>()`.
That last instruction looks pedantic. It exists because every LLM in existence reaches for AddScoped by default, and we don’t use it.
/component - 8 steps
---
name: component
description: Add a new React component following project patterns with service integration
command: /component
---
# Add React Component
## 1. Gather Requirements
> Ask what the component is for, which domain, whether it needs API calls, and whether it's a new page or child component.
## 2. TypeScript Model (if needed)
> Create the TypeScript interface in `src/model/{domain}/` matching the backend's display model.
## 3. Request Interface (if mutation needed)
> Create the request DTO in `src/requests/{domain}/` matching the backend's request shape.
## 4. Service (if new API calls needed)
> Create the service class extending the API client, plus a Service Provider and `use{Domain}Service()` hook.
## 5. Create the Component
> Build the functional component in `src/components/{domain}/`, consuming services via the hook, using the project's UI library.
## 6. Add Routing (if new page)
> Add the route in `App.tsx` wrapped in `<ProtectedRoute>` for auth gating.
## 7. Add E2E Tests
> Add Playwright tests that work in both mock mode and against the real local backend.
## 8. Run Full Test Suite
> Run `npm run build` and Playwright in both modes before considering the work done.
The Service Provider + Hook pattern is the most distinctive piece. Step 4 enforces it:
## 4. Service (if new API calls needed)
**Provider:** `apps/admin-web/src/services/{domain}/{Domain}ServiceProvider.tsx`
```typescript
const {Domain}ServiceContext =
React.createContext<{Domain}Service | undefined>(undefined);
export const {Domain}ServiceProvider: React.FC<Props> = ({ children }) => {
const { getAccessTokenSilently } = useAuth0();
const { currentClientId } = useClient();
const service = React.useMemo(
() => new {Domain}Service(getAccessTokenSilently, currentClientId),
[getAccessTokenSilently, currentClientId]
);
return (
<{Domain}ServiceContext.Provider value={service}>
{children}
</{Domain}ServiceContext.Provider>
);
};
export const use{Domain}Service = (): {Domain}Service => {
const context = React.useContext({Domain}ServiceContext);
if (!context) {
throw new Error('use{Domain}Service must be used within a Provider');
}
return context;
};
```
Components consume the service via use{Domain}Service() - never by calling fetch directly. The skill makes that non-negotiable.
/integration-test - 8 steps
---
name: integration-test
description: Add database integration tests using Testcontainers and TestFactory
command: /integration-test
---
# Add Integration Test
## 1. Gather Requirements
> Ask which repository or service to test, which scenarios matter, and whether seed data is needed.
## 2. Check Existing Patterns
> Read `TestFactory.cs` and `IntegrationTestBase.cs` first - don't reinvent test infrastructure.
## 3. Add TestFactory Method (if needed)
> If `TestFactory` doesn't have a method for this entity, add one that wires up the repository or service correctly.
## 4. Create Seed Data (if needed)
> Add an idempotent SQL seed file using `ON CONFLICT DO NOTHING` for any required test data.
## 5. Create Test Class
> Create the test fixture inheriting from `IntegrationTestBase`, with `[Category("DbIntegration")]`.
## 6. Test Naming Convention
> Follow `Method_Scenario_ExpectedResult` for every test name.
## 7. Test Assertions
> Use NUnit `Assert.That()` syntax - explicitly not FluentAssertions.
## 8. Run Tests
> Run the `DbIntegration` filtered test suite to confirm the new tests pass.
Step 2 is the most important one - it tells Claude to read TestFactory.cs and IntegrationTestBase.cs before writing anything. Without it, the agent will helpfully reinvent test infrastructure that already exists. Step 5 then anchors the test class shape:
## 5. Create Test Class
```csharp
[TestFixture]
[Category("DbIntegration")]
public class {Entity}RepositoryTests : IntegrationTestBase
{
private I{Entity}Repository _repository;
[SetUp]
public void SetUp()
{
_repository = TestFactory.Create{Entity}Repository(LogFactory);
}
[Test]
public async Task GetById_WithValidId_ReturnsEntity()
{
// Arrange - seed via SQL file or inline
// Act
var result = await _repository.GetById("test-client", 1);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Name, Is.EqualTo("Expected Name"));
}
}
```
The [Category("DbIntegration")] attribute is what lets us run the slow Testcontainers tests separately from the fast unit tests in CI.
/feature - 17 steps (the longest)
---
name: full-stack-feature
description: Add a full-stack feature spanning database, API, and frontend with tests
command: /feature
---
# Full-Stack Feature Development
Work bottom-up: database first, then API, then frontend.
## 1. Gather Requirements
> Ask for feature name, domain, data shape, CRUD operations, and what the UI should look like.
## 2. Database Migration
> Create the Flyway migration, update `schema.sql`, and add a reference DDL file (sub-steps 2a, 2b, 2c).
## 3. Backend - Domain Model
> Create the pure domain record with no external dependencies.
## 4. Backend - Display Model
> Create the API response shape with `From{Entity}()` factory methods.
## 5. Backend - SQL Queries
> Add Dapper queries as `const string`s with multi-tenancy filtering.
## 6. Backend - Repository
> Create the repository interface and Dapper implementation.
## 7. Backend - Service
> Create the service interface and implementation with error handling.
## 8. Backend - API Endpoints
> Register the endpoints with the right authorization policies.
## 9. Backend - Program.cs Wiring
> Wire dependencies manually with `new`, not `AddScoped`.
## 10. Backend - Unit Tests
> Add NUnit + Moq + AutoFixture tests for the service layer.
## 11. Backend - Integration Tests
> Add Testcontainers-backed tests for the repository.
## 12. Frontend - TypeScript Model
> Create the TypeScript interface that mirrors the backend Display Model exactly.
## 13. Frontend - Request Interface (if mutations needed)
> Create the request DTO that mirrors the backend's request shape.
## 14. Frontend - Service + Provider
> Create the service class, Service Provider, and `use{Domain}Service()` hook.
## 15. Frontend - Component
> Build the functional component consuming services via the hook, using the project's UI library.
## 16. Add E2E Tests
> Add Playwright tests covering the user-facing path in both mock and local-backend modes.
## 17. Run Full Test Suite
> Run backend build + tests, frontend build, and Playwright in both modes before considering done.
This is the longest skill in the library because it has to coordinate across all layers and explicitly enforce the parallel structure between backend and frontend. Step 12 is where the symmetry shows up:
## 12. Frontend - TypeScript Model
Create the TypeScript interface that mirrors the backend Display Model.
**File:** `apps/admin-web/src/model/{domain}/{Entity}.tsx`
```typescript
export interface {Entity} {
id: number;
name: string;
createdAt: string; // ISO date string - use DateReviver for parsing
}
```
**Rules:**
- Field names must match the C# Display Model property names exactly
(camelCased)
- Use the `.tsx` extension even for pure interfaces (project convention)
Because Claude has just written the C# Display{Entity} in step 4, generating the TypeScript interface in step 12 is mechanical - the agent reads its own earlier output and produces the matching shape. The monorepo and the layered architecture are both load-bearing here.
/ticket - 9 steps (the orchestrator)
---
name: ticket
description: Implement a JIRA ticket end-to-end with feature branch, code, tests, review, and PR
command: /ticket
---
# Implement JIRA Ticket
## 1. Fetch JIRA Ticket
> Use the Atlassian MCP tools to pull summary, description, acceptance criteria, type, and all comments.
## 2. Analyse Requirements
> Present your understanding of the ticket back to the user (what, which layers, data model, endpoints, components, test strategy) and wait for confirmation.
## 3. Prepare Git Branch
> Sync `develop` with `main` (squash-merge dance), then create a correctly-named feature branch.
## 4. Implement the Feature
> Delegate to the lower-level skills based on which layers are affected:
### If Database Changes Needed → /migration
### If API Changes Needed → /api-endpoint
### If Frontend Changes Needed → /component
### Tests → /integration-test + Playwright
## 5. Run Full Test Suite
> Run the full matrix: build, unit, `DbIntegration`, frontend build, Playwright (mock), Playwright (local-backend).
## 6. Self-Review (30-point checklist)
> Walk the checklist across Code Quality, Architecture Compliance, Frontend Compliance, Security, and Testing. Fix any issues before proceeding.
## 7. Commit Changes
> Use the conventional commit format: `feat: {description} [{TICKET-KEY}]`.
## 8. Push and Create PR
> Push the branch and use `gh pr create` with the populated PR template (summary, changes by layer, testing checklist).
## 9. Return Results
> Report the PR URL, summary of changes, files touched, and any open follow-ups.
Notice that step 4 explicitly delegates to the lower-level skills. /ticket doesn’t reimplement the patterns - it composes them. Step 3 is where the git workflow gets enforced:
## 3. Prepare Git Branch
Ensure `develop` is in sync with `main` before branching. Since we
squash-merge PRs from develop into main, develop will diverge after
each release. We must merge main back into develop to pick up the
squashed commits and avoid conflicts.
```bash
git fetch origin
git checkout develop
git pull origin develop
git merge origin/main --no-edit
git push origin develop
git checkout -b feature/{TICKET-KEY}_{snake_case_short_summary}
```
**Branch naming rules:**
- Prefix: `feature/` for features, `bugfix/` for bugs, `hotfix/` for hotfixes
- Format: `{prefix}{TICKET-KEY}_{short_snake_case_description}`
- Examples:
- `feature/FORT-95_implement_transaction_screen`
- `bugfix/FORT-102_fix_booking_overlap_validation`
That sync-develop-with-main dance is the kind of thing every team has somewhere - usually in a CONTRIBUTING.md nobody reads, occasionally in tribal knowledge passed verbally between engineers. Encoded in the skill, it just happens, every time, correctly.
The pattern across all six is consistent: tight frontmatter, an opening that gathers requirements (or in /ticket‘s case, fetches them), then numbered steps the agent walks in order. The skills get bigger as the workflow gets bigger - /migration is 7 steps, /feature is 17 - but the shape never changes.
Where to start
If you’re staring at your own codebase wondering where to begin: pick the single workflow you do most often - for most teams that’s “implement a ticket” or “add an endpoint” - and write the smallest possible skill that captures the file paths, the conventions, and a self-review checklist for that one workflow. Use it for a week. Add to it every time you correct Claude on something it should have known. By the end of the week you’ll know whether the deeper investment is worth it.
What I learned
Architecture is a prerequisite for AI leverage. The skills wouldn’t have worked across three repos. They wouldn’t have worked with a tangled codebase either. The monorepo and the DDD layering were the unglamorous changes that made everything else possible.
Skills are documentation that executes. Every constraint I encoded is also one a new engineer would need to learn. The skills are the most accurate, most up-to-date description of how the codebase works - because if they drift, the build breaks immediately. They’ve quietly become better onboarding material than any wiki I’ve maintained.
The right abstraction is the workflow, not the file. My first instinct was to write skills around file types. The skills that actually pay off are organised around the unit of work: migrate the database, ship an endpoint, implement a ticket. The orchestration skill is where the leverage really compounds.
Why I think this is the pattern
The model is the easy part. The hard part of running production software with an LLM in the loop is three things: giving the agent a codebase shape it can see (the monorepo), giving it a structure it can navigate (clean layered architecture), and encoding the thousand small decisions that make that codebase yours in a form the agent can act on (the skills).
Get all three right and the leverage compounds. Prompts don’t compound. CLAUDE.md helps but doesn’t execute. Skills do both - versioned, composable, executable, living next to the code they describe. But skills on top of a fragmented or tangled codebase are skills built on sand.
Most teams running serious production codebases on top of agentic coding tools will end up here. The teams that get there first will have a meaningful delivery advantage, and the gap will widen as the orchestration layers get richer.
Next up at Fortius: a /release skill, a /bugfix variant of /ticket, and pushing further into multi-agent orchestration with LangGraph for the parts of the workflow that benefit from genuine planning rather than scripted runbooks. More on that in a future post.
About me
I’m Ahmed El-Refee — fifteen years in financial services engineering across Elliott Management, LMR Partners, Credit Suisse, and Daiwa Capital Markets, building real-time risk, P&L, VaR engines, and trading systems. I’m now Co-founder and CTO of Fortius Technology, working at the intersection of production software engineering and applied AI: skills libraries, agentic coding workflows, and AI-native delivery systems for teams that take their codebases seriously.
If you’re a founder, engineering leader, or hiring manager thinking about how to get this kind of leverage in your own team - full-time, fractional, or consulting - I’d like to hear from you. Reach me on LinkedIn or at ahmed@fortius-app.com.
If this was useful, subscribe - I write about the seam between financial services engineering and applied AI, and there’s more in this series coming. Let me know what other interesting topics you’d like me to write about?

