AI가 썼다는 브라우저 300만 줄에서 112만 줄은 남의 코드였다

작년 말부터 AI가 브라우저를 만들었다는 얘기가 계속 지나갔다. 에이전트 2,000개를 동시에 돌려서 일주일 만에 300만 줄, 렌더링 엔진은 Rust로 밑바닥부터, 커스텀 JS VM까지. OS 쪽도 비슷했다. Claude와 64세션 해서 커널부터 데스크톱 환경까지 올렸다는 프로젝트가 별 1,500개를 받고 있었다.

숫자만 놓고 보면 감이 안 왔다. 300만 줄이 큰 건 아는데, 그게 어떤 300만 줄인지는 트윗으로 알 수 없다. 기사도 대체로 그 트윗을 옮긴 것이었다. 그래서 레포를 클론했다. 브라우저 하나, OS 두 개. 여기에 리눅스 커널까지 네 개를 받아서 줄을 세고, 커밋을 세고, 빌드를 걸었다.

세는 건 잘 됐다. 300만 줄은 진짜였다. git ls-files '*.rs' | xargs wc -l 이 3,162,904를 돌려줬고, 파일은 실제로 거기 있었다. 라이브러리는 릴리스 프로파일로 1분 35초 만에 빌드까지 됐다.

그다음에 테스트를 돌려보려다가 멈췄다. cargo test 가 테스트를 실행하지 못한 게 아니라, 테스트 빌드가 컴파일되지 않았다. 에러 두 개 중 하나는 한 파일 안에 mod tests 가 두 번 선언돼 있다는 것이었다. 241번째 줄에 하나, 965번째 줄에 하나.


밝은 창고 바닥에 큰 나무 상자가 열려 있고 안이 남이 포장해 넣은 작은 상자들로 가득 찬 오버헤드 구도

무엇을 어떻게 셌는지 먼저

재현할 수 있게 방법부터 적는다. 측정일은 2026년 8월 11일, macOS 26.2 / aarch64, cargo 1.91.1이다.

git clone https://github.com/wilsonzlin/fastrender.git
git clone https://github.com/kaansenol5/VibeOS.git
git clone https://github.com/smadi-a/ASOS.git
git clone --filter=blob:none --no-checkout --single-branch \
  https://github.com/torvalds/linux.git linux-meta

줄 수는 전부 git ls-files ... | xargs wc -l 이다. 즉 주석과 빈 줄을 포함한 물리 줄 수다. SLOC이 아니다. 이 기준을 모든 프로젝트에 똑같이 적용했으니 프로젝트끼리 비교하는 데는 문제가 없지만, 절대값을 "짠 코드의 양"으로 읽으면 과대평가가 된다. 미리 밝혀둔다.

한 번 틀렸던 것도 적어둔다. 파일이 많으면 xargs 가 여러 배치로 나뉘고 wc -ltotal 행을 배치마다 뱉는다. 처음에 tail -1 만 봐서 마지막 배치 합계를 전체 합계로 읽었다. Rust 파일 전체가 251,548줄로 나왔는데 vendor를 뺀 값이 2,036,324줄로 나오는, 부분이 전체보다 큰 결과를 보고서야 알았다. awk로 total 행을 합산하도록 고쳤다.

리눅스는 blobless 클론을 썼다. 파일 내용은 안 받고 커밋 메타데이터만 받는다. 1.7GB에서 멈췄고, 이걸로 커밋 메시지를 정확히 grep할 수 있다. GitHub 검색 API로도 세어봤는데 2,009건이 나왔고, 그중에 2014년 커밋이 섞여 있었다. 열어보니 Assisted-by: Daniel Borkmann, 사람 이름이었다. 검색 API는 토큰을 느슨하게 매칭한다. 그래서 API 숫자는 버리고 로컬 git log 결과를 썼다.


300만 줄의 내역

FastRender는 Cursor를 만드는 Anysphere의 연구 프로젝트다. CEO Michael Truell이 1월에 이렇게 적었다. GPT-5.2로 브라우저를 만들었고, 일주일 무중단으로 돌았고, 3M+ 줄이고, 렌더링 엔진은 Rust로 from-scratch이며 HTML 파싱·CSS 캐스케이드·레이아웃·텍스트 셰이핑·페인트·커스텀 JS VM이 들어 있다고.

