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:
Package Before LOC After LOC Reduction Complexity
security-scanner 395 209 (main) -47% 57 → 28
db-schema 411 262 (main) -36% 63 → 35
architecture-analyzer 382 287 (main) -25% 48 → 31
refactor-assistant 456 407 (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
- Configuration Objects
// Extract to constants/config.ts export const DEFAULT_CONFIG = { maxFileSize: 100000, excludePatterns: ['node_modules', 'dist'], supportedExtensions: ['.ts', '.js', '.tsx', '.jsx'], // ... };
- 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 { / ... */ }
- 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[] { / ... */ }
- 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
-
Extract early: Don't wait until 400+ LOC
-
Keep related code together: Group by domain
-
Use barrel exports: Re-export from index.ts
-
Document extractions: Add JSDoc comments
-
Test extracted modules: Unit test helpers independently
-
Avoid circular dependencies: Use dependency injection
-
Follow naming conventions: constants/, helpers/, utils.ts
-
Consider shared package: Move to @j0kz/shared if reusable
-
Maintain backwards compatibility: Keep public APIs stable
-
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!