anti-slop
규칙 15종을 제 코드 전부에 돌려 27건을 잡았고 한 건도 안 고치기로 했습니다. 대신 붙일 자리를 찾다가 제 유출 방어 게이트가 조용히 꺼질 수 있게 생긴 걸 봤습니다.
규칙 15종을 제 코드 전부에 돌려 27건을 잡았고, 한 건도 안 고치기로 했습니다. anti-slop 은 TypeScript 에서 「타입 증거를 버리는」 구문 16종을 금지하는 Oxlint 플러그인입니다. 타입체커를 한 번도 부르지 않고 파일 한 장의 구문 트리만으로 판정하는 게 정체입니다. 제가 연 이유는 규칙이 아니라 에이전트가 읽고 바로 행동하도록 쓴 진단 문구 쪽이었는데, 붙일 자리를 찾으려 제 발행 게이트 체인을 열었다가 훨씬 비싼 걸 봤습니다 — 제가 공개하면 안 되는 내부 용어를 막는 유일한 스캔이, 참조 파일 하나가 사라지면 출력 한 줄 없이 건너뛰어지는 if 위에 서 있었습니다. 대상 쪽에서도 같은 게 나왔습니다. 이 레포는 자기 소스에 자기 플러그인을 한 번도 돌린 적이 없습니다. 관통선은 하나입니다 — 게이트는 존재가 아니라 실행으로 센다.
무엇을 하는 물건인가
「값이 무엇인지 알고 있는데 그 정보를 버리는 구문」을 금지하는 규칙 모음입니다. Oxlint 1.78 이 새로 연 JS 플러그인 API 위에 얹혀 있고, 아무 데나 켜는 범용 15종과 특정 프레임워크에서만 켜는 옵트인 1종으로 갈라져 있습니다.
| 무엇을 막나 | 어떤 구문 |
|---|---|
| 넓은 타입을 경계 밖으로 흘리기 | 파라미터·반환·타입 별칭 자리의 unknown · any · object · {} |
| 열린 사전 타입 | Record<string, unknown> 류 — 키가 무엇이든 통과한다 |
| 타입 세탁 | x as unknown as T 연쇄 단언 |
| 넓혔다가 도로 좁히기 | 알려진 값을 넓게 선언해 두고 나중에 단언으로 되돌리는 흐름 |
| 런타임으로 도망가기 | typeof 분기 · Reflect.get · Reflect.apply · 모듈 목킹 |
| 근거 없는 단언 | 단언 앞에 SAFETY: 주석으로 불변식을 진술하게 강제 |
특이한 건 이걸 하는 방법입니다 — 타입체커를 안 부릅니다. 파일 한 장의 구문 트리에서 최상위 선언만 훑어 타입 별칭 표를 직접 만든 뒤 그 표로 별칭을 재귀 치환합니다. Record · Partial 같은 빌트인 여덟 개는 이름을 상수로 박아 뒀고, 두 타입이 같은지도 소스에서 공백을 지운 문자열 비교로 근사합니다. 대가는 명확합니다 — import 한 타입도, 블록 안에서 선언한 별칭도 안 보입니다.
| 항목 | 실측 (2026-08-22) |
|---|---|
| 규모 | 파일 66개 · 62KB · 런타임 의존성 1개 |
| 별 · 포크 | 3,313 · 63 (피드가 08-20 에 찍은 값은 2,871 — 이틀에 +442) |
| 커밋 | 13개. 전부 08-12 ~ 08-18 · 릴리스 0 · 태그 0 |
| 기여자 | 1명. 그리고 PR 이 비활성화돼 있다 |
| 테스트 | 규칙 16종 중 3종은 테스트 파일이 아예 없다 |
| 자동 수정 | 0개. 오토픽스가 런타임 의미를 바꾼다는 제보를 받고 픽서를 전부 지웠다 |
배포도 보통이 아닙니다. npm 에 안 냅니다 — 패키지가 private 이고 진입점이 빌드 산출물이 아니라 소스 파일입니다. 설치는 에이전트 스킬이 소스 .ts 를 남의 저장소 안으로 복사하는 것이고, README 가 이유를 밝힙니다: 고정 의존성으로 두지 말고 복사해 읽고 팀 기준에 맞게 고쳐 쓰라는 것.
대표 기법 — 값나가는 건 규칙이 아니라 배포 쪽이다
벤더링 자체는 위에서 봤고, 그 위에 얹힌 다섯이 이렇습니다.
| 기법 | 무엇 |
|---|---|
| 스킬이 곧 설치 프로그램 | 결정론 코드는 21줄(복사 하나)뿐. 나머지 다섯 단계 — 패키지 매니저 판별 · 최신 버전 조회 · 설정 병합 · 규칙 활성화 · 검증 — 는 산문으로 적혀 LLM 이 수행한다 |
| 절차에 에이전트 행동 제약을 박는다 | 「네가 기억하는 버전을 믿지 말고 조회하라」, 「규칙을 억제하거나 unsafe cast 로 통과시켜 린트를 넘기지 말라」가 설치 절차의 한 항목 |
| 정본↔사본 바이트 동일성 CI 게이트 | 파일 목록과 utf8 내용을 둘 다 대조해 다르면 체인을 세운다. 벤더링의 대표 실패를 CI 로 봉쇄 |
| 오토픽스 전면 포기 | 픽서가 「키 생략」을 「키를 undefined 로 설정」으로 바꾼 사고 이후 16종 전부 report-only |
| 에이전트 겨냥 진단 문구 | 「이게 틀렸다」가 아니라 「경계에서 파싱하고 이름 있는 도메인 타입을 반환하라」. 커밋 하나를 통째로 이 작업에 썼다 |
제가 이 레포를 연 이유가 마지막 줄에 있습니다. 제 발행 게이트들의 1차 독자가 사람이 아니라 세션이라서요.
깨본 결과 — 자기주장 열하나 중 아홉이 안 버텼다
이 도구는 문서 수준에서 자기 자신에게 면제돼 있습니다. 이게 핵심 반증입니다.
- 저장소 어디에도 oxlint 설정 파일이 없습니다.
lint스크립트는 기본 규칙만 돌고, 플러그인은 자기 소스에 단 한 번도 적용된 적이 없습니다. - 그 결과 21줄짜리 공용 모듈 하나가 자기 규칙 최소 4종을 어깁니다 — 파라미터 자리의
unknown,typeof런타임 분기,as unknown as연쇄 단언, 그 단언 앞에SAFETY:주석 없음. 62KB 코드베이스라 「규모가 커서」로 방어되지 않습니다. - README 와 설치 절차는 사용자에게도 벤더링된 사본을 린트 제외 목록에 넣으라고 지시합니다.
핵심 규칙 하나는 빈 주석으로 뚫립니다. 단언에 근거를 적게 만드는 규칙의 검사가 SAFETY: 마커가 있는지만 보고 내용은 안 봅니다. // SAFETY: 한 줄이면 CI 가 정확히 이 규칙이 막으려던 캐스팅을 승인합니다. 설치 절차가 산문으로 금지한 「린트를 통과시키려고 타입을 세탁하는 짓」을 에이전트가 정확히 이 경로로 자동 수행할 수 있습니다. 미탐도 같은 모양입니다 — 별칭 표를 최상위에서만 만들어서 type Identity<T> = T 같은 투명 별칭을 한 번 거치면 규칙 여럿이 동시에 눈이 멉니다.
유일한 perf: 커밋은 상류 문서가 부정합니다. 규칙 15종을 새 API 로 옮기며 성능 개선이라 적었는데, 근거가 된 상류 공식 문서가 「지금 더 빠른가」에 대해 “지금은 아니다. 하지만 곧 그렇게 될 것이다” 라고 적어 놨습니다. 저장소 안에 전후 수치가 없고, 닷새 뒤 추가된 마지막 규칙은 구 API 로 되돌아가 있습니다. 배포 모델도 이미 우회당했습니다 — 뜯기 하루 전에 커뮤니티가 같은 규칙을 번들해 npm 에 올렸고, 그건 취향이 아니라 필요였습니다. oxlint 가 공유 설정에서 상대경로 플러그인 지정자를 지원하지 않아 공유 설정을 쓰는 조직은 벤더링으로 도입 자체가 불가능합니다.
버틴 것도 적어 둡니다. 정본↔사본 동일성 CI 게이트는 깰 거리가 없었고, 옵트인 규칙이 스스로 밝힌 범위 한계도 코드와 일치했습니다. 위험한 걸 하면서 한계를 적어 두는 쪽입니다. 나쁜 건 공급자입니다 — 08-18 이후 정지, 이슈 15건 전부 무응답, 그중 여섯은 패치까지 붙은 제보인데 PR 이 비활성이라 포크 링크로만 제출돼 있습니다.
내 환경에 대봤다 — 면적이 770줄이었다
먼저 잰 건 규칙이 아니라 적용 면적입니다. 제 작업 폴더 전체에서 손으로 쓴 TypeScript 는 개인 사이트 7개 파일 770줄이 전부입니다. 나머지 프로젝트는 파이썬·파워셸·CommonJS 라 TS 가 0개입니다. 이 도구는 대형 모노레포를 겨냥해 만들어졌습니다 — 외부 적용 보고 하나가 568,443줄·4,421파일 규모였습니다. 그 770줄에 15종을 전부 켜고 돌렸습니다.
27건 중 18건이 한 패턴입니다. 프레임워크가 라우트 파라미터를 「문자열이거나 없음」으로 주기 때문에 페이지마다 그걸 좁히는 단언이 한 줄씩 있습니다. 규칙은 그 18곳 전부에 주석을 요구하는데, 경계에서 강제되는 것이라 없앨 수 없고 주석을 18개 붙여도 코드는 한 글자도 안전해지지 않습니다. 7건 쪽은 키가 이미 닫혀 있는 사전 타입까지 잡습니다 — 외부 실측에서 150건 중 117건이 「고쳐도 타입이 동일」함이 컴파일러로 증명된 오탐군과 같은 종류입니다.
예상 반증도 하나 나왔는데, 나쁜 쪽으로 나왔습니다. 저는 이 린터가 사이트 템플릿 파일을 못 읽을 거라 가정했는데 실제로는 프런트매터를 파싱해 정상 보고합니다. 적용 면적이 16장 더 늘어난 셈인데 늘어난 만큼이 전부 위의 18건짜리 노이즈였습니다. 면적이 커진 것이 이득이 아니라 손해로 나온 사례입니다. 속도는 기각 사유가 아니었습니다 — 웜 260ms → 450ms 로 70% 늘지만 절대값이 무시할 수준입니다.
수확 셋을 적었고 하나만 살아남았다
실질 산출은 플러그인이 아니라 그 밑에 깔린 맨 oxlint 였습니다. 제 사이트에는 정식 린터가 하나도 없습니다 — eslint · prettier · biome · typescript 전부 미설치고 tsconfig.json 도 없습니다. 품질 게이트 세 개는 제가 짠 도메인 검사기라 미사용 변수 같은 범용 결함을 안 봅니다.
| 수확 후보 | 판정 | 사유 |
|---|---|---|
| 맨 oxlint 를 발행 체인에 편입 | 채택 | 무설정 실행이 exit 0 이라 오늘 넣어도 배포가 안 막힌다. 발행 파이프라인 코드에서 진짜 결함 2건을 즉시 잡았다 |
| 진단 문구를 지시문으로 | 축소 | 「13곳 전부 지시문이 없다」가 틀렸다. 두 곳은 이미 실행 명령까지 인쇄하고 나머지도 결과절을 달고 있다. 4줄 다듬기로 축소 |
| 정본↔사본 동일성 게이트 | 축소 · 방향 정정 | 겨냥한 실패 모드를 퍼블리시 절차가 이미 없애 놨다. 실제 감시 부재는 반대 방향이었다 |
✖ 죽은 링크 3건 — push 하지 않고 멈춥니다
✖ 죽은 링크 3건. 아래 주소를 고치거나 해당 문서를 만든 뒤 다시 돌릴 것 (링크를 지우는 것도 수정이다 — 자동으로 하지 않는다)
축소된 쪽에서 값을 치른 교훈이 나옵니다. 동기화 게이트는 「사본이 낡는 걸 못 잡는다」고 적었는데 실측하니 오늘 낡은 사본이 0건이었습니다. 조립 단계가 매번 전량 재복사를 해서 그 실패 모드가 이미 없어져 있었습니다. 더 나쁜 건 설계 쪽입니다 — 사본이 정본과 다른 이유의 본체가 제가 가정한 경로 치환이 아니라 대응표가 아예 존재하지 않는 용어 살균이라 「반나절」이라는 견적이 성립하지 않습니다. 그대로 켰으면 첫 실행에 허위경보 열 건이 떴을 겁니다. 채택한 쪽에도 정정이 붙었습니다 — 오탐을 2건이라 셌는데 실제로는 3건이었습니다. 한 파일을 빠뜨려 놓고 「전수」라고 적었습니다.
판정
| 항목 | 판정 |
|---|---|
| anti-slop 플러그인 도입 | 기각. 면적 770줄 · 27건 중 25건이 경계 노이즈와 기지 오탐 · 고유 명분 0건 |
| 맨 oxlint 편입 | 채택. 오늘 넣어도 발행이 안 막힌다 |
| 지시문 진단 · 동기화 게이트 | 축소. 절반은 이미 되어 있었고, 하나는 방향이 틀렸다 |
| 다시 여는 조건 | TS 를 1,500줄 이상 쓰는 프로젝트를 시작할 때 · 연쇄 단언 패턴이 내 코드에 실제로 나타날 때 · 상류가 오탐 두 규칙에 옵션을 붙일 때 · 타입 인지 린팅이 안정판이 될 때 |
공급자 리스크가 마지막 한 겹입니다. 벤더링이라 도입하면 방치된 패치를 제가 사본에 직접 발라야 하고, 그 순간부터 상류와 영구히 갈라지며 유지비를 제가 집니다. 별 3,313개는 이걸 하나도 가려 주지 않습니다.
이 조사가 실제로 바꾼 것은 남의 린터가 아니었다
수확을 붙일 자리를 찾으려고 제 퍼블리시 절차를 열었다가, 유출 방어의 단일 지점이 fail-open 인 걸 봤습니다. 그 절차에는 스캔이 두 번 들어갑니다 — 푸시 전과 푸시 후. 둘 다 이렇게 시작합니다.
if [ -f "<도메인 테이블맵 경로>" ]; then
grep -nE "\b(<테이블 이름 54종>)_[A-Za-z]" ...
fi
else 가 없습니다. 참조 파일이 사라지면 스캔이 통째로 건너뛰어지고, 출력은 한 줄도 안 나오며, 다음 줄에서 게이트는 그대로 통과합니다. 푸시 후 쪽은 grep 에 에러 무음 처리까지 붙어 이중 침묵입니다.
이게 왜 비싼지는 같은 문서가 스스로 밝혀 놨습니다. 제가 막아야 하는 이름 중 한 부류는 살균 전 이름과 살균 후 이름의 모양이 완전히 같습니다. 구조적 정규식으로 구분이 안 되니 이 부류는 패턴 목록 34개가 아니라 오직 저 테이블맵 조회만이 잡습니다. 그리고 이게 이론이 아닌 이유가 이번 조사에서 실물로 나왔습니다 — 공개용 사본과 정본을 대조해 보니 실제로 살균된 항목이 정확히 그 부류였습니다. 파일은 지금 살아 있습니다. 하지만 경로가 두 곳에 하드코딩돼 있고, 저는 이미 작업 폴더 구조를 한 번 통째로 갈아엎은 적이 있습니다.
여기서 값을 치른 교훈은 대상이 준 것의 반대쪽입니다. anti-slop 은 오토픽스 사고 이후 픽서를 전부 지웠습니다 — 틀리게 고치느니 안 고치겠다는 판단이고 옳습니다. 그런데 게이트는 반대입니다. 틀리게 울리는 것보다 안 울리는 쪽이 훨씬 위험합니다. 허위경보는 사람이 보고 지우지만 침묵은 아무도 안 봅니다.
못 한 것
- 그 한 줄을 아직 안 고쳤습니다. 찾기만 했습니다. 고치기 전에 퍼블리시 절차 전체를 다시 읽어 다른 fail-open 이 더 있는지부터 세야 해서 별건으로 남겼습니다.
- 커뮤니티 npm 포크를 안 돌려봤습니다. 「발행 하루짜리라 신뢰 근거가 없다」고 적고 건너뛰었는데, 그건 안 잰 이유이지 잰 결과가 아닙니다.
- 규칙 정밀도는 남의 숫자를 인용했습니다. 568,443줄 실측은 외부 사용자가 올린 것이고 제 측정 면적은 770줄뿐입니다. 「대형 코드베이스에서도 노이즈다」라고는 못 합니다 — 제가 말할 수 있는 건 제 코드에서 값이 0이었다는 것까지입니다.
- 상류에 아무것도 제출하지 않았습니다. 자기 규칙 4중 위반은 이슈 하나로 낼 만한데, PR 이 막혀 있고 제보 15건이 전부 무응답인 걸 보고 안 냈습니다. 판단이었지 확인은 아닙니다.