AgentSkillsCN

architecture-compliance

确保代码严格遵循各层边界与依赖规则。

SKILL.md
--- frontmatter
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

code
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│     Views      │───▶│   Components   │───▶│    Services     │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                        │                        │
         │                        │                        ▼
         │                        │                ┌─────────────────┐
         │                        │                │       API       │
         │                        ▼                └─────────────────┘
         │                ┌─────────────────┐                │
         │                │   Services     │◀───────────────┤
         ▼                └─────────────────┘                │
┌─────────────────┐                        │                │
│    Core       │◀───────────────────────┤                ▼
└─────────────────┘                        │        ┌─────────────────┐
                                          │        │   Core Utils    │
                                          │        └─────────────────┘
                                          ▼
                                 ┌─────────────────┐
                                 │ External Libs  │
                                 └─────────────────┘

When to Use This Skill

Use this skill when ANY of these are true:

  • Adding new modules or files
  • Refactoring existing code
  • Moving code between layers
  • Adding new dependencies
  • Architecture violations detected
  • Planning major refactoring

Architecture Rules

Enforced Rules (from .dependency-cruiser.cjs)

Immediate Enforcement (Errors)

RuleDescriptionViolation Example
no-view-to-viewViews should not import other views (except BaseView)import HomeView from './HomeView.js'
views-should-not-import-dbViews must use services layer instead of direct db accessimport { saveTopic } from '@/core/db.js'
components-should-not-import-apiComponents should be presentational, receive callbacks as propsimport { apiCall } from '@/api/'
api-should-not-import-dbAPI layer should receive credentials as parametersimport { getDb } from '@/core/db.js'

Additional Best Practices

Import Patterns

Correct:

javascript
// Views using services
import { quizService } from '@/services/quiz-service.js';

// Services using API
import { generateQuiz } from '@/api/api.real.js';

// Core utilities only using external libs
import { openDB } from 'idb';

Incorrect:

javascript
// Views importing API (skip service layer)
import { generateQuiz } from '@/api/api.real.js';

// Services importing other services directly
import { otherService } from '@/services/other-service.js';

// API accessing database directly
import { saveQuiz } from '@/core/db.js';

Layer Boundary Patterns

LayerShould ImportShould Not Import
ViewsServices, Components, Core, StateAPI, Database, Other Views
ComponentsCore, StateServices, API, Database
ServicesAPI, Core, DatabaseViews, Other Services
APICoreDatabase, Services, Views
CoreExternal libraries onlyInternal layers

Compliance Validation Process

Step 1: Current State Analysis

bash
# Run architecture validation
npm run arch:test

# Generate dependency graph
npm run arch:graph

# Check for circular dependencies
npm run arch:test | grep -i circular

# Analyze specific file
npx depcruise src/views/QuizView.js --output-type dot

Step 2: Identify Violations

Common Violation Patterns

  1. View Importing API Directly
javascript
// VIOLATION
import { generateQuiz } from '@/api/api.real.js';

export default class QuizView extends BaseView {
  async createQuiz() {
    const quiz = await generateQuiz(topic); // Direct API access
  }
}
  1. Component Making API Calls
javascript
// VIOLATION
import { fetchQuiz } from '@/api/api.real.js';

export function QuizComponent() {
  const [quiz, setQuiz] = useState(null);
  
  useEffect(() => {
    fetchQuiz().then(setQuiz); // API call in component
  }, []);
}
  1. API Accessing Database
javascript
// VIOLATION
import { saveQuiz } from '@/core/db.js';

export async function generateQuiz(topic) {
  const quiz = createQuizData(topic);
  await saveQuiz(quiz); // Direct DB access
  return quiz;
}

Step 3: Fix Architecture Violations

Fix Pattern 1: Views → Services → API

javascript
// BEFORE (Violation)
import { generateQuiz } from '@/api/api.real.js';

export default class QuizView extends BaseView {
  async createQuiz() {
    const quiz = await generateQuiz(topic);
  }
}

// AFTER (Compliant)
import { quizService } from '@/services/quiz-service.js';

export default class QuizView extends BaseView {
  async createQuiz() {
    const quiz = await quizService.generateQuiz(topic);
  }
}

// In quiz-service.js
import { generateQuiz } from '@/api/api.real.js';

export const quizService = {
  async generateQuiz(topic) {
    return await generateQuiz(topic);
  }
};

Fix Pattern 2: Components → Props/Callbacks

javascript
// BEFORE (Violation)
import { fetchQuiz } from '@/api/api.real.js';

export function QuizComponent({ topic }) {
  const [quiz, setQuiz] = useState(null);
  
  useEffect(() => {
    fetchQuiz(topic).then(setQuiz);
  }, [topic]);
}

// AFTER (Compliant)
export function QuizComponent({ topic, onQuizGenerated }) {
  const [quiz, setQuiz] = useState(null);
  
  useEffect(() => {
    onQuizGenerated(topic).then(setQuiz);
  }, [topic, onQuizGenerated]);
}

