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
| Type | When to use | Example |
|---|---|---|
feat | New feature or capability | feat(auth): add password reset via email |
fix | Bug fix | fix(orders): prevent double-confirmation race condition |
refactor | Code change that neither fixes a bug nor adds a feature | refactor(payment): extract StripeAdapter from PaymentService |
test | Adding or updating tests | test(orders): add BDD scenario for cancelled order refund |
docs | Documentation only | docs(readme): add installation steps for M1 Macs |
chore | Build, tooling, dependency updates | chore(deps): upgrade TypeScript to 5.4 |
perf | Performance improvement | perf(queries): add index on orders.created_at |
ci | CI/CD configuration | ci: add pre-commit hook for conventional commits lint |
style | Formatting, missing semicolons (no logic change) | style: apply prettier formatting |
revert | Revert a previous commit | revert: 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)
| Pattern | What to look for |
|---|---|
| SRP violations | Services that do 3 things; methods with multiple && guard clauses |
| Missing error paths | Happy-path implementation that ignores failures; no error types defined |
| DIP violations | Direct new SomeDatabase() or new StripeClient() inside domain |
| Unstated assumptions | Agent assumes a field is always present; no null/undefined handling |
| Generic commit messages | chore: update instead of feat(auth): implement JWT refresh token |
| Tests that check implementation details | Tests calling private methods; tests verifying SQL queries |
| God objects | 400-line service class that “handles everything related to users” |
| Mutable shared state | Static 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.
Recommended tools
// 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.
On This Page
- Conventional Commits
- Format
- Commit types reference
- Breaking changes
- Agent instruction for Conventional Commits
- AI determinism rules
- Architecture rules
- Naming conventions
- Code style rules
- Reviewing AI-generated code
- What the agent tends to get right (less focus in review)
- What the agent tends to get wrong (more focus in review)
- Review checklist
- Linting and formatting
- Recommended tools
- Boundary enforcement with eslint-plugin-boundaries