Farol: 오픈소스, LLM 없는 풀 리퀘스트 위험 지도 (TypeScript, Go, Python)

Farol은 브라질 회사 Winnin이 만든 무료 MIT 라이선스 CLI 및 GitHub Action으로, 승인하는 사람을 위해 풀 리퀘스트의 위험 지도를 생성합니다. AI 리뷰 에이전트 이후에 실행되며, 모델 대신 정적 분석을 사용하고, 0-100 병합 위험 점수, 판정(fast track, quick review, careful review), 그리고 각 파일이 도달하는 페이지, 엔드포인트, 작업, 스크립트와 함께 가장 위험한 변경 파일의 순위 목록을 반환합니다.

farol이 하는 일, 한 단락으로

farol은 CLI(npx @winninjs/farol) 및 GitHub Action(winnin/farol/action)으로, 프로젝트 언어의 실제 컴파일러를 사용하여 풀 리퀘스트의 base와 head를 분석하고 병합이 얼마나 위험한지, 위험이 어디에 있는지 보고합니다. 분석된 저장소의 코드를 실행하지 않으며, 코드를 어디로도 보내지 않고, 병합을 차단하지 않습니다. AI 리뷰 이후에 위치하도록 설계되었으며, 이를 대체하지 않습니다: 리뷰 에이전트는 diff의 라인에 주석을 달고, farol은 인간 승인자에게 그 라인이 도달하는 것을 알려줍니다.

핵심 주장: 작은 변경이 큰 영향력을 가질 때 에이전트 리뷰만으로는 충분하지 않다

프로젝트의 명시된 테제는 승인이 희소 자원이며 모든 풀 리퀘스트에 주의를 균등하게 분산하면 리뷰가 훑어보기가 된다는 것입니다. Hacker News에서 저자의 말로는 실패 모드는 구체적입니다: "38개의 파일이 격리된 40개 파일 PR은 23개의 엔드포인트에서 사용되는 모델을 변경하는 3개 파일 PR과 매우 다릅니다."

따라서 farol은 리뷰어가 diff를 열기 전에 분류를 수행하며 세 가지 질문에 답합니다:

  • 이 변경이 도달하는 것은 무엇인가? 변경된 코드를 실행하는 모든 기존 페이지, API 작업, RPC 메서드, 작업 및 스크립트와 각각에 대한 호출 체인.
  • 병합이 얼마나 위험한가? 영향 범위, diff 커버리지, 보안 발견, 계약 변경 및 의존성 변경에서 계산된 판정과 0-100 점수.
  • 어디서 시작해야 하나? 가장 위험한 파일부터 시작하며, 인라인 diff, 테스트되지 않은 라인 표시, 수동으로 시도할 체크리스트가 포함됩니다.

README는 Claude Code 팀이 같은 원리를 설명한 것을 인용합니다: "변경이 등급을 설정하지, 작성자가 아니다."

판정 및 0-100 병합 위험 점수

farol은 모든 풀 리퀘스트에 세 가지 판정 중 하나를 지정하며, 각 판정은 특정 리뷰어 작업에 매핑됩니다:

  • Fast track: 새 코드, 테스트 또는 저위험 변경만. diff를 훑어보고 승인.
  • Quick review: 기존 코드가 제한된 방식으로 변경되었거나, 낮은 커버리지, 린트 오류 또는 구성 파일과 같은 약한 신호. "Start here" 파일을 확인한 후 승인.
  • Careful review: 광범위하거나 조용한 파손 가능성. 점수 60 이상, 계약 변경, 높은 심각도 발견, 테스트되지 않은 중앙 파일 또는 farol이 읽을 수 없는 코드에 의해 트리거됩니다.

점수(0-100)는 위험한 항목이 하나 이상 무언가를 깨뜨릴 확률로 설명됩니다. 기여 항목은 "무엇이 추가하는가" 아래에 분류됩니다: 영향 범위에 테스트되지 않은 비율을 곱한 파일, 발견, 계약 변경 및 의존성. 모든 판정에는 이유가 함께 제공됩니다.

정적 분석, 모델 아님: 답이 결정적인 이유

AI 리뷰어와 farol의 주요 아키텍처 차이는 영향 범위 분석이 언어 모델이 아닌 컴파일러에 기반한다는 것입니다. README의 비교 표는 AI 리뷰 에이전트가 diff와 프롬프트에 맞는 컨텍스트를 읽고 실행마다 다르게 표현되는 확률적 답변을 제공하는 반면, farol은 TypeScript 타입 체커, go/types 및 Python의 ast를 사용하여 base와 head의 전체 프로그램을 읽고 각 주장에 호출 체인이 포함된 결정적인 답변을 제공한다고 명시합니다.

