AST-powered TypeScript refactoring agent with LLM-generated options, metric-driven decisions, and automatic validation.
OJ Refactor is a local CLI tool that analyzes TypeScript source files at the AST level, detects code smells, and presents refactoring options generated by a large language model — each one grounded in a different engineering trade-off. Every option includes before/after metrics and is validated by the TypeScript compiler before any file is touched.
The core principle: the algorithm decides what to fix, the LLM executes the fix, and the compiler verifies the result. No guess-work, no single "just do it" suggestion.
| Problem with existing tools | How OJ Refactor solves it |
|---|---|
| Cursor / Copilot give one LLM answer with no rationale | Catalog rules + 3 trade-off options + measurable metrics |
| Sourcery is rule-only with no AI flexibility | Rules as the decision engine, LLM as the executor |
| CodeRabbit / Greptile are cloud-only and comment-only | Local-first, applies changes, auto-commits |
| Linters auto-fix style but not structure | Structural refactoring across 5 catalogs |
┌─────────────────────────────────────────────────────────────┐
│ CLI Entry Point │
│ npx refactor <file> | npx refactor --pr │
└───────────────────────┬─────────────────────────────────────┘
│
┌─────────────▼──────────────┐
│ Tidy First Advisor │ ← Kent Beck "Tidy First?" §16, §21
│ analyzePRReadiness() │ classifies uncommitted diffs as
│ whenToTidy() │ FIRST / AFTER / LATER / NEVER
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ Convention Selector │ ← auto-detects from CONVENTIONS.md
│ selectConvention() │ or lets user pick enterprise preset
│ Airbnb / Google / TS-strict│ (Airbnb, Google Standard, TS-strict…)
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ Smell Detection │
│ detect() → any types │ ← TypeScript Compiler API (AST walk)
│ detectNesting()→ deep nest │
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ Catalog Router │
│ replace-any │ ← picks catalog based on
│ guard-clauses │ which smell dominates
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ LLM Option Generator │ ← Anthropic Claude (SDK)
│ generate() │ sends catalog rule + full AST context
│ generateGuardClauses() │ returns 1 refactored option
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ Metrics Engine │
│ measureComplexity() │ ← Cyclomatic complexity, LOC,
│ before / after diff │ max nesting depth — shown on
└─────────────┬──────────────┘ the option card
│
┌─────────────▼──────────────┐
│ Interactive Option Card │ ← terminal diff rendered side-by-side
│ formatSingle() │ user confirms or skips
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ Apply + Validate │
│ apply() │ ← writes file, keeps .bak backup
│ tscCheckAsync() │ runs tsc --noEmit
│ validateConvention() │ LLM checks style rule compliance
│ rollback() │ reverts on any failure
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ Commit + PR │
│ buildCommitMessage() │ ← semantic commit message
│ commitRefactoring() │ optional git commit
└─────────────────────────────┘
| Catalog | Trigger | What it does |
|---|---|---|
replace-any |
any in params, returns, variables, generics, assertions |
Replaces any with precise types (unknown, interfaces, unions) |
guard-clauses |
Nesting depth ≥ 3 | Flattens deeply nested conditionals into early-return guard clauses |
Each catalog carries a Tidy First timing label (FIRST / AFTER / LATER / NEVER) derived from Kent Beck's §21 decision rules, so developers know when to tidy, not just how.
OJ Refactor embeds Kent Beck's Tidy First? methodology at two points:
-
Pre-flight check — Before processing any file,
analyzePRReadiness()uses Claude to classify uncommitted git diffs asTIDY | BEHAVIOR | MIXED. If structural and behavioral changes are mixed in the same branch, a warning is surfaced (§16: Separate Tidying). -
Timing advice —
whenToTidy()evaluates issue severity and returns one of four timing labels per §21:FIRST— tidy before the behavior change (high smell density, blocks comprehension)AFTER— tidy in a follow-up commitLATER— batch with other minor tidyingNEVER— issue count is zero or code is never touched again
The tool detects your project's coding style automatically and enforces it during refactoring:
| Source | How it works |
|---|---|
auto |
Scans CONVENTIONS.md in the repo root and passes it to the LLM as a style constraint |
enterprise |
User selects from built-in presets: Airbnb JS, Google JS, Standard JS, Airbnb TS, Google TS, TS Strict |
When parallel mode is active, TypeScript type-check and convention validation run concurrently to cut wait time.
Every option card shows measurable before/after numbers:
| Metric | Description |
|---|---|
| Cyclomatic Complexity | Branch point count (if, for, while, &&, ||, ??) |
| Lines of Code | Non-blank lines |
| Max Nesting Depth | Deepest control-flow level |
Example card output:
◆ auth.ts · 12 `any` occurrences
Tidy FIRST any 12개는 동작 변경 전 먼저 제거해야 합니다
─ before ──────────────────────────────
function fetchUser(id: any): any { ... }
─ after ────────────────────────────────
function fetchUser(id: string): Promise<User> { ... }
complexity 14 → 8 (-43%)
lines 84 → 61 (-27%)
depth 4 → 2 (-50%)
Prerequisites
- Node.js 20+
- An Anthropic API key
# Clone and install
git clone <repo-url>
cd OJ_Code_Refactor
npm install
# Set your API key
export ANTHROPIC_API_KEY=sk-ant-...
# Build
npm run buildnpx refactor src/auth.ts- Select language (English / 한국어)
- Optionally enable inline comments in the generated code
- Tool detects smells, shows a refactoring option with metrics
- Confirm to apply — tsc validates the output automatically
- Optionally commit with a generated semantic commit message
npx refactor --prWorks on every .ts file changed since your base branch (main / develop). Supports auto-apply (no confirmation prompt per file) or confirm-each modes. A rollback prompt appears if any file fails validation.
| Flag | Effect |
|---|---|
--pr |
PR mode: scan all changed TS files |
--quokka |
Enable the animated quokka mascot |
Developer runs: npx refactor src/api.ts
│
▼
[1] Tidy First pre-flight
└─ any uncommitted mixed changes? → warn to separate tidying from behavior
│
▼
[2] Convention detection
└─ CONVENTIONS.md found → "Airbnb TS" preset → locked in for this session
│
▼
[3] AST smell detection
└─ detect(): 5 `any` usages found
└─ detectNesting(): 1 nesting depth violation (depth 4)
└─ replace-any selected (5 > 1)
│
▼
[4] Timing advice
└─ 5 occurrences → AFTER (tidy in a follow-up commit)
│
▼
[5] LLM generation (Claude)
└─ catalog rule + full source → 1 refactored option returned
│
▼
[6] Metrics computed
└─ complexity 14 → 8, lines 84 → 61, depth 4 → 2
│
▼
[7] Option card displayed → user confirms
│
▼
[8] apply() writes file, .bak backup created
│
▼
[9] tsc --noEmit + convention check (parallel)
└─ pass → done
└─ fail → rollback() restores original, error shown
│
▼
[10] Semantic commit message shown
└─ "refactor(replace-any): replace 5 any usages in api.ts"
└─ user chooses to commit or skip
- Languages other than TypeScript
- Multi-file refactoring
- IDE plugins
- Cloud / SaaS mode
- GitLab / Bitbucket PR adapters (interface exists, implementation is v1.1+)
- Self-hosted LLM hosting
| Version | Milestone |
|---|---|
| v1.0 | replace-any + guard-clauses, metrics, tsc validation, conventional commits, PR mode |
| v1.1 | +3 catalog rules (loop-pipeline, async-await, extract-function), GitLab adapter |
| v1.2 | Python catalog (5 rules), multi-file analysis |
| Future | IDE plugin, opt-in telemetry, option-selection learning |
MIT
OJ Refactor는 TypeScript 소스 파일을 AST 수준에서 분석하고, 코드 스멜을 감지한 뒤, 대형 언어 모델(LLM)이 생성한 리팩토링 옵션을 제시하는 로컬 CLI 도구입니다. 각 옵션은 서로 다른 엔지니어링 트레이드오프를 기반으로 하며, Before/After 메트릭을 포함합니다. 변경 사항은 TypeScript 컴파일러가 검증한 후에만 파일에 적용됩니다.
핵심 철학: 알고리즘이 무엇을 고칠지 결정하고, LLM이 고치고, 컴파일러가 검증합니다. 단일 "그냥 해줘" 제안 없이, 측정 가능한 근거 위에서 의사결정합니다.
| 기존 도구의 한계 | OJ Refactor의 해결 방식 |
|---|---|
| Cursor/Copilot은 LLM 단일 답, 근거 없음 | 카탈로그 룰 + 트레이드오프 기반 옵션 + 메트릭 |
| Sourcery는 룰만, AI 유연성 없음 | 룰이 의사결정, LLM이 실행 |
| CodeRabbit/Greptile은 클라우드 전용, 코멘트만 | 로컬 우선, 변경 적용 + 자동 커밋 |
| 린터는 스타일만, 구조 리팩토링 없음 | 5종 카탈로그 기반 구조 리팩토링 |
┌─────────────────────────────────────────────────────────────┐
│ CLI 진입점 │
│ npx refactor <파일> | npx refactor --pr │
└───────────────────────┬─────────────────────────────────────┘
│
┌─────────────▼──────────────┐
│ Tidy First 어드바이저 │ ← Kent Beck "Tidy First?" §16, §21
│ analyzePRReadiness() │ 미커밋 diff를 FIRST/AFTER/LATER/NEVER
│ whenToTidy() │ 로 분류
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ 컨벤션 선택기 │ ← CONVENTIONS.md 자동 감지
│ selectConvention() │ 또는 기업 프리셋 선택
│ Airbnb / Google / TS-strict│ (Airbnb, Google, TS Strict…)
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ 코드 스멜 감지 │
│ detect() → any 타입 │ ← TypeScript Compiler API (AST 탐색)
│ detectNesting()→ 깊은 중첩 │
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ 카탈로그 라우터 │
│ replace-any │ ← 지배적인 스멜에 따라
│ guard-clauses │ 카탈로그 자동 선택
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ LLM 옵션 생성기 │ ← Anthropic Claude (SDK)
│ generate() │ 카탈로그 룰 + 전체 AST 컨텍스트 전송
│ generateGuardClauses() │ 리팩토링된 옵션 1개 반환
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ 메트릭 엔진 │
│ measureComplexity() │ ← 순환 복잡도, LOC, 최대 중첩 깊이
│ before / after 비교 │ 옵션 카드에 수치로 표시
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ 인터랙티브 옵션 카드 │ ← 터미널 diff 표시
│ formatSingle() │ 사용자가 확인 또는 건너뜀
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ 적용 + 검증 │
│ apply() │ ← 파일 쓰기, .bak 백업 생성
│ tscCheckAsync() │ tsc --noEmit 실행
│ validateConvention() │ LLM 스타일 룰 준수 확인
│ rollback() │ 실패 시 자동 복구
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ 커밋 + PR │
│ buildCommitMessage() │ ← 시맨틱 커밋 메시지 생성
│ commitRefactoring() │ 선택적 git commit
└─────────────────────────────┘
| 카탈로그 | 감지 조건 | 수행 내용 |
|---|---|---|
replace-any |
파라미터·반환·변수·제네릭·단언의 any |
any를 unknown, 인터페이스, 유니온 등 정밀 타입으로 교체 |
guard-clauses |
중첩 깊이 ≥ 3 | 깊은 중첩 조건문을 early-return 가드 클로즈로 평탄화 |
각 카탈로그에는 Kent Beck §21 기준으로 도출된 Tidy First 타이밍 레이블(FIRST / AFTER / LATER / NEVER)이 붙어, 개발자가 언제 정리해야 하는지 바로 알 수 있습니다.
OJ Refactor는 Kent Beck의 Tidy First? 방법론을 두 지점에 내재화합니다.
-
사전 점검 — 파일 처리 전
analyzePRReadiness()가 Claude를 사용해 미커밋 git diff를TIDY | BEHAVIOR | MIXED로 분류합니다. 구조 변경과 동작 변경이 같은 브랜치에 섞여 있으면 경고를 표시합니다 (§16: 정리 분리). -
타이밍 조언 —
whenToTidy()가 이슈 심각도를 평가해 §21 기준으로 네 가지 타이밍 레이블 중 하나를 반환합니다.FIRST— 동작 변경 전 먼저 정리 (스멜 밀도 높음, 이해를 막음)AFTER— 동작 변경 후 별도 커밋으로 정리LATER— 나중에 일괄 처리NEVER— 이슈가 없거나 해당 코드를 다시 건드릴 일이 없음
도구가 프로젝트 코딩 스타일을 자동으로 감지하고, 리팩토링 시 해당 스타일을 강제합니다.
| 소스 | 동작 방식 |
|---|---|
auto |
저장소 루트의 CONVENTIONS.md를 스캔해 LLM에 스타일 제약으로 전달 |
enterprise |
내장 프리셋 중 선택: Airbnb JS, Google JS, Standard JS, Airbnb TS, Google TS, TS Strict |
parallel 모드에서는 TypeScript 타입 체크와 컨벤션 검증이 동시에 실행되어 대기 시간을 줄입니다.
모든 옵션 카드에 측정 가능한 Before/After 수치가 표시됩니다.
| 메트릭 | 설명 |
|---|---|
| 순환 복잡도 | 분기점 수 (if, for, while, &&, ||, ??) |
| 코드 라인 수 | 비공백 라인 수 |
| 최대 중첩 깊이 | 가장 깊은 제어 흐름 레벨 |
옵션 카드 예시:
◆ auth.ts · 12개 `any` 감지
Tidy FIRST any 12개는 동작 변경 전 먼저 제거해야 합니다
─ 변경 전 ──────────────────────────────
function fetchUser(id: any): any { ... }
─ 변경 후 ──────────────────────────────
function fetchUser(id: string): Promise<User> { ... }
복잡도 14 → 8 (-43%)
라인 수 84 → 61 (-27%)
중첩 깊이 4 → 2 (-50%)
필요 사항
- Node.js 20 이상
- Anthropic API 키
# 클론 및 설치
git clone <repo-url>
cd OJ_Code_Refactor
npm install
# API 키 설정
export ANTHROPIC_API_KEY=sk-ant-...
# 빌드
npm run buildnpx refactor src/auth.ts- 언어 선택 (English / 한국어)
- 생성된 코드에 인라인 주석 포함 여부 선택
- 도구가 스멜을 감지하고, 메트릭이 포함된 리팩토링 옵션을 표시
- 확인 시 적용 — tsc가 자동으로 결과를 검증
- 생성된 시맨틱 커밋 메시지로 커밋 선택 가능
npx refactor --pr베이스 브랜치(main / develop) 이후 변경된 모든 .ts 파일에 대해 동작합니다. 자동 적용 (파일별 확인 없음) 또는 개별 확인 모드를 선택할 수 있습니다. 어느 파일이라도 검증에 실패하면 전체 롤백 프롬프트가 표시됩니다.
| 플래그 | 효과 |
|---|---|
--pr |
PR 모드: 변경된 모든 TS 파일 스캔 |
--quokka |
쿼카 마스코트 애니메이션 활성화 |
개발자 실행: npx refactor src/api.ts
│
▼
[1] Tidy First 사전 점검
└─ 미커밋 변경에 구조·동작 혼재? → 분리 커밋 권장 경고
│
▼
[2] 컨벤션 감지
└─ CONVENTIONS.md 발견 → "Airbnb TS" 프리셋 → 세션 고정
│
▼
[3] AST 스멜 감지
└─ detect(): `any` 5곳 발견
└─ detectNesting(): 중첩 깊이 위반 1곳 (깊이 4)
└─ replace-any 선택 (5 > 1)
│
▼
[4] 타이밍 조언
└─ 5건 → AFTER (동작 변경 후 별도 커밋으로 정리)
│
▼
[5] LLM 생성 (Claude)
└─ 카탈로그 룰 + 전체 소스 → 리팩토링 옵션 1개 반환
│
▼
[6] 메트릭 계산
└─ 복잡도 14 → 8, 라인 84 → 61, 중첩 깊이 4 → 2
│
▼
[7] 옵션 카드 표시 → 사용자 확인
│
▼
[8] apply()가 파일 쓰기, .bak 백업 생성
│
▼
[9] tsc --noEmit + 컨벤션 검사 (병렬)
└─ 통과 → 완료
└─ 실패 → rollback()으로 원본 복구, 오류 표시
│
▼
[10] 시맨틱 커밋 메시지 표시
└─ "refactor(replace-any): api.ts의 any 5곳 교체"
└─ 사용자가 커밋 또는 건너뜀 선택
- TypeScript 외 언어
- 다중 파일 리팩토링
- IDE 플러그인
- 클라우드 / SaaS 모드
- GitLab / Bitbucket PR 어댑터 (인터페이스 존재, 구현은 v1.1+)
- 자체 LLM 호스팅
| 버전 | 마일스톤 |
|---|---|
| v1.0 | replace-any + guard-clauses, 메트릭, tsc 검증, 컨벤셔널 커밋, PR 모드 |
| v1.1 | 카탈로그 +3개 (loop-pipeline, async-await, extract-function), GitLab 어댑터 |
| v1.2 | Python 카탈로그 (5개 룰), 다중 파일 분석 |
| 향후 | IDE 플러그인, opt-in 텔레메트리, 옵션 선택 학습 |
MIT