Refactor File Skill
A comprehensive skill for refactoring TypeScript files in this stock-prediction-ui repository with strict behavior preservation, SOLID principles, performance-first design, and feature-folder organization.
When to Use This Skill
- •User asks to "refactor a file", "clean up this file", "improve code structure"
- •File exceeds 150 lines (big file threshold)
- •Function exceeds 40 lines (big function threshold)
- •Nesting depth > 3 levels
- •Code has duplicate logic, magic strings/numbers, or unclear naming
- •User requests to "extract helpers", "split this up", or "reorganize"
Prime Directive
⚠️ STRICTLY BEHAVIOR-PRESERVING REFACTOR ONLY ⚠️
- •No changes to outputs, side-effects, timing/ordering (where it matters), error behavior, or defaults
- •If something looks like a bug:
- •Do NOT "fix" it silently
- •Prove it with a test first
- •Only change behavior if you explicitly choose to (and document why)
Core Principles
1) SOLID + Practical TypeScript
- •SRP (Single Responsibility): Each function/module should have one clear job. If it has multiple flows, split into helpers.
- •OCP (Open/Closed): Prefer extending via new helpers/strategies/handlers rather than rewriting large blocks.
- •LSP (Liskov Substitution): If types have subtypes/unions, refactor must keep substitutability (no hidden assumptions).
- •ISP (Interface Segregation): Keep interfaces small; don't force callers to provide fields they don't need.
- •DIP (Dependency Inversion): High-level logic depends on abstractions (types/interfaces), not concrete implementations.
2) Public API + Renames Policy
- •Renaming exported/public APIs is allowed and encouraged when it improves clarity, even if it requires updating usages across the repo.
- •Example: if
abc()is vague and there's alsoabcRound(), rename to something explicit (e.g.,roundToAbcRule()), and update all call sites.
- •Example: if
- •Never leave the repo half-migrated. Any rename must update:
- •All imports/usages
- •Related types
- •Tests
- •Docs/comments where relevant
3) "Big Refactor" Mindset
- •Prefer one coherent, root-level refactor over small micro changes.
- •Follow the Boy Scout Rule: if you're already in the file, clean related issues in that file too.
- •Still keep changes structured and reviewable:
- •Group by logical steps (helpers extraction, naming pass, folder split, tests update, etc.)
4) File and Function Size Rules (Hard Thresholds)
- •File is "big" if > 150 lines → refactor toward a clearer structure and likely a dedicated folder
- •Function is "big" if > 40 lines → split into helpers
- •Nesting depth max ~3 where possible → use early returns, extract helpers, or use strategy maps
5) Folder Structure Rules (Feature-Folder First)
Prefer feature-folder organization. If a file grows big, it usually deserves its own folder:
featureX/
├── FeatureX.ts # Main entry point
├── helpers/ # or utils/
│ ├── someHelper.ts
│ └── anotherHelper.ts
├── constants.ts # Feature-specific constants
├── types.ts # Feature-specific types (if needed)
└── __tests__/ # Tests next to relevant sources
└── FeatureX.test.ts
Avoid tons of files in one folder. If a folder starts getting crowded, create subfolders by category.
Helpers extraction rule:
- •Extract private helpers into a local
helpers/orutils/folder for that feature when it improves readability. - •If a helper becomes reused across multiple files/features, promote it upward to a more shared location.
6) Single Source of Truth Rules
- •No magic strings/numbers. All repeated or meaningful literals go into constants.
- •Prefer
constants.tsper domain/feature. - •If a constant becomes shared and isn't domain-specific, promote it up (shared constants area).
7) Utility Extraction Rules
- •Create shared utilities only if used at least twice.
- •If used once:
- •Keep it local (private helper or feature-local util)
- •If used twice+ across places:
- •Extract and share (and remove duplication)
8) Naming Rules (Enforced)
- •camelCase: variables + functions
- •PascalCase: types + interfaces + classes
- •UPPER_SNAKE_CASE: constants
- •No single-letter names (
i,n,x) except very tight scopes where readability is still obvious (prefer real names anyway). - •Boolean naming:
- •Prefer
isLoading,hasAccess,shouldRetry,canExecute - •Avoid vague suffixes like
Flag,Val,Dataif it hides meaning
- •Prefer
- •Function naming should describe what it does + where it does it when relevant:
- •Prefer
fetchUserFromApi,loadUserFromCache,parseXFromY - •Avoid generic
handle,doThing,processunless paired with a meaningful noun
- •Prefer
9) "Readable Like Pseudo-Code" Structure
Large functions should read like high-level steps:
- •Validate/guard
- •Derive inputs
- •Compute/transform
- •Call side-effects
- •Return result
- •Put details into helpers so the main flow is easy to scan.
- •Prefer early returns to reduce nesting.
10) TypeScript Typing Rules
- •TypeScript only (this is a TS-only repo).
- •Avoid
any. Prefer:- •Precise types
- •
unknown+ narrowing - •Generics where useful
- •Don't weaken types as a "refactor shortcut."
- •Keep types aligned with runtime behavior (no lying casts unless absolutely unavoidable, and then explain why).
11) Performance is a Top Priority
This is a performance-critical genetic algorithm application. Performance comes first.
- •Prefer the more performant approach when choices are equivalent:
- •Lookup maps vs long switch/if chains when it reduces branching
- •Avoid unnecessary allocations / copies in hot paths
- •Avoid extra abstraction layers that add overhead in critical code
- •Don't add logging or heavy debug work in runtime hot paths.
- •Prefer efficient libraries/patterns already in the codebase:
- •
@tanstack/react-queryhooks for data fetching - •
date-fnsfor date handling (unless a clearly more performant approach is needed) - •Typed arrays (
Float64Array,Uint8Array, etc.) for hot-path numerical operations
- •
12) Comments & Docs Rules
- •Prefer code clarity over comments, but keep:
- •Short "why" comments where intent isn't obvious
- •Use simple language in comments.
- •Use TSDoc for exported/public utilities when it improves usage clarity.
- •In complex areas, longer comments are OK (still simple and direct).
13) Error Handling Rules
- •Preserve existing error semantics.
- •Allowed patterns (choose what fits the current code style/context):
- •Throwing errors
- •Returning
Result-style objects
- •Don't convert between "throw" and "Result" unless the surrounding code already uses that pattern (or you refactor the whole flow consistently without behavior changes).
14) Logging Rules
- •Preserve existing logging behavior.
- •Avoid adding new logs by default (performance concern + consistency).
- •If you suspect logs are needed, treat it as a separate decision (not automatic in refactor).
15) Imports and Exports Rules
- •
index.tsbarrels are allowed at folder boundaries (not everywhere). - •Avoid creating exports that aren't used.
- •Delete unused files/unused code during refactor (no "unfinished" leftovers).
- •Import order (if not auto-sorted by ESLint):
- •External libraries
- •Internal absolute imports (e.g.,
@shared) - •Relative imports (parent → sibling → child)
Repository-Specific Context
Project Structure
src/ ├── algorithm/ # GA core logic (performance-critical) │ ├── indicators/ # Technical indicator genes │ ├── dna/ # DNA tree logic, crossover, pruning │ ├── population/ # Population management │ └── geneLibrary/ # Gene library and injection ├── simulation/ # Market simulation engines ├── components/ # React UI components ├── hooks/ # React hooks (@tanstack/react-query) ├── utils/ # Shared utilities ├── types/ # Shared type definitions └── workers/ # Web Workers for GA execution e2e/ # Playwright end-to-end tests scripts/ # Build/perf testing scripts server/ # Backend (separate Node.js server)
Testing Infrastructure
Unit Tests (Vitest):
- •Run with:
npm test(runs all src tests) - •Test files:
**/__tests__/*.test.ts(co-located with source) - •Configuration: vite.config.ts
- •Performance tests:
npm run perf:fitness,npm run perf:ga,npm run perf:ga:heavy - •Performance test config: vitest.perf.config.ts
E2E Tests (Playwright):
- •Run with:
npm run e2e(via scripts/run-e2e.mjs) - •Smoke tests:
npm run e2e:smoke - •Test files:
e2e/*.spec.ts - •Configuration: playwright.config.ts
Test Requirements:
- •All tests must pass before refactor is "done"
- •Add coverage if the refactored area lacks tests (at least minimal but meaningful)
- •Use
describe,it,expectfromvitest - •Mock external dependencies where appropriate
Linting & Type Checking
- •ESLint:
npm run lint- •Configuration: eslint.config.js
- •Rules:
@typescript-eslint/no-explicit-anyiswarn(used sparingly in charts/tests) - •
no-emptywithallowEmptyCatch: true(common for optional operations)
- •TypeScript:
tsc -b(build-time type check)- •Configuration: tsconfig.json, tsconfig.app.json, tsconfig.node.json
Build & Dev Commands
- •Dev:
npm run dev(Vite dev server on port 45678) - •Dev with server:
npm run dev:all(concurrent frontend + backend) - •Build:
npm run build(TypeScript compile + Vite build) - •Preview:
npm run preview
Key Dependencies & Patterns
- •React 19 with hooks (
useState,useEffect, custom hooks) - •@tanstack/react-query for data fetching (replaces manual fetch state management)
- •date-fns for date manipulation
- •Recharts for visualization
- •lucide-react for icons
- •Web Workers for GA computation (see
src/workers/)
Common Refactor Scenarios in This Repo
- •
Algorithm files (
src/algorithm/):- •Performance-critical hot paths
- •Heavy use of typed arrays
- •Genes, DNA, population management
- •Refactor for clarity WITHOUT losing performance
- •
Simulation files (
src/simulation/):- •Market engines, data processing
- •Heavy numerical computation
- •Prefer typed arrays for data
- •
Component files (
src/components/):- •React components with hooks
- •Extract custom hooks for complex logic
- •Prefer
@tanstack/react-queryfor data fetching - •Co-locate types and helpers in component folders
- •
Utility files (
src/utils/):- •Shared helpers across features
- •Only promote here if used 2+ times
- •Keep focused and testable
Step-by-Step Refactoring Workflow
Step 1: Analyze the File
- •Read the full file to understand:
- •Purpose and responsibilities
- •Dependencies (imports/exports)
- •Current structure
- •Tests (if any)
- •Identify issues:
- •File size (> 150 lines?)
- •Function size (> 40 lines?)
- •Nesting depth (> 3 levels?)
- •Magic strings/numbers
- •Unclear naming
- •Code duplication
- •Missing tests
- •Check for usages across the repo:
- •Use
list_code_usagesto find all references - •Plan rename/restructure impact
- •Use
Step 2: Plan the Refactor
Create a todo list with manage_todo_list for complex refactors:
Example plan:
- •Extract constants (magic strings/numbers →
constants.ts) - •Extract helpers (big functions → helper functions or
helpers/folder) - •Rename unclear variables/functions (update all usages)
- •Simplify control flow (early returns, reduce nesting)
- •Update/add types (improve type safety)
- •Update tests (ensure behavior preserved)
- •Update imports/exports (if folder structure changed)
- •Run all tests + lint + typecheck
Step 3: Execute the Refactor
Use multi_replace_string_in_file for multiple edits to maximize efficiency.
For each change:
- •Ensure exact string matching (3-5 lines of context before/after)
- •Preserve behavior exactly
- •Update related code (types, tests, usages) atomically when possible
Recommended order:
- •Extract constants first (establishes single source of truth)
- •Extract helpers second (reduces complexity, improves reusability)
- •Rename for clarity third (improves readability)
- •Simplify control flow fourth (reduces nesting, improves readability)
- •Update types/tests last (ensures everything still works)
Step 4: Update Tests
- •Run existing tests:
npm test(or specific test file) - •Add missing coverage if the refactored area lacks tests
- •Update test assertions if structure changed (but behavior preserved)
- •Test file patterns:
typescript
import { describe, it, expect, beforeEach } from 'vitest'; describe('FeatureName', () => { it('should do something specific', () => { // Arrange const input = ...; // Act const result = functionUnderTest(input); // Assert expect(result).toBe(expected); }); });
Step 5: Verify Quality
Run the full quality checklist:
# 1. Type check npm run build # or tsc -b # 2. Lint npm run lint # 3. Unit tests npm test # 4. E2E tests (if relevant to changes) npm run e2e:smoke # or npm run e2e for full suite # 5. Performance tests (if algorithm changes) npm run perf:fitness # or perf:ga
All must pass before the refactor is considered "done."
Step 6: Document Changes (if significant)
- •Update comments/TSDoc if public APIs changed
- •Update related documentation in
docs/(if algorithm/architecture changed) - •Do NOT create a summary markdown file unless explicitly requested
Refactoring Patterns & Examples
Pattern 1: Extract Constants
Before:
if (price > 100 && volume > 1000000) {
// ...
}
After:
// constants.ts
export const MIN_PRICE_THRESHOLD = 100;
export const MIN_VOLUME_THRESHOLD = 1_000_000;
// main.ts
if (price > MIN_PRICE_THRESHOLD && volume > MIN_VOLUME_THRESHOLD) {
// ...
}
Pattern 2: Extract Helper Functions
Before:
function processData(data: Data[]): Result {
// 50 lines of validation
// 30 lines of transformation
// 20 lines of aggregation
return result;
}
After:
function processData(data: Data[]): Result {
const validated = validateData(data);
const transformed = transformData(validated);
const result = aggregateData(transformed);
return result;
}
function validateData(data: Data[]): Data[] {
// 50 lines of validation
}
function transformData(data: Data[]): Transformed[] {
// 30 lines of transformation
}
function aggregateData(data: Transformed[]): Result {
// 20 lines of aggregation
}
Pattern 3: Feature Folder for Big Files
Before:
src/ ├── bigFeature.ts (500 lines) ├── bigFeatureTypes.ts └── bigFeatureUtils.ts
After:
src/
└── bigFeature/
├── BigFeature.ts # Main entry (exports public API)
├── types.ts # Feature-specific types
├── constants.ts # Feature-specific constants
├── helpers/
│ ├── validation.ts
│ ├── transformation.ts
│ └── aggregation.ts
└── __tests__/
├── BigFeature.test.ts
├── validation.test.ts
└── transformation.test.ts
Pattern 4: Early Returns to Reduce Nesting
Before:
function processItem(item: Item): Result {
if (item.isValid) {
if (item.hasData) {
if (item.data.length > 0) {
// Deep nesting
return processValidItem(item);
}
}
}
return null;
}
After:
function processItem(item: Item): Result {
if (!item.isValid) return null;
if (!item.hasData) return null;
if (item.data.length === 0) return null;
return processValidItem(item);
}
Pattern 5: Strategy Map Over Switch/If Chains
Before:
function getIndicator(type: string): Indicator {
if (type === 'RSI') return new RSIIndicator();
if (type === 'MACD') return new MACDIndicator();
if (type === 'SMA') return new SMAIndicator();
// ... 20 more types
throw new Error(`Unknown type: ${type}`);
}
After:
const INDICATOR_REGISTRY: Record<string, () => Indicator> = {
RSI: () => new RSIIndicator(),
MACD: () => new MACDIndicator(),
SMA: () => new SMAIndicator(),
// ... 20 more types
};
function getIndicator(type: string): Indicator {
const factory = INDICATOR_REGISTRY[type];
if (!factory) throw new Error(`Unknown type: ${type}`);
return factory();
}
Decision Trees
Should I Extract a Helper?
Is the logic > 40 lines OR used in multiple places? ├─ YES → Extract to helper function │ └─ Is it used only in this file? │ ├─ YES → Keep in same file (or local helpers/ folder) │ └─ NO → Move to shared utils/ or feature helpers/ └─ NO → Keep inline (clarity is more important than extraction)
Should I Create a Feature Folder?
Is the file > 150 lines OR has 3+ related files? ├─ YES → Create feature folder with proper structure │ └─ Does it have complex logic? │ ├─ YES → Add helpers/ subfolder │ └─ NO → Keep flat with types.ts + constants.ts └─ NO → Keep as single file (over-abstraction hurts readability)
Should I Rename This?
Is the name unclear OR inconsistent with conventions? ├─ YES → Rename │ └─ Is it exported/public? │ ├─ YES → Update ALL usages across repo (use list_code_usages) │ └─ NO → Rename locally └─ NO → Keep existing name (consistency matters)
Troubleshooting
Tests Fail After Refactor
- •Check behavior preservation: Did you accidentally change logic?
- •Review test assertions: Do they still match the refactored structure?
- •Run specific test:
npm test -- path/to/test.test.ts - •Check mocks: Did you change interfaces that tests mock?
TypeScript Errors After Refactor
- •Run typecheck:
npm run buildortsc -b - •Check type imports: Did you move types to a new file?
- •Update export/import paths: Did folder structure change?
- •Check type compatibility: Did you change interfaces?
ESLint Errors After Refactor
- •Run lint:
npm run lint - •Auto-fix: ESLint can auto-fix some issues
- •Check naming conventions: camelCase, PascalCase, UPPER_SNAKE_CASE
- •Check unused vars: Remove or prefix with
_if intentionally unused
Performance Regression
- •Run performance tests:
npm run perf:fitnessornpm run perf:ga - •Check hot paths: Did you add allocations in tight loops?
- •Check typed arrays: Did you replace typed arrays with regular arrays?
- •Profile if needed: Use browser DevTools or Node.js profiler
Import Cycles
- •Identify the cycle: TypeScript will report circular dependency
- •Break with interfaces: Extract shared types to separate file
- •Dependency injection: Pass dependencies as parameters
- •Restructure: Sometimes indicates wrong abstraction boundaries
Quality Checklist (Must Pass)
Before marking a refactor as "done", verify:
- • Type check passes:
npm run build(ortsc -b) - • Lint passes:
npm run lint - • Unit tests pass:
npm test - • E2E tests pass (if relevant):
npm run e2e:smoke - • Performance tests pass (if algorithm changes):
npm run perf:fitness - • All usages updated (if renamed/moved): Use
list_code_usagesto verify - • No unused code: Deleted unused files/exports
- • Tests added/updated: Coverage for refactored areas
- • Behavior preserved: No accidental logic changes
- • Documentation updated (if public API changed): Comments, TSDoc, docs/
References
- •SOLID Principles
- •Clean Code by Robert C. Martin
- •Refactoring by Martin Fowler
- •TypeScript Handbook
- •Vitest Documentation
- •Playwright Documentation
- •ESLint TypeScript
Example: Full Refactor Workflow
Let's say you're refactoring src/algorithm/someComplexFile.ts (300 lines, multiple responsibilities):
- •
Analyze:
- •File has 300 lines (> 150 threshold)
- •Main function has 80 lines (> 40 threshold)
- •Has magic numbers scattered throughout
- •Unclear variable names (
a,tmp,data2) - •No tests
- •
Plan (create todo list):
- •Extract constants to
constants.ts - •Split main function into 3 helpers
- •Rename unclear variables
- •Create feature folder structure
- •Add tests for each helper
- •Run quality checks
- •Extract constants to
- •
Execute:
typescript// Step 1: Create feature folder src/algorithm/someComplex/ ├── SomeComplex.ts ├── constants.ts ├── types.ts ├── helpers/ │ ├── validation.ts │ ├── computation.ts │ └── formatting.ts └── __tests__/ ├── SomeComplex.test.ts ├── validation.test.ts ├── computation.test.ts └── formatting.test.ts // Step 2: Extract constants // constants.ts export const MAX_ITERATIONS = 100; export const CONVERGENCE_THRESHOLD = 0.001; // Step 3: Extract helpers // helpers/validation.ts export function validateInput(input: Input): ValidationResult { ... } // helpers/computation.ts export function computeResult(validated: ValidInput): ComputeResult { ... } // helpers/formatting.ts export function formatOutput(result: ComputeResult): Output { ... } // Step 4: Refactor main function // SomeComplex.ts export function someComplex(input: Input): Output { const validated = validateInput(input); const result = computeResult(validated); return formatOutput(result); } // Step 5: Add tests for each // __tests__/validation.test.ts import { describe, it, expect } from 'vitest'; import { validateInput } from '../helpers/validation'; describe('validateInput', () => { it('should validate correct input', () => { const input = { /* ... */ }; const result = validateInput(input); expect(result.isValid).toBe(true); }); it('should reject invalid input', () => { const input = { /* invalid */ }; const result = validateInput(input); expect(result.isValid).toBe(false); }); }); - •
Verify:
powershellnpm run build # Type check npm run lint # Lint npm test # Unit tests npm run e2e:smoke # E2E (if relevant)
- •
Done: All quality checks pass, refactor is complete.
Anti-Patterns to Avoid
- •Over-abstraction: Don't create layers that don't add value (e.g., single-use wrappers)
- •Under-abstraction: Don't leave 500-line files with duplicated logic
- •Half-migrations: Don't rename things without updating all usages
- •Silent behavior changes: Don't fix "bugs" during refactor without tests
- •Performance degradation: Don't replace performant code with slower patterns
- •Breaking tests: Refactors should preserve behavior, so tests should still pass
- •Unused code: Don't leave dead code behind (delete it)
- •Magic strings/numbers: Don't leave literals scattered (extract to constants)
Summary: The Refactoring Loop
1. Analyze file → Identify issues 2. Plan refactor → Create todo list 3. Extract constants → Single source of truth 4. Extract helpers → Reduce complexity 5. Rename for clarity → Improve readability 6. Simplify control flow → Reduce nesting 7. Update tests → Preserve behavior 8. Run quality checks → Verify all pass 9. Done → Refactor complete
Remember: Refactoring is about improving structure while preserving behavior. Performance, readability, and maintainability are the goals—not just making the code "look nice."