저자의 Hacker News 댓글에 따르면 "LLM이 없고, 분석 중 네트워크가 없으며, 분석된 저장소의 코드를 실행하지 않습니다. 결정적이며 정보 제공만 하고 병합을 차단하지 않습니다." Go 및 Python 경로의 경우 도우미 프로그램은 표준 라이브러리만 사용하고 구문 분석만 수행합니다.

분석 파이프라인은 다음과 같습니다:

  1. merge-base와 head를 diff하고, 이름 변경을 감지하고 공백 전용 및 후행 쉼표 라인을 무시합니다.
  2. TypeScript 컴파일러 API, go list 및 오프라인 go/types, Python ast를 사용하여 아무것도 실행하지 않고 두 트리를 읽습니다.
  3. 코드 해시로 선언을 비교하여 주석과 서식이 절대 중요하지 않도록 합니다.
  4. 영향 범위 계산: 각 변경된 선언에서 이를 실행하는 모든 진입점까지의 최단 체인을 유지합니다.
  5. 분류, 가중치 부여 및 계획: 클래스, 점수, 판정, 시작할 파일, 테스트 체크리스트.

지원 스택

farol은 TypeScript/React 프론트엔드, TypeScript 및 JavaScript 백엔드, Go 및 Python을 지원합니다. 진입점은 언어별로 읽습니다:

  • React 및 TypeScript 프론트엔드: React Router 라우트 테이블(객체 및 JSX 형식, 지연 라우트, 분할 라우트 모듈), Next.js Pages 및 App Router(페이지, API 라우트, 레이아웃, 미들웨어), TanStack Router 파일 기반 라우트. 영향 범위는 TypeScript 타입 체커를 통한 선언별입니다.
  • TypeScript/JavaScript 백엔드: 스키마 파일 및 gql 템플릿의 GraphQL 루트 작업, Fastify/Express 스타일 REST 엔드포인트. 영향 범위는 모듈별, GraphQL의 경우 리졸버별입니다. 계약 변경은 GraphQL 스키마에서 오며, 파괴적, 위험, 안전으로 분류됩니다.
  • Go: chi, gin, echo 및 net/http 라우트, gRPC 서비스 메서드 및 main 바이너리, 인터페이스 호출은 구현으로 해석됩니다. 계약 변경은 핸들러 요청 및 응답 유형과 gRPC 메시지에서 옵니다.
  • Python: FastAPI 및 Flask 라우트(라우터, 블루프린트, 접두사), Django URL, MCP 도구, 리소스 및 프롬프트, Celery 작업, Temporal 워크플로 및 활동, Prefect 흐름 및 __main__ 스크립트. 계약 변경은 FastAPI 매개변수, 본문, 응답 및 Pydantic 모델에서 옵니다.

farol이 CI에서 재사용하는 입력에는 diff 커버리지를 위한 Istanbul coverage-final.json(Vitest, Jest), go test -coverprofile, coverage.py JSON, ESLint 및 golangci-lint JSON, ruff JSON, golangci-lint를 통한 gosec, ruff S 규칙을 통한 flake8-bandit, 알려진 취약점을 위한 npm audit, pnpm audit, govulncheck 및 pip-audit이 포함됩니다. 또한 eslint-disable, @ts-ignore, //nolint, #noqa 및 #nosec와 같은 억제를 읽습니다. 선택적 Datadog RUM 및 APM 통합은 영향을 받는 페이지 또는 작업당 트래픽 점유율과 고유한 프로덕션 오류를 추가하며, 명시적으로 정보 전용입니다: 점수를 변경하지 않습니다.

출력 내용