커밋부터 셌다. 29,858개. "거의 3만 개"라는 설명과 맞는다. 2025년 11월 16일 첫 커밋, 2026년 1월 18일 마지막 커밋. 두 달이다. 저자 이름은 대부분 Wilson Lin(25,924)이고 나머지는 root@ip-10-140-...ec2.internal 같은 EC2 인스턴스 호스트명이다. 에이전트가 사람 계정으로 커밋한다. 딱 하나 Agent 19 <agent19@example.com> 이라는 저자가 커밋 하나를 남겼는데, 이름을 설정하는 단계에서 뭔가 어긋난 흔적으로 보인다.

줄 수는 이렇게 나뉜다.

.rs 전체가 3,162,904줄이다. 그중 vendor/ 가 1,126,580줄로 35.6%, src/ 가 1,738,354줄로 55.0% 다. tests/.rs 만 세어 177,442줄로 5.6%, 나머지인 crates·xtask·benches·fuzz 가 120,528줄로 3.8% 다.

vendor/ 가 112만 줄이다. 전체의 3분의 1이 넘는다. 안에 뭐가 있냐면 ecma-rs, taffy, selectors 세 개다. taffy는 Dioxus 팀의 레이아웃 엔진이고, selectors는 Servo의 CSS 셀렉터 매칭 크레이트다. 둘 다 남이 쓴 코드를 레포 안으로 복사해 넣은 것이다.

그리고 ecma-rs 하나가 1,082,164줄이다. 300만 줄 중 34%가 이 디렉터리 하나다.


ecma-rs가 어디서 왔는지

wilsonzlin/ecma-rs 를 GitHub API로 조회했다. 생성일 2022년 10월 3일. FastRender가 시작되기 3년 전이다. 설명은 "TypeScript parser, type checker, minifier, and compiler". 브라우저용으로 급조된 게 아니라 원래 있던 개인 프로젝트다.

이게 FastRender에 들어온 커밋을 찾았다.

e26c5ce15c07  2026-01-09  chore(js): vendor ecma-rs and update paths

1월 9일. 프로젝트 마지막 커밋이 1월 18일이니까 끝나기 아흐레 전이다. 반입 시점의 크기를 그 커밋에서 직접 셌다.

  • 반입 시점: 888 파일 / 292,010줄
  • HEAD 시점: 2,561 파일 / 1,082,164줄

그러니까 29만 줄이 통째로 들어왔고, 그 후 아흐레 동안 79만 줄이 더 붙었다. 그 아흐레 동안 이 디렉터리를 건드린 커밋이 8,272개다. 사람이 3년에 걸쳐 만든 파서 위에 에이전트가 아흐레 만에 그 세 배를 얹은 셈이다.

어느 쪽으로 요약해도 한쪽이 지워진다. "JS 엔진은 원래 있던 걸 가져다 쓴 것"이라고 하면 79만 줄이 지워지고, "에이전트가 JS 엔진을 밑바닥부터 썼다"고 하면 29만 줄과 3년이 지워진다. 레포에는 둘 다 남아 있다.


"from-scratch"라는 말

렌더링 엔진이 from-scratch인지는 import문을 보면 된다.

src/dom.rs:14: use html5ever::parse_document;
src/dom.rs:15: use html5ever::tendril::TendrilSink;
src/dom.rs:16: use html5ever::tree_builder::QuirksMode as HtmlQuirksMode;

html5ever는 Servo의 HTML5 파서다. src/ 안 21개 파일이 이걸 쓴다. 역시 Servo인 cssparser는 31개 파일에서 쓰인다. taffy는 18개 파일. Cargo.lock 에는 크레이트가 760개 들어 있고 그 안에 servo_arc, markup5ever, rustybuzz, tiny-skia, wgpu, winit 이 다 있다.

트윗에는 "from-scratch in Rust with HTML parsing"이라고 적혀 있다. 레포에서 HTML 파싱은 Servo 코드를 부른다.

