Docs
Quality Guardrails

Quality Guardrails

Conventional commits, AI determinism rules, and code review practices that steer agent output towards architecturally sound code.


Quality guardrails are the rules and conventions that prevent the agent from taking shortcuts. Without them, generated code can work today and need a rewrite in six months. Each guardrail encodes a specific quality constraint — and combined, they significantly increase the predictability of the agent’s output.

Conventional Commits

Conventional Commits is a specification for commit message formatting that produces a history readable by both humans and machines. At Aircury it’s used for all commits, agent-generated or otherwise.

Format

<type>[optional scope]: <description>

[optional body]

[optional footer]

Commit types reference

TypeWhen to useExample
featNew feature or capabilityfeat(auth): add password reset via email
fixBug fixfix(orders): prevent double-confirmation race condition
refactorCode change that neither fixes a bug nor adds a featurerefactor(payment): extract StripeAdapter from PaymentService
testAdding or updating teststest(orders): add BDD scenario for cancelled order refund
docsDocumentation onlydocs(readme): add installation steps for M1 Macs
choreBuild, tooling, dependency updateschore(deps): upgrade TypeScript to 5.4
perfPerformance improvementperf(queries): add index on orders.created_at
ciCI/CD configurationci: add pre-commit hook for conventional commits lint
styleFormatting, missing semicolons (no logic change)style: apply prettier formatting
revertRevert a previous commitrevert: feat(auth): add password reset

Breaking changes

Mark breaking changes explicitly in the footer, or with ! after the type:

feat(api)!: change /orders endpoint to require authentication

BREAKING CHANGE: The /orders endpoint now requires a valid Bearer token.
Clients must include the `Authorization` header with all requests.

Agent instruction for Conventional Commits

Include this in your agent rules:

All commit messages must follow Conventional Commits specification:
- Format: type(scope): subject
- Types: feat, fix, refactor, test, docs, chore, perf, ci, style, revert
- Subject: imperative mood ("add", not "added" or "adds"), no period at end
- Breaking changes: append ! to type or add BREAKING CHANGE footer
- Never commit with generic messages like "update code" or "fix stuff"
Default agent behaviour

Without this rule, the agent typically commits with chore: update or descriptive-but-untyped messages like "Added the new payment feature". Conventional commits aren’t optional — they enable automated changelogs, semantic versioning, and a git history that actually means something.

AI determinism rules

These are the specific rules that reduce variance in the agent’s generated code — steering it towards the same structural decisions every time, regardless of the model or the day.

Architecture rules

## Architecture Rules (include in all agent instructions)

1. DOMAIN LAYER PURITY
   - Domain classes (entities, value objects, aggregates, use cases) MUST NOT
     import from infrastructure packages (ORMs, HTTP clients, queues, etc.)
   - All external dependencies are injected via constructor as interfaces

2. DEPENDENCY DIRECTION
   - Dependencies always point inward (adapters → ports → domain)
   - Never import from a higher-level layer into a lower-level layer

3. REPOSITORY PATTERN
   - Persistence access MUST go through Repository interfaces
   - Direct database queries in domain/service classes are forbidden

4. NO ANEMIC MODELS
   - Domain entities must contain business logic, not just getters/setters
   - Use cases orchestrate; entities enforce invariants

5. HEXAGONAL STRUCTURE
   - File structure: src/domain/, src/application/, src/infrastructure/
   - Ports in: src/application/ports/
   - Adapters in: src/infrastructure/adapters/

Naming conventions

## Naming Rules

- Classes:          PascalCase (OrderService, PaymentGateway)
- Interfaces:       PascalCase, no "I" prefix (OrderRepository, not IOrderRepository)
- Files:            kebab-case (order-service.ts, payment-gateway.ts)
- Domain folders:   kebab-case, plural (orders/, users/, payments/)
- Constants:        SCREAMING_SNAKE_CASE (MAX_RETRY_ATTEMPTS)
- Generics:         Single uppercase letter or descriptive (T, TEntity, TResult)
- Event classes:    Past tense noun (OrderCreated, PaymentFailed)
- Command classes:  Imperative noun (CreateOrder, ProcessPayment)

