modular-refactoring-pattern

Guides systematic file refactoring following @j0kz/mcp-agents modular architecture pattern to maintain files under 300 LOC through constants/, helpers/, utils/ extraction with proven results from 2...

$ Installer

git clone https://github.com/j0KZ/mcp-agents /tmp/mcp-agents && cp -r /tmp/mcp-agents/starter-kit/template/.claude/skills/modular-refactoring-pattern ~/.claude/skills/mcp-agents

// tip: Run this command in your terminal to install the skill


name: modular-refactoring-pattern description: "Guides systematic file refactoring following @j0kz/mcp-agents modular architecture pattern to maintain files under 300 LOC through constants/, helpers/, utils/ extraction with proven results from 2..."

Modular Refactoring Pattern for @j0kz/mcp-agents

Systematic approach to keeping files under 300 LOC through strategic extraction.

Philosophy: Modular Architecture

Core Principle: Keep files focused and maintainable through consistent extraction patterns.

Established 2025: 4 packages successfully refactored with proven results.

Quality Targets

File Size

  • Target: <300 LOC per file
  • Maximum: 500 LOC (hard limit)
  • Action: Extract when approaching 300 LOC

Complexity

  • Target: <50 per file
  • Warning: >70 per file
  • Action: Simplify or extract complex functions

Duplication

  • Target: 0% duplicate code blocks
  • Action: Extract shared code to helpers/utils

When to Refactor (Triggers)

Trigger 1: File Size Exceeds 300 LOC

# Check file sizes
wc -l packages/*/src/**/*.ts

# Example output:
  456 packages/refactor-assistant/src/mcp-server.ts
  395 packages/security-scanner/src/scanner.ts
  411 packages/db-schema/src/designer.ts

Action: Extract to modular structure

Trigger 2: Magic Numbers (5+)

// ❌ Magic numbers scattered throughout
if (complexity > 70) { /* ... */ }
if (lines > 1000) { /* ... */ }
if (imports > 50) { /* ... */ }
const threshold = 0.8;
const maxRetries = 3;

Action: Extract to constants/

Trigger 3: Complex Functions (30+ LOC)

// ❌ Complex calculation embedded
function analyzeCode(file) {
  // 45 lines of complex logic
  // Multiple nested loops
  // Complex conditionals
  // Mathematical calculations
}

Action: Extract to helpers/

Trigger 4: Duplicate Code Blocks

// ❌ Same pattern repeated 3+ times
function processFile1() {
  validateInput();
  parseContent();
  transformData();
}

function processFile2() {
  validateInput();  // Duplicate!
  parseContent();   // Duplicate!
  transformData();  // Duplicate!
}

Action: Extract to utils.ts

Trigger 5: Complexity Score >70

// ❌ High cyclomatic complexity
function handleRequest(request) {
  if (condition1) {
    if (condition2) {
      if (condition3) {
        // Many nested branches
        // Multiple return paths
        // Complex error handling
      }
    }
  }
  // Complexity: 89
}

Action: Simplify or extract sub-functions

Extraction Decision Tree

Is it a configuration value/threshold/pattern?
  YES → Extract to constants/

Is it a complex calculation (30+ LOC)?
  YES → Extract to helpers/

Is it used by multiple modules?
  YES → Extract to utils.ts

Is it cross-cutting concern (error handling, validation)?
  YES → Extract to utils.ts OR @j0kz/shared

Is it still >30 LOC after extraction?
  YES → Break into smaller functions

Standard Refactoring Pattern

Step 1: Create Modular Structure

# Inside package directory
mkdir -p src/{constants,helpers}

# Verify structure
tree src/
src/
├── mcp-server.ts
├── main-logic.ts
├── types.ts
├── constants/
├── helpers/
└── utils.ts

Step 2: Extract Constants

What to Extract:

  • Magic numbers
  • Thresholds and limits
  • Regex patterns
  • Configuration values
  • Error messages
  • Default options

Example: constants/analysis-thresholds.ts

/**
 * Complexity analysis thresholds
 */
export const COMPLEXITY_THRESHOLDS = {
  LOW: 20,
  MODERATE: 50,
  HIGH: 70,
  CRITICAL: 100,
} as const;

/**
 * File size limits (lines of code)
 */
export const SIZE_LIMITS = {
  TARGET: 300,
  WARNING: 400,
  MAXIMUM: 500,
} as const;

/**
 * Quality score weights
 */
export const QUALITY_WEIGHTS = {
  COMPLEXITY: 0.3,
  MAINTAINABILITY: 0.3,
  COVERAGE: 0.2,
  DUPLICATION: 0.2,
} as const;

