fixearly
v0.4.0
Published
코드가 아니라 '다음에 고칠 때 드는 비용'을 잰다 — 일찍 고치면 싸다. JS/TS 정적 분석, 100% 로컬, SSS~E 등급, 오픈소스 70개 기준선
Maintainers
Readme
fixearly
우리 엔진도 B입니다. 2천 줄 단일 파일이라서요. 툴은 자기한테도 안 봐줍니다 — 그게 요점입니다. (랜딩)
코드가 아니라 그 코드를 다음에 고칠 때 드는 비용을 잰다. JS·TS 전용 · 정적 분석 · 100% 로컬 · LLM 0 · SSS ~ E 등급.
npx fixearly --dir=src --report명령 한 줄, 설정 제로. 코드는 컴퓨터 밖으로 한 발짝도 안 나간다. --report는 한 장짜리 HTML을 뽑는다 —
같은 체급 오픈소스 안에서 내 위치, 축별 좌표, 무엇부터 고칠지(파일:줄), 그리고 각 항목을 코딩 에이전트에 그대로
붙여넣을 작업 지시문까지.
등급: B (73점) — 함수 11,000개, cog15+ 293개, 중복 5.5%, 200줄+ 173개
지난 측정(2026-07-20 09:00) 대비: C 69 → B 73 (+4점)
✓ 리포트 → fixearly-report.html유명 오픈소스 70개, 실제 점수
같은 자로, 예외 없이 채점했다. 2026-07-26 · 채점 규칙 v6 기준 전량 재측정. 전체는 랜딩에.
| 프로젝트 | 등급 | 점수 | 핵심 지표 | |----------|------|------|-----------| | mitt | SSS | 100 | 1파일 · 75줄 · 중복 0% | | rxjs | SSS | 99 | 188파일 · 6,536줄 · 중복 6.5% | | execa | SSS | 98 | 103파일 · 5,726줄 · 중복 0.7% | | discordjs | SS | 95 | 695파일 · 37,164줄 · 중복 6.3% | | nestjs | SS | 95 | 483파일 · 31,726줄 · 중복 7.3% | | cypress | SS | 93 | 226파일 · 26,436줄 · 중복 1% | | trpc | SS | 93 | 70파일 · 7,295줄 · 중복 2.2% | | prisma | SS | 93 | 147파일 · 9,287줄 · 중복 1.2% | | mongoose | D+ | 52 | 긴 함수 6.1% · 중복 5.7% | | lexical | D+ | 50 | 긴 함수 7.4% · 중복 1.3% | | vue | D | 40 | 긴 함수 8.1% · 중복 1.1% | | payload | D | 39 | 긴 함수 15.5% · 중복 5.5% | | react | E+ | 28 | 긴 함수 10.1% · 중복 18.8% |
react가 E+다. 이건 "나쁜 라이브러리"라는 뜻이 아니다. 성능과 번들 크기를 위해 추상화를 일부러 포기한 코드 (모노모픽 메가함수, 빌드 타깃별 손복제)를 이 축이 깎기 때문이다. 실제로
react-devtools를 제외하면 점수가 더 내려간다 — 툴링이 완충재였다. 점수는 판결이 아니라 측정이다. 무엇을 재는지 전부 아래에 공개한다.
무엇을 재는가
채점축 4개 (등급을 만든다 · 캡 기준 배점)
| 축 | 배점 | 설명 |
|----|------|------|
| 함수 길이 | 26 | 한 번에 읽어야 하는 양. 중첩 함수·주석을 뺀 자기 코드 줄 기준, 40줄 초과(JSX 60줄) 비율 + p90 + 상위 10개 평균. 파일을 쪼개도 안 변하므로 조작이 안 된다. |
| 인지 복잡도 | 37 | SonarSource S3776 스펙 준수(정본값 일치 검증). cog15+·cog25+ 비율 + p90 + 상위 10개 평균. |
| 중복 | 9 | 토큰 단위 복붙 밀도. 70개 실측에서 점수와 상관 −0.11이라 배점을 16→9로 낮췄다 — 대부분엔 0점, 소수에게만 큰 항목. |
| 파일 크기 | 8 | 평균 줄 수 + 대형 파일 비중. 27→8로 강등했다: 같은 코드를 6파일로 쪼개기만 해도 옛 공식이 +27점을 줬다. |
상위 10개 평균을 쓰는 이유: 최악값 1개를 재면 "가장 나쁜 하나만 고치고 끝"이 최적 전략이 된다(실측: 하나 고치면 +0.99점, 그 다음부터 ~0). 상위 10개 평균은 10개를 실제로 줄여야 계속 내려간다.
진단 17종 (등급을 건드리지 않는다 · 파일:줄만 짚는다)
O(n²) 배열 조회 · 루프 안 파일 IO/N+1 · await in forEach · 스프레드 누적 O(n²) · 루프 안 new RegExp ·
공유 참조 fill · 빈 catch · 타입 위생(any·non-null·@ts-ignore) · 데드코드(--dead, knip) 외.
왜 점수에서 뺐나: O(n²)는 n이 실제로 큰지 사람이 확인해야 의미가 있다. 사람 판단이 필요한 축을 자동 채점에 넣으면
작은 저장소가 통째로 무너진다. 그래서 "고칠 목록"으로만 낸다.
왜 믿나 — 점수는 방어할 수 있어야 점수다
- 한계를 먼저 밝힌다 — JS·TS 전용. 파서가 TypeScript 컴파일러 API라
.ts .tsx .js .jsx .mjs .cjs만 읽는다. 그리고 설계의 우아함·테스트·문서·보안·성능·커뮤니티는 하나도 재지 않는다. - 게이밍을 측정해서 공개한다 — 같은 코드를 함수 경계에서 6파일로 기계 분할: 옛 공식 +27점 → 지금 +8점(파일 축 캡). 5줄짜리 파일 대량 투입은 분석 제외로 막힌다. 다만 "게임 불가"라고 주장하지 않는다 — 남은 구멍(파일 축 8점)을 여기 적어두는 편이 정직하다.
- 오픈소스 70개로 보정 — 유예값은 임의 임계가 아니라 코퍼스 중앙값이고, 기울기는 p90에서 캡에 닿게 잡았다. (중앙 81)
- 체급은 코드줄로 나눈다 — 파일 수로 나누면 파일을 잘게 쪼갠 쪽이 체급만 올라간다(실측: 줄/파일이 34~2,008로 59배 차이).
- 종류별 편향을 공개한다 — 라이브러리 87 · 프레임워크 85 · 툴체인 84 · 앱 76 · 엔진·컴파일러 66. 엔진은 추상화를 포기하고, 앱은 화면 단위 함수가 길다. 편향이 아니라 이 축의 성격이다. 그래서 리포트는 동종 기준선을 함께 보여준다.
- 채점 규칙에 버전을 박았다 — 유예·기울기를 바꾸면
v6 → v7. 이력 비교 시 규칙이 다르면 "점수 비교 무효"를 경고한다. - 네거티브 컨트롤 · 자기 검증 루프 · 패턴 카탈로그 — LOOP.md · PATTERNS.md
- 실전 검증 — 등급만 매기지 않고 진짜 고칠 것을 짚는다. 실제 OSS에 낸 PR 기록: IMPACT.md
사용법
# 리포트 한 장 (권장) — 내 위치 + 고칠 목록 + AI 지시문
npx fixearly --dir=src --report
# 점수만
npx fixearly --dir=src
# 먼저 고칠 파일 랭킹 (복잡도 × git churn)
npx fixearly --dir=src --hotspots
# 데드코드 축 추가 (knip 내장)
npx fixearly --dir=src --dead
# 모노레포에서 산출물 아닌 패키지 제외 (근거를 남길 것)
npx fixearly --dir=packages --exclude=packages/devtools,packages/examples
# README 배지
npx fixearly --dir=src --badge측정할 때마다 .fixearly-history.json에 스냅샷이 쌓이고, 리포트에 지난번 대비 변화가 표시된다.
점수는 절대 위치, 변화량은 당신이 한 일이다 — 자기 이력과의 비교라 지표만 건드려서는 움직이지 않는다.
결과는 --out 디렉토리에 kit-stats.json으로도 떨어진다 (CI 추이 추적용).
등급
| 등급 | 점수 | | 등급 | 점수 | |------|------|---|------|------| | SSS | ≥ 97 | | C+ | ≥ 64 | | SS | ≥ 93 | | C | ≥ 55 | | S | ≥ 90 | | D+ | ≥ 47 | | A+ | ≥ 86 | | D | ≥ 35 | | A | ≥ 80 | | E+ | ≥ 21 | | B+ | ≥ 76 | | E | ≥ 0 | | B | ≥ 70 | | | |
등급은 아래쪽을 후하게 잡았다 — 큰 서비스·앱은 구조 지표상 원래 불리하다. E는 사실상 방치된 경우만.
라이선스
MIT