Code style rules

## Code Style Rules

- Prefer explicit return types on all public functions
- No any type — use unknown and narrow with type guards
- Prefer early returns over nested conditionals
- Error handling: use typed error classes, never throw raw strings
- No magic numbers or strings — extract to named constants
- Functions: max 20 lines before considering extraction
- Files: max 200 lines before considering splitting

Reviewing AI-generated code

Reviewing code from an agent requires a different mindset than reviewing human-written code. Humans make mistakes of understanding; agents make mistakes of pattern. Knowing which patterns to watch for makes review much more effective.

What the agent tends to get right (less focus in review)

  • Syntax correctness
  • Happy-path test coverage
  • Names for obvious things
  • Implementation of well-specified algorithms

What the agent tends to get wrong (more focus in review)

PatternWhat to look for
SRP violationsServices that do 3 things; methods with multiple && guard clauses
Missing error pathsHappy-path implementation that ignores failures; no error types defined
DIP violationsDirect new SomeDatabase() or new StripeClient() inside domain
Unstated assumptionsAgent assumes a field is always present; no null/undefined handling
Generic commit messageschore: update instead of feat(auth): implement JWT refresh token
Tests that check implementation detailsTests calling private methods; tests verifying SQL queries
God objects400-line service class that “handles everything related to users”
Mutable shared stateStatic fields used as cross-request state; module-level variables

Review checklist

## AI Code Review Checklist

Architecture:
- [ ] Domain layer has zero infrastructure imports
- [ ] All external dependencies are constructor-injected as interfaces
- [ ] Repository pattern used for all persistence
- [ ] File structure follows src/domain|application|infrastructure

SOLID:
- [ ] Each class has one responsibility (describable in one sentence)
- [ ] No switch/if-else on types that should use polymorphism
- [ ] Dependencies are abstract (interfaces), not concrete implementations

Testing:
- [ ] Happy paths covered at BDD layer
- [ ] Business rules covered at unit layer
- [ ] Integration points covered at integration layer
- [ ] Tests describe behaviour, not implementation

Commits:
- [ ] All commit messages follow conventional commits format
- [ ] Breaking changes explicitly marked
- [ ] Scope reflects the affected capability

Error handling:
- [ ] All error paths handled (no silent ignores)
- [ ] Custom error types defined and used
- [ ] Errors are meaningful and actionable
The right mindset for review

When reviewing agent code, you’re not checking if it works — your tests do that. You’re checking if it will still be maintainable when the next engineer (or agent) touches it six months from now. Review for future readability, not current correctness.

Linting and formatting

Automated linting is the first line of defence — it catches guardrail violations before code review.

// package.json
{
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^7.0.0",  // TypeScript-specific rules
    "@typescript-eslint/parser": "^7.0.0",
    "eslint": "^9.0.0",
    "eslint-plugin-boundaries": "^4.0.0",           // Enforce layer boundaries
    "prettier": "^3.0.0",
    "husky": "^9.0.0",                              // Pre-commit hooks
    "@commitlint/cli": "^19.0.0",                   // Lint commit messages
    "@commitlint/config-conventional": "^19.0.0"
  }
}

Boundary enforcement with eslint-plugin-boundaries

// eslint.config.js — enforce your hexagonal architecture
{
  rules: {
    "boundaries/element-types": ["error", {
      default: "disallow",
      rules: [
        { from: "infrastructure", allow: ["application", "domain"] },
        { from: "application",    allow: ["domain"] },
        { from: "domain",         allow: [] }, // domain imports nothing
      ]
    }]
  }
}

This turns architecture violations into ESLint errors. The agent can’t silently break layer boundaries — the lint check fails.