// Parent provides API access
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

javascript
// BEFORE (Violation)
import { saveQuiz } from '@/core/db.js';

export async function generateQuiz(topic) {
  const quiz = createQuizData(topic);
  await saveQuiz(quiz); // Direct DB access
  return quiz;
}

// AFTER (Compliant)
export async function generateQuiz(topic, dbConnection = null) {
  const quiz = createQuizData(topic);
  
  // Accept DB connection as parameter
  if (dbConnection) {
    await dbConnection.save(quiz);
  }
  
  return quiz;
}

// Service layer provides DB connection
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

bash
# Re-run architecture validation
npm run arch:test

# Should show no violations
# ✅ No dependency rule violations found

# Check specific patterns
npx depcruise src/ --config .dependency-cruiser.cjs --output-type err

# Generate updated graph
npm run arch:graph
dot -Tpng dependency-graph.dot -o architecture-fixed.png

Advanced Architecture Patterns

Service Layer Design

Proper Service Structure

javascript
// src/services/quiz-service.js
import { generateQuiz } from '@/api/api.real.js';
import { saveQuiz, loadQuiz } from '@/core/db.js';
import { logger } from '@/utils/logger.js';

export const quizService = {
  // High-level operations
  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;
  },

  // Business logic validation
  validateQuiz(quiz) {
    if (!quiz.questions || quiz.questions.length === 0) {
      throw new Error('Quiz must have at least one question');
    }
    if (quiz.questions.length > 20) {
      throw new Error('Quiz cannot have more than 20 questions');
    }
    return true;
  }
};

Dependency Injection Pattern

javascript
// Allow services to receive dependencies
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;
  }
}

// Factory for creating services
export function createQuizService() {
  return new QuizService(
    apiReal,
    getDatabase(),
    logger
  );
}

Component Design Patterns

Presentational Components

javascript
// Only handle UI, receive data via props
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>
  );
}

// Container component handles logic
import { quizService } from '@/services/quiz-service.js';

export function QuizContainer({ quizId }) {
  const [quiz, setQuiz] = useState(null);
  
  useEffect(() => {
    quizService.loadQuiz(quizId).then(setQuiz);
  }, [quizId]);

  const handleStart = () => {
    // Navigate to quiz
  };

  const handleShare = () => {
    // Share quiz
  };

  return quiz ? (
    <QuizCard 
      quiz={quiz}
      onQuizStart={handleStart}
      onQuizShare={handleShare}
    />
  ) : null;
}

Dependency Analysis Tools

Using Dependency Cruiser

bash
# Full analysis
npx depcruise src/ --config .dependency-cruiser.cjs --output-type html

# Specific directory
npx depcruise src/views/ --config .dependency-cruiser.cjs

# Specific file with details
npx depcruise src/views/QuizView.js --config .dependency-cruiser.cjs --output-type err

# Custom focus
npx depcruise src/ --config .dependency-cruiser.cjs --focus "src/views"

Visualizing Architecture

bash
# Generate DOT graph
npm run arch:graph

# Convert to PNG (requires graphviz)
dot -Tpng dependency-graph.dot -o architecture.png

# Convert to SVG for web
dot -Tsvg dependency-graph.dot -o architecture.svg

# Focus on specific module
npx depcruise src/ --config .dependency-cruiser.cjs --output-type dot | 
  dot -Tpng -o focused-architecture.png

Circular Dependency Detection

bash
# Find circular dependencies
npm run arch:test | grep -A5 -B5 "circular"

# Visualize cycles
npx depcruise src/ --config .dependency-cruiser.cjs --output-type dot | 
  dot -Tpng -o cycles.png

# Fix by extracting common interface
// 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

bash
# Find all files with violations
npx depcruise src/ --config .dependency-cruiser.cjs --output-type json > violations.json

# Analyze patterns
jq '.violations[].rule' violations.json | sort | uniq -c

Step 2: Plan Refactoring

markdown
## 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

javascript
// Phase 1: Add service layer (keep old code)
import { legacyAPICall } from './legacy.js';
import { newService } from '@/services/new-service.js';

export default class QuizView extends BaseView {
  async createQuiz() {
    // Use new service for new features
    if (this.isNewFlow) {
      return await newService.createQuiz(this.topic);
    }
    
    // Keep legacy for old flow
    return await legacyAPICall(this.topic);
  }
}

// Phase 2: Migrate all callers
// Phase 3: Remove legacy code

Quality Metrics

Architecture Health Indicators

MetricTargetHow to Measure
Dependency rule violations0npm run arch:test
Circular dependencies0Dependency cruiser output
Layer couplingLowDependency graph analysis
Service layer coverage100%All API calls through services
Component purityHighNo API calls in components

Automated Checks

javascript
// In CI/CD pipeline
"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+