farol은 두 가지 산출물을 생성합니다: 판정, 상위 파일 및 테스트 체크리스트가 포함된 PR 댓글과, 해당 댓글에서 링크된 단일 파일 HTML 보고서로 리뷰가 이루어집니다. 보고서 섹션은 다음과 같습니다:

  • 판정 및 점수 diff 전에 이유가 나열됩니다.
  • Start here: 가장 위험한 순서로 정렬된 변경된 코드 파일, 각 행은 파일을 실행하는 기존 페이지 또는 작업 수, diff 커버리지, 발견 및 자체 위험 점수를 보여줍니다. 테스트가 실행되지 않는 추가 라인은 열린 원으로 표시되고, 발견이 있는 라인은 경고 표시로 표시됩니다.
  • 기능 플래그 뒤: 진입점을 완전히 플래그 뒤에 표시; 플래그된 진입점만 사용하는 파일은 리뷰를 떠납니다.
  • 페이지 / 작업: 모든 새 및 영향을 받는 진입점, 모듈별 목록 또는 트리, 변경이 도달하는 경로.
  • 계약(백엔드): 요청 또는 응답 형태가 변경된 각 엔드포인트, 파괴적, 위험, 안전으로 분류, 정확한 필드 및 호출 서비스. README의 예는 GET /brands가 더 이상 response[].name을 반환하지 않는 파괴적 변경입니다.
  • 보안, 의존성, 커버리지: 점수 뒤의 증거. 제공되지 않은 보고서는 통과로 표시되지 않고 "확인되지 않음"으로 표시됩니다.
  • 테스트 계획: 수동으로 시도할 새 및 영향을 받는 페이지 또는 작업, PR 댓글의 작업 목록.
  • 리뷰어: 각 파일의 위험에 따라 가중치가 적용된 git blame의 제안된 리뷰어, CODEOWNERS 포함, PR 작성자와 봇 제외.
  • 결과: 리뷰의 Markdown 기록으로 PR에 다시 복사할 수 있습니다.
  • 마지막 푸시 이후: 열린 PR에 대한 푸시의 경우, 이전 푸시 이후의 커밋만 다루는 두 번째 보고서(analyze --since <sha>).

PR별 클래스는 접힌 블록에 유지되어 리뷰어가 먼저 자신의 의견을 형성합니다: isolated(추가만), touches-existing, global(전역 파일, 매니페스트 또는 중요한 의존성) 및 unknown.

실패 시 닫힘 동작 및 측정된 정확도

farol이 변경의 일부를 읽을 수 없으면 안전을 가정하는 대신 unknown을 보고하고 신중한 리뷰를 요청합니다. 호출을 해결할 수 없으면 변경이 더 많이 도달한다고 가정하며, 더 적게 도달하지 않습니다.

README는 네 개의 저장소에서 662개의 과거 커밋에 대한 보정을 보고합니다: fast track은 56개 커밋에서 0개의 문제가 있었고, 점수의 AUC는 0.72에서 0.83이었습니다. 프로젝트는 팀이 자체 기록에서 점수를 확인할 수 있도록 farol calibrate 명령을 제공합니다. README는 점수와 판정 임계값이 몇 개의 저장소에서 확인된 휴리스틱이라고 명시합니다.

런타임 및 CI 안전 모델

farol은 테스트 작업 후에 실행되고 해당 보고서를 재사용하므로 두 번 실행되지 않습니다. README는 대략적인 분석 시간을 제공합니다: 300개 파일 Python 서비스의 경우 약 3초, 대규모 TypeScript 모노레포 또는 콜드 캐시가 있는 Go 서비스의 경우 8-10초, 도우미 출력이 캐시된 경우 base의 경우 1초 미만.

권장 GitHub Actions 설정은 두 개의 작업을 사용합니다. tests 작업은 풀 리퀘스트의 코드를 실행하고 보고서를 유지하므로 비밀이 없고 읽기 전용 토큰이 있습니다. farol 작업은 그 후에 실행되며, 성공 여부와 관계없이 git과 보고서를 읽고 모든 푸시에 새 댓글을 게시합니다. 초안은 건너뛰고, 포크 풀 리퀘스트는 토큰이 읽기 전용이므로 댓글이 없으며, 모든 farol 단계는 continue-on-error입니다. comment 및 usage 명령만 네트워크에 접촉합니다. 오류 메시지는 Datadog에서 복사되지 않고 오류 유형만 복사됩니다.

farol은 Node.js 22+ 및 git, Go 프로젝트의 경우 go, Python 프로젝트의 경우 python3 3.11+가 필요합니다. analyze, html 및 report는 GitHub 외부에서 작동합니다. comment만 GitHub 전용입니다.

알려진 제한 사항

  • 정적 읽기는 런타임에 빌드된 값을 볼 수 없습니다: 계산된 라우트 경로, getattr, importlib, 의존성 주입 컨테이너 및 루프에서 채워진 레지스트리는 보이지 않습니다. 읽을 수 없는 진입점은 PR을 unknown으로 만듭니다.
  • TypeScript 프론트엔드는 React Router, Next.js 및 파일 기반 TanStack Router로 제한됩니다. 코드 기반 TanStack 라우트 트리, Remix 및 React Router 파일 규칙, Vue 및 Svelte는 지원되지 않습니다. JavaScript 파일, 이름이 변경된 파일 및 TypeScript 백엔드는 모듈 수준 영향 범위를 사용합니다.
  • Go에서 chi/gin/echo 그룹 이외의 미들웨어 스택의 라우트 접두사는 적용되지 않으며, 자체 등록 API로 라우터를 래핑하는 프로젝트는 읽히지 않습니다. Python에서 Django REST Framework 라우터는 읽히지 않습니다.
  • 점수와 판정 임계값은 휴리스틱이며 프로젝트는 자체 기록에서 보정할 것을 권장합니다.

