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:
| 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
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
- 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!
Repository