Wilson Lin 본인은 좀 더 정확하게 말했다. 에이전트가 의존성을 스스로 골랐고, from-scratch가 목표였는데도 레이아웃에 taffy를 끌어왔고, JS 엔진을 다른 에이전트가 작업 중이라는 걸 알고서 자기 작업을 막지 않으려고 QuickJS를 임시로 넣었다고. 실제로 Cargo.toml 62번째 줄에 quickjs = ["dep:rquickjs"] 가 남아 있다.

같은 프로젝트를 두고 CEO의 트윗과 엔지니어의 글이 다르게 말한다. 그리고 레포는 엔지니어 쪽에 가깝다.


빌드는 됐다

여기까지는 세기만 한 거고, 도는지는 다른 문제다.

cargo build --release

libaom-sys 빌드 스크립트에서 멈췄다. cmake: not found. 이건 프로젝트 잘못이 아니라 내 맥에 cmake가 없어서다. AVIF 이미지 디코딩이 기본 feature에 들어 있고 그게 libaom을 CMake로 빌드한다. 프로젝트 결함으로 적으면 부정확하니 그렇게 쓰지 않는다.

README에 CI용으로 --no-default-features 를 쓰라고 적혀 있어서 그걸로 다시 걸었다.

Finished `release` profile [optimized + debuginfo] target(s) in 1m 35s
warning: `fastrender` (lib) generated 6 warnings

빌드됐다. 1분 35초, 경고 6개. 산출물 libfastrender.rlib 이 413,499,016바이트다. 413MB짜리 단일 라이브러리. 경고 6개는 전부 let mut 인데 안 바꾸는 변수라는, 사소한 종류였다.

이건 그냥 인정하고 넘어갈 부분이다. 에이전트가 두 달 동안 쏟아낸 170만 줄의 Rust가 컴파일된다. Rust는 컴파일러가 까다로운 언어라서 이게 공짜로 되는 일은 아니다.


테스트는 안 됐다

레포 안에 #[test] 가 vendor 제외 21,549개 있다. 이 정도면 테스트를 꽤 진지하게 쓴 프로젝트다. 돌려봤다.

cargo test --release --no-default-features --lib
error[E0428]: the name `tests` is defined multiple times
   --> src/sandbox/spawn.rs:965:1
    |