Before (in main file):

if (complexity > 70) { /* ... */ }
if (lines > 300) { /* ... */ }
const score = complexity * 0.3 + maintainability * 0.3;

After:

import { COMPLEXITY_THRESHOLDS, SIZE_LIMITS, QUALITY_WEIGHTS } from './constants/analysis-thresholds.js';

if (complexity > COMPLEXITY_THRESHOLDS.HIGH) { /* ... */ }
if (lines > SIZE_LIMITS.TARGET) { /* ... */ }
const score = complexity * QUALITY_WEIGHTS.COMPLEXITY +
              maintainability * QUALITY_WEIGHTS.MAINTAINABILITY;

Benefits:

  • Single source of truth
  • Easy to tune thresholds
  • Self-documenting
  • Prevents inconsistencies

Step 3: Extract Helpers

What to Extract:

  • Complex calculations (30+ LOC)
  • Reusable business logic
  • Data transformations
  • Algorithm implementations

Example: helpers/complexity-calculator.ts

import { COMPLEXITY_THRESHOLDS } from '../constants/analysis-thresholds.js';

/**
 * Calculate cyclomatic complexity for a function
 *
 * @param ast - Abstract syntax tree node
 * @returns Complexity score
 */
export function calculateComplexity(ast: any): number {
  let complexity = 1; // Base complexity

  // Count decision points
  const decisionNodes = [
    'IfStatement',
    'SwitchCase',
    'ConditionalExpression',
    'LogicalExpression',
    'ForStatement',
    'WhileStatement',
    'CatchClause',
  ];

  function traverse(node: any) {
    if (!node) return;

    if (decisionNodes.includes(node.type)) {
      complexity++;
    }

    // Recursive traversal
    for (const key in node) {
      if (node[key] && typeof node[key] === 'object') {
        traverse(node[key]);
      }
    }
  }

  traverse(ast);
  return complexity;
}

/**
 * Categorize complexity level
 *
 * @param complexity - Complexity score
 * @returns Level category
 */
export function categorizeComplexity(complexity: number): string {
  if (complexity <= COMPLEXITY_THRESHOLDS.LOW) return 'low';
  if (complexity <= COMPLEXITY_THRESHOLDS.MODERATE) return 'moderate';
  if (complexity <= COMPLEXITY_THRESHOLDS.HIGH) return 'high';
  return 'critical';
}

Before (in main file - 45 LOC):

function analyzeFunction(ast) {
  // 45 lines of complexity calculation
  // Nested traversal logic
  // Multiple conditionals
  // Returns complexity score
}

After (in main file - 3 LOC):

import { calculateComplexity, categorizeComplexity } from './helpers/complexity-calculator.js';

function analyzeFunction(ast) {
  const complexity = calculateComplexity(ast);
  return categorizeComplexity(complexity);
}

Benefits:

  • Independently testable
  • Reusable across analyzers
  • Clear single responsibility
  • Easier to optimize

Step 4: Extract Utilities

What to Extract:

  • Cross-cutting concerns
  • Used by multiple modules
  • Error handling patterns
  • Validation logic
  • Shared transformations

Example: utils.ts

/**
 * Validate file path for security
 *
 * @param filePath - Path to validate
 * @throws Error if path is invalid or unsafe
 */
export function validateFilePath(filePath: string): void {
  if (!filePath) {
    throw new Error('File path is required');
  }

  // Prevent path traversal
  if (filePath.includes('..')) {
    throw new Error('Path traversal detected');
  }

  // Ensure supported extension
  const ext = filePath.split('.').pop();
  const supported = ['ts', 'js', 'tsx', 'jsx'];
  if (!ext || !supported.includes(ext)) {
    throw new Error(`Unsupported file type: ${ext}`);
  }
}

/**
 * Format error message with context
 *
 * @param operation - Operation that failed
 * @param error - Original error
 * @returns Formatted error message
 */
export function formatError(operation: string, error: Error): string {
  return `Failed to ${operation}: ${error.message}`;
}

/**
 * Truncate long strings for logging
 *
 * @param str - String to truncate
 * @param maxLength - Maximum length
 * @returns Truncated string
 */
export function truncate(str: string, maxLength: number = 100): string {
  if (str.length <= maxLength) return str;
  return str.substring(0, maxLength - 3) + '...';
}

Usage:

import { validateFilePath, formatError } from './utils.js';

