name: architecture-compliance
description: Ensure code follows strict layer boundaries and dependency rules
version: 1.0.0
author: Saberloop Project
usage: |
Use this skill for architecture-related tasks:
- Validating new module placement against layer boundaries
- Checking dependency rule compliance
- Generating dependency graphs for visualization
- Identifying and fixing circular dependencies
- Architectural refactoring to improve layer separation
Examples:
"Validate new service architecture using the architecture-compliance skill"
"Check dependency rules for new component using the architecture-compliance skill"
"Fix architecture violations using the architecture-compliance skill"
Architecture Compliance Validation Skill
Overview
This skill ensures code follows Saberloop's strict layer boundaries and dependency rules as enforced by dependency-cruiser, maintaining clean architecture and preventing technical debt accumulation.
Architecture Layers
Layer Definitions
| Layer | Directory | Purpose | Allowed Dependencies |
|---|
| Views | src/views/ | UI components, Services, Core utilities, State | |
| Components | src/components/ | Core utilities, State | |
| Services | src/services/ | API layer, Database, Core utilities | |
| API | src/api/ | Core utilities | |
| Core | src/core/ | External libraries only | |
Dependency Flow
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Views │───▶│ Components │───▶│ Services │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ ▼
│ │ ┌─────────────────┐
│ │ │ API │
│ ▼ └─────────────────┘
│ ┌─────────────────┐ │
│ │ Services │◀───────────────┤
▼ └─────────────────┘ │
┌─────────────────┐ │ │
│ Core │◀───────────────────────┤ ▼
└─────────────────┘ │ ┌─────────────────┐
│ │ Core Utils │
│ └─────────────────┘
▼
┌─────────────────┐
│ External Libs │
└─────────────────┘
When to Use This Skill
Use this skill when ANY of these are true:
Architecture Rules
Enforced Rules (from .dependency-cruiser.cjs)
Immediate Enforcement (Errors)
| Rule | Description | Violation Example |
|---|
no-view-to-view | Views should not import other views (except BaseView) | import HomeView from './HomeView.js' |
views-should-not-import-db | Views must use services layer instead of direct db access | import { saveTopic } from '@/core/db.js' |
components-should-not-import-api | Components should be presentational, receive callbacks as props | import { apiCall } from '@/api/' |
api-should-not-import-db | API layer should receive credentials as parameters | import { getDb } from '@/core/db.js' |
Additional Best Practices
Import Patterns
Correct:
import { quizService } from '@/services/quiz-service.js';
import { generateQuiz } from '@/api/api.real.js';
import { openDB } from 'idb';
Incorrect:
import { generateQuiz } from '@/api/api.real.js';
import { otherService } from '@/services/other-service.js';
import { saveQuiz } from '@/core/db.js';
Layer Boundary Patterns
| Layer | Should Import | Should Not Import |
|---|
| Views | Services, Components, Core, State | API, Database, Other Views |
| Components | Core, State | Services, API, Database |
| Services | API, Core, Database | Views, Other Services |
| API | Core | Database, Services, Views |
| Core | External libraries only | Internal layers |
Compliance Validation Process
Step 1: Current State Analysis
npm run arch:test
npm run arch:graph
npm run arch:test | grep -i circular
npx depcruise src/views/QuizView.js --output-type dot
Step 2: Identify Violations
Common Violation Patterns
- View Importing API Directly
import { generateQuiz } from '@/api/api.real.js';
export default class QuizView extends BaseView {
async createQuiz() {
const quiz = await generateQuiz(topic);
}
}
- Component Making API Calls
import { fetchQuiz } from '@/api/api.real.js';
export function QuizComponent() {
const [quiz, setQuiz] = useState(null);
useEffect(() => {
fetchQuiz().then(setQuiz);
}, []);
}
- API Accessing Database
import { saveQuiz } from '@/core/db.js';
export async function generateQuiz(topic) {
const quiz = createQuizData(topic);
await saveQuiz(quiz);
return quiz;
}
Step 3: Fix Architecture Violations
Fix Pattern 1: Views → Services → API
import { generateQuiz } from '@/api/api.real.js';
export default class QuizView extends BaseView {
async createQuiz() {
const quiz = await generateQuiz(topic);
}
}
import { quizService } from '@/services/quiz-service.js';
export default class QuizView extends BaseView {
async createQuiz() {
const quiz = await quizService.generateQuiz(topic);
}
}
import { generateQuiz } from '@/api/api.real.js';
export const quizService = {
async generateQuiz(topic) {
return await generateQuiz(topic);
}
};
Fix Pattern 2: Components → Props/Callbacks
import { fetchQuiz } from '@/api/api.real.js';
export function QuizComponent({ topic }) {
const [quiz, setQuiz] = useState(null);
useEffect(() => {
fetchQuiz(topic).then(setQuiz);
}, [topic]);
}
export function QuizComponent({ topic, onQuizGenerated }) {
const [quiz, setQuiz] = useState(null);
useEffect(() => {
onQuizGenerated(topic).then(setQuiz);
}, [topic, onQuizGenerated]);
}
import { fetchQuiz } from '@/services/quiz-service.js';
export function ParentComponent() {
const handleQuizGenerated = async (topic) => {
return await fetchQuiz(topic);
};
return <QuizComponent topic="science" onQuizGenerated={handleQuizGenerated} />;
}
Fix Pattern 3: API → Parameter Passing
import { saveQuiz } from '@/core/db.js';
export async function generateQuiz(topic) {
const quiz = createQuizData(topic);
await saveQuiz(quiz);
return quiz;
}
export async function generateQuiz(topic, dbConnection = null) {
const quiz = createQuizData(topic);
if (dbConnection) {
await dbConnection.save(quiz);
}
return quiz;
}
import { getDatabase } from '@/core/db.js';
export const quizService = {
async generateQuiz(topic) {
const db = await getDatabase();
return await generateQuiz(topic, db);
}
};
Step 4: Validation and Testing
npm run arch:test
npx depcruise src/ --config .dependency-cruiser.cjs --output-type err
npm run arch:graph
dot -Tpng dependency-graph.dot -o architecture-fixed.png
Advanced Architecture Patterns
Service Layer Design
Proper Service Structure
import { generateQuiz } from '@/api/api.real.js';
import { saveQuiz, loadQuiz } from '@/core/db.js';
import { logger } from '@/utils/logger.js';
export const quizService = {
async createQuiz(topic, options = {}) {
logger.info('Creating quiz', { topic, options });
try {
const quiz = await generateQuiz(topic, options);
await saveQuiz(quiz);
return quiz;
} catch (error) {
logger.error('Quiz creation failed', { topic, error });
throw error;
}
},
async loadQuiz(id) {
const quiz = await loadQuiz(id);
logger.debug('Quiz loaded', { id, questions: quiz?.questions?.length });
return quiz;
},
validateQuiz(quiz) {
if (!quiz.questions || quiz.questions.length === ) {
();
}
(quiz.. > ) {
();
}
;
}
};
Dependency Injection Pattern
export class QuizService {
constructor(apiClient, database, logger) {
this.api = apiClient;
this.db = database;
this.logger = logger;
}
async createQuiz(topic) {
this.logger.info('Creating quiz', { topic });
const quiz = await this.api.generateQuiz(topic);
await this.db.saveQuiz(quiz);
return quiz;
}
}
export function createQuizService() {
return new QuizService(
apiReal,
getDatabase(),
logger
);
}
Component Design Patterns
Presentational Components
export function QuizCard({
quiz,
onQuizStart,
onQuizShare,
isDisabled = false
}) {
return (
<div className="quiz-card" data-testid="quiz-card">
<h3>{quiz.title}</h3>
<p>{quiz.description}</p>
<div className="quiz-actions">
<button
onClick={() => onQuizStart(quiz.id)}
disabled={isDisabled}
data-testid="start-quiz-btn"
>
Start Quiz
</button>
<button
onClick={() => onQuizShare(quiz.id)}
data-testid="share-quiz-btn"
>
Share
</button>
</div>
</div>
);
}
import { quizService } from '@/services/quiz-service.js';
export function QuizContainer({ quizId }) {
const [quiz, setQuiz] = useState();
( {
quizService.(quizId).(setQuiz);
}, [quizId]);
= () => {
};
= () => {
};
quiz ? (
) : ;
}
Dependency Analysis Tools
Using Dependency Cruiser
npx depcruise src/ --config .dependency-cruiser.cjs --output-type html
npx depcruise src/views/ --config .dependency-cruiser.cjs
npx depcruise src/views/QuizView.js --config .dependency-cruiser.cjs --output-type err
npx depcruise src/ --config .dependency-cruiser.cjs --focus "src/views"
Visualizing Architecture
npm run arch:graph
dot -Tpng dependency-graph.dot -o architecture.png
dot -Tsvg dependency-graph.dot -o architecture.svg
npx depcruise src/ --config .dependency-cruiser.cjs --output-type dot |
dot -Tpng -o focused-architecture.png
Circular Dependency Detection
npm run arch:test | grep -A5 -B5 "circular"
npx depcruise src/ --config .dependency-cruiser.cjs --output-type dot |
dot -Tpng -o cycles.png
// Create shared interface
// src/core/interfaces.js
export const EventEmitter = {
on(event, handler) { /* ... */ },
emit(event, data) { /* ... */ }
};
Migration Strategies
Legacy Code Migration
Step 1: Identify Violations
npx depcruise src/ --config .dependency-cruiser.cjs --output-type json > violations.json
jq '.violations[].rule' violations.json | sort | uniq -c
Step 2: Plan Refactoring
## Migration Plan: [Module Name]
### Current Issues
- [ ] View importing API directly: [files]
- [ ] Component making service calls: [files]
- [ ] Service coupling: [files]
### Refactoring Steps
1. Extract business logic to services
2. Create service interfaces
3. Update views to use services
4. Update components to be presentational
5. Add dependency injection where needed
Step 3: Incremental Migration
import { legacyAPICall } from './legacy.js';
import { newService } from '@/services/new-service.js';
export default class QuizView extends BaseView {
async createQuiz() {
if (this.isNewFlow) {
return await newService.createQuiz(this.topic);
}
return await legacyAPICall(this.topic);
}
}
Quality Metrics
Architecture Health Indicators
| Metric | Target | How to Measure |
|---|
| Dependency rule violations | 0 | npm run arch:test |
| Circular dependencies | 0 | Dependency cruiser output |
| Layer coupling | Low | Dependency graph analysis |
| Service layer coverage | 100% | All API calls through services |
| Component purity | High | No API calls in components |
Automated Checks
"arch:test": "depcruise src/ --config .dependency-cruiser.cjs --output-type err --max-depth 10",
"arch:validate": "npm run arch:test && echo '✅ Architecture compliance passed' || echo '❌ Architecture violations found'",
Integration with Other Skills
This skill integrates with:
- epic-hygiene-process - For architecture compliance fixes during hygiene
- testing-suite-management - For testing architecture fixes
- pwa-feature-development - For validating new feature architecture
- feature-flag-management - For architectural impact of flag changes
Version: 1.0.0
Last Updated: 2026-01-15
Compatible with: Saberloop v2.0.0+