Hacker News 댓글 작성자들이 반박한 내용

Show HN 스레드(39점, 25개 댓글)는 네 가지 반복적인 반대를 제기했습니다: 보고서의 신호 대 잡음 비, UI의 취향, 기계 할당 위험 점수의 아이디어, 그리고 도구가 전혀 필요한지에 대한 더 기본적인 질문.

보고서의 밀도가 가장 실질적인 비판이었습니다. 한 댓글 작성자는 다음과 같이 썼습니다:

보고서에 잡음이 많아서 신호 대 잡음 비율이 더 필요합니다. 이미 AI 출력을 너무 많이 읽고 있습니다.

또 다른 사람은 같은 점을 제품 방향 논쟁으로 구성했습니다:

백엔드 데모 보고서가 코드를 읽는 것보다 더 길고 복잡해 보입니다. 저도 비슷한 것을 만들려고 하는데 아직 아무도 제대로 해내지 못했습니다. 위험 지도 / 지원 리뷰는 간결하고 정보 밀도가 높아 주의를 안내해야 합니다. 제 현재 철학은 코드에 주석을 다는 것이지 전체 코드 리뷰 인터페이스를 텍스트 덩어리로 대체하는 것이 아닙니다.

세 번째는 라이브 데모의 순위를 직접 비판했습니다:

백엔드 데모를 보니 첫 줄이 import하는 것을 보라고 했습니다. 정확히 제가 리뷰할 필요가 없는 것입니다.

점수를 신뢰하는 것에 대해 한 댓글 작성자는 말했습니다:

최근에 "LLM을 신뢰 점수에 사용하는 것은 나쁜 생각"이라는 게시물이 있었는데, 위험 점수에 LLM을 사용하는 것이 바로 떠올랐습니다. 나쁜 생각인 것 같습니다. 여전히 인간이 필요한 것이 있다면 위험 평가입니다.

그 반대는 LLM 기반 점수 지정을 구체적으로 겨냥합니다. farol의 분석 경로에는 LLM이 없으며, 저자는 출력이 결정적이고 코드를 읽을 수 없을 때 추측 대신 unknown을 반환한다고 말합니다.

두 댓글은 지원 분류의 전제를 논쟁했습니다. 하나는 단호했습니다: "아니면, 알다시피, 승인하는 변경 사항을 읽을 수도 있습니다." 또 다른 하나는 프레젠테이션을 무시했습니다: "개념은 좋습니다. 위험 지도는 어떤 상황에서는 유용할 수 있지만 UI 디자인이 정말 싫습니다. 인간의 생각이나 관심이 없는 AI 냄새가 납니다."

데모 풀 리퀘스트 자체는 포함된 코드로 주목을 받았습니다. 한 댓글 작성자는 Claude로 생성된 것으로 설명된 예제 변경 사항에 SQLAlchemy 코드베이스 내부의 f-string으로 조립된 SQL이 포함되어 있다고 지적하고, SQLAlchemy의 표현 언어를 사용하면 테이블 매핑 ORM 계층 없이 프로그래밍 방식으로 SQL을 구성할 수 있다고 주장했습니다.

스레드에는 또한 여러 개의 플래그가 지정되거나 죽은 댓글이 있었고, 적어도 한 댓글 작성자는 게시물 자체가 남아 있는 동안 제출자의 자신의 댓글이 왜 플래그가 지정되었는지 물었습니다.

이 도구가 필요한 사람

farol은 AI 에이전트가 코드의 많은 부분을 작성하고 리뷰하지만 인간이 여전히 병합을 누르는 팀을 대상으로 합니다. 그 주장은 좁고 반증 가능합니다: 리뷰 노력은 변경의 위험을 따라야 하며, 승인하는 사람은 변경이 도달하는 것에 대한 결정적이고 재현 가능한 진술이 필요합니다. 스레드의 반대 주장도 equally 좁습니다: diff보다 길거나 import 라인을 먼저 순위 매기는 보고서는 주의를 안내하는 유일한 작업에 실패합니다.

farol은 MIT 라이선스이며, 계정, 호스팅 서비스 또는 유료 등급이 없고, npx @winninjs/farol로 설치하거나 GitHub Action winnin/farol/action으로 사용합니다.

Sources