try {
  validateFilePath(args.filePath);
  // Process file...
} catch (error) {
  return {
    success: false,
    errors: [formatError('process file', error)]
  };
}

Real-World Example: security-scanner

For a comprehensive 371-line example showing the complete refactoring of the security-scanner package from 395 LOC down to modular components:

cat .claude/skills/modular-refactoring-pattern/references/security-scanner-example.md

This example demonstrates:

  • Before: Monolithic 395 LOC file with complexity 57
  • After: Modular structure with no file >120 LOC
  • Extraction of constants, helpers, and utilities
  • Proven results with metrics improvements

Proven Results (2025 Refactoring)

4 Packages Successfully Refactored:

PackageBefore LOCAfter LOCReductionComplexity
security-scanner395209 (main)-47%57 → 28
db-schema411262 (main)-36%63 → 35
architecture-analyzer382287 (main)-25%48 → 31
refactor-assistant456407 (main)-11%72 → 44

Average Improvements:

  • -36% file size reduction
  • -42% complexity reduction
  • +122% maintainability improvement
  • -52% duplicate code

Refactoring Workflow

Complete Workflow

# 1. Analyze current state
npx @j0kz/smart-reviewer@latest review src/*.ts --metrics

# 2. Identify refactoring targets
wc -l src/*.ts | sort -rn

# 3. Apply refactoring pattern
npx @j0kz/refactor-assistant@latest refactor src/scanner.ts \
  --extract-constants \
  --extract-helpers \
  --target-size=300

# 4. Verify improvements
npx @j0kz/smart-reviewer@latest review src/*.ts --metrics

# 5. Run tests
npm test

# 6. Commit changes
git add -A
git commit -m "refactor: apply modular pattern to scanner

- Extract constants to constants/
- Extract helpers to helpers/
- Reduce main file from 395 to 209 LOC
- Improve complexity from 57 to 28"

Common Patterns to Extract

1. Configuration Objects

// Extract to constants/config.ts
export const DEFAULT_CONFIG = {
  maxFileSize: 100000,
  excludePatterns: ['node_modules', 'dist'],
  supportedExtensions: ['.ts', '.js', '.tsx', '.jsx'],
  // ...
};

2. Validation Functions

// Extract to helpers/validators.ts
export function isValidPath(path: string): boolean { /* ... */ }
export function isSupported(file: string): boolean { /* ... */ }
export function validateConfig(config: Config): void { /* ... */ }

3. Business Logic

// Extract to helpers/[domain].ts
export function calculateScore(metrics: Metrics): number { /* ... */ }
export function generateReport(results: Results): Report { /* ... */ }
export function analyzePatterns(code: string): Pattern[] { /* ... */ }

4. Error Handling

// Extract to utils.ts
export function wrapError(error: Error, context: string): Error { /* ... */ }
export function isRecoverableError(error: Error): boolean { /* ... */ }
export function formatErrorResponse(error: Error): Response { /* ... */ }

Best Practices

  1. Extract early: Don't wait until 400+ LOC
  2. Keep related code together: Group by domain
  3. Use barrel exports: Re-export from index.ts
  4. Document extractions: Add JSDoc comments
  5. Test extracted modules: Unit test helpers independently
  6. Avoid circular dependencies: Use dependency injection
  7. Follow naming conventions: constants/, helpers/, utils.ts
  8. Consider shared package: Move to @j0kz/shared if reusable
  9. Maintain backwards compatibility: Keep public APIs stable
  10. Review after refactoring: Use smart-reviewer to verify

Anti-Patterns to Avoid

❌ Over-extraction: Don't create 10 LOC files ❌ Premature extraction: Wait for actual need ❌ Deep nesting: Avoid src/helpers/utils/common/shared/ ❌ Generic names: Use domain-specific names ❌ Mixed concerns: Keep single responsibility ❌ Circular imports: Plan module dependencies ❌ Lost context: Keep related logic nearby


Verification Checklist

  • All files <300 LOC (max 500)
  • Complexity <50 per file (max 70)
  • No duplicate code blocks
  • Constants extracted and documented
  • Complex functions extracted to helpers
  • Shared utilities in utils.ts
  • Tests still passing
  • Import paths correct (.js extension)
  • No circular dependencies
  • Improved metrics from smart-reviewer

Success Metric: If smart-reviewer scores improve and tests pass, refactoring succeeded!

Repository

j0KZ
j0KZ
Author
j0KZ/mcp-agents/starter-kit/template/.claude/skills/modular-refactoring-pattern
0
Stars
0
Forks
Updated2d ago
Added1w ago