241 | mod tests {
    | --------- previous definition of the module `tests` here
...
965 | mod tests {
    | ^^^^^^^^^ `tests` redefined here

error[E0599]: no method named `env_os` found for mutable reference
              `&mut std::process::Command` in the current scope
    --> src/sandbox/macos.rs:1539:8
    |
help: there is a method `envs` with a similar name, but with different arguments

두 개다. 하나씩 본다.

E0428. src/sandbox/spawn.rs 는 1,201줄짜리 파일인데 그 안에 mod tests 가 두 번 있다. 241번 줄 것은 #[cfg(all(test, target_os = "macos"))], 965번 줄 것은 #[cfg(test)] 로 달려 있다. macOS에서 테스트를 빌드하면 두 조건이 동시에 참이 되고, 같은 네임스페이스에 같은 이름의 모듈이 두 개 생긴다.

누가 넣었는지 커밋을 찾았다. 둘 다 2026년 1월 13일이다.

be69df15e  test(macos): cover sandbox-exec env gate in configure_renderer_command
4bc105eef  feat(sandbox): spawn renderer subprocesses with pre_exec sandbox

하나는 테스트를 추가하는 커밋, 하나는 기능을 추가하면서 테스트를 같이 붙인 커밋이다. 같은 날, 같은 파일, 각자 자기 mod tests 를 붙였다. 이 프로젝트는 최대 2,000개 에이전트가 동시에 돌았고 시간당 수천 커밋이 들어갔다. 그 조건에서 두 에이전트가 같은 파일 아래쪽에 각각 테스트 모듈을 여는 건, 일어나면 이상한 일이 아니라 언젠가는 일어날 일이다.

E0599. src/sandbox/macos.rs 1539번 줄이 Command::env_os() 를 부른다. 이 메서드는 Rust 표준 라이브러리에 없다. 로컬 툴체인 소스에서 확인했다.

grep -rn 'fn env_os' ~/.rustup/toolchains/stable-aarch64-apple-darwin/.../process.rs
# (출력 없음)

rustc가 envs 를 대안으로 제시해준다. 그런데 envs 는 시그니처가 다르다. 애초에 Command::envAsRef<OsStr> 를 받으니까 env_os 라는 게 있을 이유가 없다. 이름만 그럴듯한 API를 부른 것이다.

두 에러에 공통점이 있다. 둘 다 macOS 전용 경로다. 하나는 target_os = "macos" 로 게이트돼 있고, 하나는 아예 sandbox/macos.rs 안에 있다. 그리고 에이전트는 EC2 리눅스 인스턴스에서 돌았다.

그래서 이렇게 된다. 리눅스에서는 이 코드가 컴파일 대상에 안 들어간다. 라이브러리 빌드에서는 #[cfg(test)] 가 걸린 모듈이 제외되니까 통과한다. 두 조건이 겹치는 지점, 그러니까 macOS에서 테스트를 빌드할 때만 터진다. 그 조합을 아무도 한 번도 안 돌린 것이다.

결과적으로 21,549개의 #[test] 중 이 환경에서 실행된 건 0개다. 테스트가 실패한 게 아니라 테스트까지 못 갔다.

이건 특정 프로젝트를 흉보려는 얘기가 아니다. 에이전트를 대규모로 병렬로 돌릴 때 CI가 무엇을 컴파일하는지가, 코드를 얼마나 많이 만들어내는지보다 먼저 결정된다는 얘기다. 컴파일되지 않은 코드는 에이전트에게 존재하지 않는 코드다. 피드백이 안 오니까.


외부인이 실제로 켜봤을 때

이슈 목록이 그 답을 이미 갖고 있다.

  • #104 (1월 19일) "Unable to load any websites." google.com, reddit.com, yahoo.com, bing.com, example.com, linux.org, cursor.com 전부 같은 panic. accesskit_consumer 안의 assertion left == right failed.
  • #105 구글 홈에서 검색창 클릭이 안 잡힌다.
  • #113 윈도우에서 빌드 실패. #116 컴파일 불가. #107 최신 CMake에서 빌드 실패.
  • #125 (3월) 번들 로더가 심볼릭 링크를 따라가서 루트 밖 파일을 읽는다.
  • #126 (8월 8일) "No license?" 댓글 0개.

마지막 게 실무에서는 제일 크다. 레포에 라이선스 파일이 없다. GitHub API도 license: null 을 돌려준다. 라이선스가 없는 코드는 기본적으로 저작권자 독점이다. 별 1,569개에 포크 105개가 달려 있고, 그중 어느 것도 법적으로는 쓸 수 없다. 8월 8일에 물어본 사람이 있고 사흘째 답이 없다.

마지막 커밋은 1월 18일이다. 오늘 기준 205일 멈춰 있다.


OS 쪽 — VibeOS

브라우저는 이쯤 하고 OS로 넘어간다. VibeOS는 aarch64용 hobby OS다. README 첫 줄이 "A hobby OS vibecoded completely from scratch with Claude Code"이고, Claude와 64세션 했다고 적혀 있다. 별 1,521개, 포크 113개, MIT.

C/헤더/어셈블리를 다 세면 260,585줄이 나온다. 디렉터리별로 나눠보면 성격이 달라진다.

micropython/ 이 76,874줄, doomgeneric/ 이 73,094줄, tinycc/ 가 50,923줄인데 셋 다 포팅이다. vendor/ 14,932줄은 외부 코드다. 자체 코드는 kernel/user/ 를 합쳐 44,188줄이다.

26만 줄 중 20만 줄이 남의 프로젝트를 올린 것이다. MicroPython, DOOM, Tiny C Compiler. 이걸 깎아내릴 필요는 없다. 없는 OS 위에 이 세 개를 올리려면 파일시스템, 프로세스 로더, libc 흉내, 프레임버퍼가 다 돌아야 한다. 포팅은 실력이다. 다만 "26만 줄짜리 바이브코딩 OS"라고 읽으면 틀린다. 자기 코드는 44,188줄이다.

README의 기능 목록에 "Web browser with HTML/CSS rendering"이 있다. 파일을 찾았다.

/* user/bin/browser.c — 전체 22줄 */
int main(kapi_t *api, int argc, char **argv) {
    char *args[16];
    int n = 0;
    args[n++] = "/bin/micropython";
    args[n++] = "/bin/browser.py";
    ...
    return api->exec_args("/bin/micropython", n, args);
}

C 파일은 런처다. 실제 브라우저는 user/bin/browser.py, 1,099줄짜리 MicroPython 스크립트다. 안을 열면 FONT_SIZES = {'h1': 28, 'h2': 24, 'h3': 20, ...} 같은 태그별 폰트 크기 테이블이 있고, file/http/https를 처리하고, TTF 폰트로 그린다. HTML을 화면에 그리는 건 맞다. 앞에서 본 300만 줄짜리 렌더링 엔진과 같은 목록에 "웹 브라우저"로 나란히 적히는 것뿐이다.


그 브라우저에서 커널까지 뚫린다

VibeOS 이슈 목록에 외부인이 올린 취약점 리포트가 다섯 건 있다. 전부 열려 있다.

가장 눈에 띄는 건 #27이다. 제목이 "1-Click Kernel RCE via Buffer Overflow in dns_resolve (browser.py)".

내용은 이렇다. dns_resolveuint8_t query[512] 라는 고정 버퍼를 쓴다. 브라우저가 HTTP 302 응답의 Location: 헤더를 따라가는데, 그 도메인 이름이 512바이트를 넘으면 버퍼를 넘어선다. NX/DEP 같은 메모리 보호가 없으니 PC를 덮어쓸 수 있고, 그러면 웹페이지 본문에 심어둔 셸코드로 점프한다. 유저 프로세스가 아니라 커널 권한으로.

경로를 다시 보면, 방금 본 1,099줄짜리 browser.py로 링크 한 번 누르면 OS 전체가 넘어간다.

나머지도 성격이 같다.

  • #25 kernel/elf.celf_load() 가 ELF 헤더의 e_phoff, e_phentsize, e_phnum 을 파일 크기와 대조하지 않고 그대로 쓴다. p_vaddr 를 목적지 주소로 그대로 넣고 memcpy 한다. 범위 밖 읽기 + 임의 주소 쓰기.
  • #28 vfs_lookup RCE.
  • #26 경로 조합을 검증하지 않아서 스택 오버플로.
  • #29 KAPI/FAT32 핸들 위조, 정수 오버플로.

패턴이 하나다. 경계 검사가 빠져 있다. 기능은 다 있다. 파일시스템도 되고 TCP/IP도 되고 TLS도 된다. 되는 경로는 만들어졌고, 안 되는 입력이 들어왔을 때 뭘 할지가 안 만들어졌다. 데모에서 확인되는 건 앞쪽이고, 뒤쪽은 데모로는 안 보인다.

마지막 push는 1월 27일이다. 5월에 올라온 커널 RCE 리포트는 그 뒤로 답이 없다. 196일 멈춰 있다.


ASOS — 8,234줄이 어디서 왔나

마지막은 ASOS다. x86-64 UEFI 부팅, 선점형 멀티태스킹, FAT32, 윈도 매니저, 그리고 1993년 DOOM 포팅까지. 저자는 dev.to에 후기를 썼고 거기서 "25,709줄(DOOM 제외), 82커밋, 8일"이라고 적었다. Claude 웹앱을 설계 상담역으로, Claude Code를 구현역으로 나눠 썼고, 초과 사용료가 125호주달러 나왔다는 것까지 적어뒀다. 솔직한 글이었다. 자기가 Claude의 설명을 다 이해하지 못한 채 작은 결정들을 믿고 넘어갔다는 것도 써놨다.

레포를 세보니 숫자가 조금 달랐다.

  • 커밋: 83개 (82개로 적혀 있다. 오차 1)
  • 기간: 2026-03-06 → 2026-03-21, 16일 (8일로 적혀 있다)
  • DOOM 제외 C/헤더/어셈블리: 17,028줄 (105개 파일)

25,709와 17,028의 차이를 찾다가 디렉터리 목록에서 발견했다.

kernel.bak/   6,747줄
user.bak/     1,487줄

.bak 디렉터리 두 개, 94개 파일, 합쳐서 8,234줄이 커밋에 들어 있다. 17,028 + 8,234 = 25,262. 저자가 센 25,709와 거의 맞는다. 나머지 400줄쯤은 링커 스크립트나 Makefile 차이일 것이다.

.o, .elf 같은 빌드 산출물도 96개가 추적되고 있다.

이걸 부풀리기라고 부르기는 좀 그렇다. 그냥 wc -l 을 디렉터리에 걸면 나오는 숫자를 그대로 쓴 것이다. 다만 AI와 빠르게 작업할 때 생기는 특유의 흔적이 이거다. 되던 상태를 통째로 복사해두고 다음을 시도한다. git이 하는 일을 디렉터리 복사로 대신한다. 그리고 그 복사본이 커밋에 남는다.

README에 한계를 직접 적어둔 건 좋았다. VM에서만 동작한다(USB 스택과 HID 드라이버가 없다), TCP/IP 스택이 없다, 데스크톱에 최소화·최대화가 없다. 별은 2개다. 마지막 push 이후 143일.


정작 움직인 건 리눅스 쪽이었다

세 프로젝트를 다 재고 나서 남은 인상은 "인상적인데 멈춰 있다"였다. 205일, 196일, 143일. 셋 다 화제가 됐을 때 딱 멈췄다.

그래서 방향을 돌려서, 아무도 새로 만들지 않은 OS를 봤다. 리눅스 커널이다.

커널은 올해 AI 코딩 도구를 다루는 공식 문서를 만들었다(Documentation/process/coding-assistants.rst). 규칙은 단순하다. AI는 Signed-off-by 를 달 수 없다. DCO는 사람만 법적으로 인증할 수 있으니까. 대신 Assisted-by: 태그를 쓴다. 처음 초안은 Co-developed-by 였는데, 그 태그는 대응하는 Signed-off-by 를 요구하는 규칙이 있어서 충돌했고, 그래서 새 태그를 만들었다.

이 태그 덕분에 셀 수 있다. blobless 클론에 grep을 걸었다.

git log --grep='^Assisted-by:' --no-merges --format='%H' | wc -l
# 1413

1,413개 커밋. 이건 제출된 패치가 아니라 Linus 트리에 머지된 커밋이다.

커미터 날짜 기준으로 월별 비율을 냈다. 커미터 날짜를 쓴 이유는, 작성 날짜로 세면 아직 머지되지 않은 최근 달이 비어 보이기 때문이다.

2026년 2월에는 Assisted-by 태그가 7건, 전체 커밋이 4,535건이라 0.15% 였다. 3월에 78 / 8,959 로 0.87%, 4월에 140 / 6,517 로 2.15%, 5월에 333 / 8,173 로 4.07% 가 됐다. 6월이 503 / 6,227 로 8.08%, 7월이 303 / 2,223 로 13.63%, 8월은 10일까지 44 / 287 로 15.33% 다.

다섯 달 만에 0.15%에서 8%로 갔다.

7월과 8월 숫자는 조심해서 읽어야 한다. 이 구간은 v7.2-rc 후반이라 전체 커밋 수 자체가 적고, 들어오는 패치가 버그픽스 쪽으로 쏠린다. 분모가 작으면 비율이 부풀 수 있다. 다만 2월부터 6월까지 다섯 달만 봐도 추세는 충분히 보인다. 0.15 → 0.87 → 2.15 → 4.07 → 8.08.

어떤 에이전트인지도 태그에 적혀 있다.

claude 가 634건으로 제일 많다. 그다음이 codex 201건, gemini 105건, antigravity 55건이다. copilot 44건, opencode 42건, gemini-cli 30건, cursor 26건이 뒤를 잇는다.

태그 원문은 이런 모양이다.

Assisted-by: Claude:opus-5
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: Antigravity:gemini-3.6-flash
Assisted-by: Hermes:muse-spark-1.2 syzkaller

마지막 줄이 재밌다. 커널 퍼저인 syzkaller를 도구 자리에 같이 적었다. 모델이 혼자 판단한 게 아니라 퍼저가 찾은 크래시를 모델이 정리했다는 기록이다. 사람 이름을 뺀 자리에 도구 이름이 들어가고, 그 도구가 무엇을 근거로 삼았는지까지 커밋 메시지에 남는다.


그래서 쓸만한가

처음 질문으로 돌아간다. AI가 만든 브라우저와 OS는 쓸 만한가.

지금 당장 갖다 쓸 수 있는 건 없다. FastRender는 라이선스가 없어서 법적으로 쓸 수 없고, 어떤 사이트도 못 연다는 이슈가 반년 넘게 열려 있다. VibeOS는 링크 하나로 커널이 넘어가는 리포트가 석 달째 답이 없다. ASOS는 저자 본인이 VM 전용이라고 적어놨다. 셋 다 마지막 커밋 이후 다섯 달에서 일곱 달이 지났다.

그런데 세는 순간 인상이 바뀌는 지점도 있다. 170만 줄짜리 Rust 크레이트가 1분 35초에 경고 6개로 컴파일된다. 44,188줄짜리 커널 위에서 MicroPython과 TCC와 DOOM이 돈다. 17,028줄로 UEFI에서 윈도 매니저까지 올라간다. 2년 전이면 이 중 어느 것도 두 달짜리 개인 프로젝트가 아니었다.

그리고 이 셋을 재는 동안 실제로 굴러가고 있던 건 네 번째였다. 새 OS를 만드는 쪽이 아니라, 30년 된 OS에 패치를 넣는 쪽. 거기서는 2월에 0.15%였던 게 6월에 8%가 됐고, 그 커밋들은 사람 메인테이너의 리뷰를 통과했고, 태그에 어떤 모델이 도왔는지가 적혀 있다.

세 프로젝트에서 나온 고장의 모양도 서로 닮았다. FastRender는 아무도 컴파일해본 적 없는 조합에서 깨졌다. VibeOS는 아무도 넣어본 적 없는 입력에서 깨졌다. ASOS는 되던 상태를 디렉터리째 복사해둔 게 커밋에 남았다. 세 군데 다 만들어지지 않은 곳이 아니라 확인된 적 없는 곳에서 났다.

리눅스 쪽 규칙이 그 자리를 정확히 짚는다. AI가 짠 코드는 받는다. 대신 Assisted-by 로 무엇이 도왔는지 적고, Signed-off-by 는 사람이 단다. 코드를 누가 썼는지와 누가 책임지는지를 두 줄로 갈라놓은 것이다. 1,413개 커밋이 그 두 줄을 달고 들어갔다.


직접 세보려면

이 글의 숫자는 전부 아래로 재현된다. 오래 걸리는 건 리눅스 클론(1.7GB)과 FastRender 빌드(약 350MB 클론 + 릴리스 빌드)다.

# 1. 300만 줄의 내역
git clone https://github.com/wilsonzlin/fastrender.git && cd fastrender
git ls-files '*.rs' | tr '\n' '\0' | xargs -0 wc -l \
  | awk '$2=="total"{t+=$1} END{print t}'          # 3162904
git ls-files 'vendor/**.rs' | tr '\n' '\0' | xargs -0 wc -l \
  | awk '$2=="total"{t+=$1} END{print t}'          # 1126580

# 2. 테스트가 컴파일되는지 (macOS에서)
cargo build --release --no-default-features        # 성공
cargo test  --release --no-default-features --lib  # E0428, E0599

# 3. 커널의 AI 커밋
git clone --filter=blob:none --no-checkout --single-branch \
  https://github.com/torvalds/linux.git linux-meta && cd linux-meta
git log --grep='^Assisted-by:' --no-merges --format='%H' | wc -l
git log --grep='^Assisted-by:' --no-merges \
  --format='%cd' --date=format:'%Y-%m' | sort | uniq -c

리눅스 쪽 숫자는 매달 바뀐다. 이 글의 값은 2026년 8월 11일, HEAD가 d58772d8520c (v7.2-rc7)일 때다.

부팅해서 화면까지 확인하지는 않았다. qemu도 크로스 컴파일러도 이 맥에 없고, 이번에 잰 건 레포다. 화면이 뜨느냐는 다른 글의 재료다.

댓글

이 블로그의 인기 게시물

아이들은 우리 게임에 들어오지 못한다

AI가 준 선택지, 초5 아들은 어떻게 골랐을까?