aeo-architecture
AEO Architecture
Purpose: Analyze and protect code architecture. Detects circular dependencies, layer violations, and manages ADRs (Architecture Decision Records).
Configuration
Define architecture layers at $PAI_DIR/USER/aeo-layers.yaml:
layers:
- name: "presentation"
path: "src/components/"
may_import: ["domain", "application"]
may_not_import: ["infrastructure", "presentation"]
- name: "domain"
path: "src/domain/"
may_import: []
may_not_import: ["presentation", "application", "infrastructure"]
- name: "application"
path: "src/services/"
may_import: ["domain"]
may_not_import: ["presentation", "infrastructure"]
- name: "infrastructure"
path: "src/infrastructure/"
may_import: ["domain"]
may_not_import: ["presentation", "application"]
Default: No layers defined - only detect circular dependencies
When to Analyze
Run architecture analysis:
- After feature implementation
- Before git commit (via aeo-qa-agent)
- When circular dependency suspected
- During code review
Detection Types
1. Circular Dependencies
Detection:
# Build dependency graph
find src -name "*.js" -o -name "*.ts" | while read file; do
grep -h "^import" "$file" | \
sed "s/.*from ['\"]\(.*\)['\"].*/\1/" | \
while read import; do
echo "$file -> $import"
done
done > /tmp/deps.txt
# Detect cycles
# (Use graph algorithm or madge)
npx madge --circular --extensions ts,tsx,js,jsx src/
Example Circular Dependency:
❌ CIRCULAR DEPENDENCY DETECTED
Cycle:
src/services/UserService.js
→ src/repositories/UserRepository.js
→ src/models/User.js
→ src/services/UserService.js
(back to start - cycle!)
Why this matters:
• Creates tight coupling
• Makes code impossible to test in isolation
• Can cause runtime errors during module loading
• Violates clean architecture principles
Resolution Options:
1. Extract shared code - Create new module for shared functionality
2. Invert dependency - Use dependency injection
3. Introduce interface - Abstract the dependency
Recommended: Option 1 - Extract shared functionality
Your choice (1-3):
2. Layer Violations
Detection:
# Check if presentation layer imports infrastructure
grep -r "import.*from.*infrastructure" src/components/
# Check if domain imports presentation
grep -r "import.*from.*components" src/domain/
Example Layer Violation:
⚠️ LAYER VIOLATION DETECTED
Violation: Presentation layer importing Infrastructure
File: src/components/UserList.tsx:5
Import: import db from '../infrastructure/database.js'
Why this violates architecture:
• Presentation should only import from Application/Domain
• Direct database access in component creates tight coupling
• Makes testing difficult (need real database)
• Violates separation of concerns
Correct Pattern:
❌ src/components/UserList.tsx
import db from '../infrastructure/database.js'
✅ src/components/UserList.tsx
import { getUsers } from '../services/UserService.js'
✅ src/services/UserService.js
import db from '../infrastructure/database.js'
export function getUsers() {
return db.query('SELECT * FROM users')
}
Action: Fix violation before commit
3. Breaking Encapsulation
Detection:
# Check for private field access from outside class
grep -r "#[a-zA-Z]*\s*=" src/ | grep -v "this\.#"
Example Encapsulation Breaking:
❌ ENCAPSULATION VIOLATION
File: src/utils/userHelper.js:42
Issue: Accessing private field #passwordHash from outside
Code:
```javascript
class User {
#passwordHash // Private field
}
// In another file:
user.#passwordHash = 'new' // ❌ VIOLATION
Why this violates encapsulation: • Private fields are implementation details • Bypasses validation and invariants • Makes code fragile to internal changes • Breaks abstraction boundary
Correct Approach:
class User {
#passwordHash
setPassword(newPassword) {
// Validate and hash
this.#passwordHash = hash(newPassword)
}
getPassword() {
return this.#passwordHash
}
}
// Use public API:
user.setPassword('new')
Action: Fix encapsulation violation
---
## Architecture Decision Records (ADRs)
### ADR Format
Create ADRs at `$PAI_DIR/USER/ADRs/`:
```markdown
# ADR-001: Use JWT for Authentication
## Status
Accepted
## Context
We need authentication for our API. Options considered:
- Session-based auth
- JWT tokens
- API keys
## Decision
Use JWT tokens because:
1. Stateless - scales horizontally
2. Standard - well-supported libraries
3. Flexible - supports multiple auth providers
## Consequences
- Positive: No session storage needed
- Positive: Works well with microservices
- Negative: Token revocation requires blacklist
- Negative: Larger payload than session IDs
## Implementation
- Use jose library for JWT handling
- Store refresh tokens in Redis
- Set access token expiry to 15 minutes
## Date
2026-01-22
Recording ADRs
When making significant architectural decisions:
-
Create ADR file:
# Find next ADR number next_num=$(ls ~/.claude/USER/ADRs/ | grep ADR- | wc -l) adr_file=~/.claude/USER/ADRs/ADR-$(printf "%03d" $((next_num + 1)))-${title}.md -
Use template:
cat > "$adr_file" << 'EOF' # ADR-XXX: [Title] ## Status Proposed ## Context [Problem statement and context] ## Decision [The decision] ## Consequences - Positive: [Benefits] - Negative: [Drawbacks] ## Date $(date -u +%Y-%m-%d) EOF -
Reference ADRs in code:
// See ADR-001: Use JWT for Authentication import { generateToken } from './auth/jwt.js'
Architecture Analysis Commands
Check Circular Dependencies
# Using madge
npx madge --circular --extensions ts,tsx src/
# Output:
# ✅ No circular dependencies found
# or
# ❌ Circular dependencies found:
# src/a.js → src/b.js → src/a.js
Check Layer Violations
# Check layer compliance
check_layers() {
local layer=$1
local path=$2
local forbidden=$3
echo "Checking $layer layer..."
for forbidden_import in $forbidden; do
violations=$(grep -r "import.*from.*$forbidden_import" "$path" 2>/dev/null)
if [ -n "$violations" ]; then
echo "❌ $layer importing from $forbidden_import:"
echo "$violations"
fi
done
}
# Run checks
check_layers "presentation" "src/components" "infrastructure"
check_layers "domain" "src/domain" "presentation,application"
Generate Dependency Graph
# Visualize dependencies
npx madge --image deps.svg --extensions ts,tsx src/
# Output:
# Generated deps.svg
Integration
With aeo-qa-agent:
// In QA review, Step 4: Check Architecture
if (architecture_violations_found) {
invoke_skill('aeo-architecture', {
type: 'violation',
violations: violations
});
}
With aeo-escalation:
// When architecture violation detected
invoke_skill('aeo-escalation', {
type: 'architecture_violation',
issue: 'circular_dependency',
options: [
'Extract shared code',
'Refactor dependencies',
'Defer to architect'
],
recommended: 1
});
Best Practices
DO:
- Define clear layer boundaries upfront
- Use dependency injection to break cycles
- Record significant architectural decisions
- Review architecture before committing
- Generate dependency graphs periodically
DON'T:
- Allow circular dependencies to persist
- Let layers bleed into each other Skip architecture review for "quick fixes"
- Break encapsapsulation for convenience
- Forget to document architectural decisions
Example Session
AEO-QA: [Step 4: Check Architecture]
AEO-Architecture: Analyzing codebase...
Detected Issues:
❌ Circular dependency: UserService → UserRepository → User → UserService
❌ Layer violation: UserList.tsx importing database.js
✅ Encapsulation: All private fields properly protected
[Invokes aeo-escalation for circular dependency]
Escalation: ⚠️ ARCHITECTURE VIOLATION DETECTED
Issue: Circular dependency between modules
Circular Path:
UserService.js imports UserRepository.js
UserRepository.js imports User.js
User.js imports UserService.js
Options:
1. Extract shared code - Create UserValueObject module
2. Refactor dependencies - Introduce UserInterface
3. Defer to architect - Let architect decide
Recommended: Option 1 - Extract shared code
Developer: 1
AEO-Architecture: Creating UserValueObject module...
[Refactoring...]
[Updating imports...]
[Running tests...]
✅ Circular dependency resolved
[Recording ADR-003: Extract User Value Object]
AEO-QA: Architecture check passed
AEO-Core: Ready to commit
ADR Management
List ADRs
ls -1 ~/.claude/USER/ADRs/ | sort
# ADR-001-use-jwt-for-authentication.md
# ADR-002-adopt-event-sourcing.md
# ADR-003-extract-user-value-object.md
Find ADR by Topic
grep -r "JWT" ~/.claude/USER/ADRs/
# ADR-001-use-jwt-for-authentication.md:## Context
# ADR-001-use-jwt-for-authentication.md:# - Use JWT tokens
Propose New ADR
# Create proposed ADR
cat > ~/.claude/USER/ADRs/ADR-004-adopt-graphql.md << 'EOF'
# ADR-004: Adopt GraphQL
## Status
Proposed
## Context
Current REST API has issues:
- Over-fetching data
- Multiple round trips for related data
- Versioning complexity
## Decision
Adopt GraphQL for...
## Consequences
- Positive: Single query for related data
- Positive: Strongly typed schema
- Negative: Learning curve
- Negative: Complexity in caching
## Date
$(date -u +%Y-%m-%d)
EOF
# Then discuss with team before marking as "Accepted"
Architecture Health Score
Calculate architecture health:
# 100 points total
score=100
# Subtract for issues
circular_deps=$(npx madge --circular src/ 2>/dev/null | grep "Found" | wc -l)
score=$((score - circular_deps * 20))
layer_violations=$(grep -r "import.*infrastructure" src/components/ 2>/dev/null | wc -l)
score=$((score - layer_violations * 10))
echo "Architecture Health: $score/100"
Interpretation:
- 90-100: Excellent architecture
- 70-89: Good, minor issues
- 50-69: Needs improvement
- < 50: Critical architectural problems
Disable Architecture Checks
To disable for a project, delete $PAI_DIR/USER/aeo-layers.yaml:
rm ~/.claude/USER/aeo-layers.yaml
# AEO will skip layer checks, still detect circular deps