| name | deptrac-fixer |
| description | Diagnose and fix Deptrac architectural violations by refactoring code to respect hexagonal architecture boundaries. Use when Deptrac reports dependency violations ("must not depend on"), layers are incorrectly coupled, the Domain layer imports framework code, or Infrastructure calls Application handlers directly. Never modifies deptrac.yaml โ always fixes the code to match the architecture. |
Deptrac Fixer Skill
Profile keys consumed
make.deptrac
make.tests
make.ci
quality.deptrac_violations
architecture.source_root
architecture.bounded_contexts
architecture.shared_context
persistence.mapper
framework.name
framework.api_platform
Context (Input)
- The target mapped by
make.deptrac reports violations
- Error message contains "must not depend on"
- Domain layer has framework imports (
framework.name components, Doctrine, API Platform)
- Infrastructure directly calls Application handlers
- Any architectural boundary violation detected
If make.deptrac is null, the capability is absent โ record a degrade note and stop instead of inventing a target.
Task (Function)
Diagnose and fix Deptrac violations by refactoring code to respect hexagonal architecture boundaries.
Success criteria: the target mapped by make.deptrac reports quality.deptrac_violations violations โ a fixed ceiling of 0 that may never be raised (relaxing it would lower the bar; raise-only thresholds and fixed ceilings per ADR-7).
Core Principle
Fix the code, NEVER modify deptrac.yaml
The architecture is correct. The code must conform to it, not vice versa.
Quick Start: Fix a Violation
Step 1: Run Deptrac
Run the target mapped by make.deptrac.
Step 2: Parse Violation Message
Domain must not depend on Symfony
<architecture.source_root>/<Context>/Domain/Entity/Customer.php:8
uses Symfony\Component\Validator\Constraints as Assert
<Context> is one of architecture.bounded_contexts (or architecture.shared_context).
Extract:
- Violating layer: Domain
- Forbidden dependency: the framework component
- File & line: path under
architecture.source_root
- Violation type:
uses (import statement)
Step 3: Identify Fix Pattern
| Violation | Fix pattern |
|---|
| Domain โ framework validator | Move constraints to validator YAML config (Pattern 1) |
| Domain โ Doctrine (ORM/ODM) | Move mapping to XML files (Pattern 2) |
| Domain โ API Platform | Move resource config to YAML (Pattern 3) |
| Infrastructure โ Handler | Use Command Bus (Pattern 4) |
Step 4: Apply Fix
Apply the matching pattern below, then re-run the target mapped by make.deptrac. Repeat until zero violations.
Layer Dependency Rules
Domain โโโโโโโโโโโโโโโโโ> (NO dependencies)
โ
Application โโโโโโโโโโ> Domain + Infrastructure + framework + API Platform
โ
Infrastructure โโโโโโโ> Domain + Application + framework + Doctrine
| Layer | Can depend on |
|---|
| Domain | Nothing (pure PHP only) |
| Application | Domain, Infrastructure, framework.name, API Platform |
| Infrastructure | Domain, Application, framework.name, Doctrine |
Layers repeat per bounded context: <architecture.source_root>/<context>/{Domain,Application,Infrastructure} for each entry in architecture.bounded_contexts, plus the shared kernel under architecture.shared_context when set.
Common Fix Patterns (Quick Reference)
Pattern 1: Domain โ Framework Validator
โ Problem: validation attributes in a Domain entity
use Symfony\Component\Validator\Constraints as Assert;
class Customer {
#[Assert\NotBlank]
private string $name;
}
โ
Solution: move validation to the framework's validator config (e.g. config/validator/{Entity}.yaml)
Pattern 2: Domain โ Doctrine Annotations
โ Problem: Doctrine attributes in a Domain entity
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Customer { }
โ
Solution: create XML mapping in config/doctrine/ โ file suffix follows persistence.mapper (.orm.xml for doctrine-orm, .mongodb.xml for doctrine-odm)
Pattern 3: Domain โ API Platform
Applies only when framework.api_platform is set (not false).
โ Problem: API Platform attributes in a Domain entity
use ApiPlatform\Metadata\ApiResource;
#[ApiResource]
class Customer { }
โ
Solution: create YAML config in config/api_platform/resources/{entity}.yaml
Pattern 4: Infrastructure โ Application Handler
โ Problem: direct handler call from Infrastructure
class Repository {
public function __construct(
private SomeHandler $handler // โ Circular dependency
) {}
}
โ
Solution: use the Command Bus pattern
class Repository {
public function __construct(
private CommandBusInterface $commandBus // โ
Interface
) {}
public function someMethod() {
$this->commandBus->dispatch(new SomeCommand());
}
}
Diagnostic Workflow
When facing multiple violations:
- Get all violations: run the target mapped by
make.deptrac and capture the output to a scratch file.
- Categorize by layer pair: Domain โ framework, Domain โ Doctrine, Domain โ API Platform, Infrastructure โ Application, etc. Group per bounded context (
architecture.bounded_contexts).
- Fix in priority order:
- Domain violations first (most critical โ purity of the core)
- Infrastructure violations (circular dependencies)
- Application violations (least common)
- Verify incrementally: re-run the target mapped by
make.deptrac after each fix. Track progress: 15 violations โ 10 โ 5 โ 0.
Constraints (Parameters)
NEVER
- Modify
deptrac.yaml to allow violations
- Disable Deptrac checks
- Add suppression comments or ignore directives (
@SuppressWarnings, @psalm-suppress, @phpstan-ignore*, @infection-ignore*, phpcs:ignore)
- Create "wrapper" classes to hide dependencies
- Move an entire class to the wrong layer just to satisfy Deptrac
- Move classes into unrelated/random directories just to make Deptrac pass (for example, hiding
Generator classes under Factory)
- Brute-force Deptrac by reorganizing code around tool output instead of business responsibility
- Create ad-hoc directory/class types; use only well-known software patterns (e.g., Strategy, Factory, Provider, Resolver, CQRS Command/Handler)
- Use reflection or dynamic loading to bypass checks
ALWAYS
- Fix the code to match the architecture
- Keep the Domain layer pure (no framework imports)
- Use interfaces for cross-layer dependencies
- Move configuration to YAML/XML files
- Keep "Directory X contains ONLY class type X" semantics
- For new directories/classes, use explicit, well-known pattern names aligned with DDD/CQRS
- Verify fixes with the target mapped by
make.deptrac after each change
- Check that tests still pass after refactoring (target mapped by
make.tests)
Format (Output)
Expected Deptrac output:
Deptrac
Checking dependencies...
โ
No violations found
Expected CI output: the target mapped by make.ci passes.
Verification Checklist
After fixing violations:
Anti-Patterns to Avoid
โ DON'T Modify deptrac.yaml
paths:
- { collector: layer_domain, exclude: '.*Annotation.*' }
โ DON'T Create Wrapper Classes
class MyValidator {
private SymfonyValidator $validator;
}
โ DON'T Move Classes to the Wrong Layer
โ
DO Fix the Root Cause
- Extract validation to YAML
- Move mapping configuration to XML
- Use interfaces and dependency inversion
- Respect layer responsibilities
Quick Commands
vendor/bin/deptrac analyze --report-uncovered
architecture:
source_root: src
bounded_contexts: [User, OAuth]
shared_context: Shared
make:
deptrac: deptrac
tests: tests
ci: ci
quality:
deptrac_violations: 0
persistence:
mapper: doctrine-odm
Related Skills
Success Criteria Summary
- Zero Deptrac violations (
quality.deptrac_violations ceiling)
- Domain layer pure (no framework imports)
- All configuration externalized (YAML/XML)
- Proper use of Command Bus for cross-layer communication
- All tests passing (target mapped by
make.tests)
- CI pipeline green (target mapped by
make.ci)