Ars Technica에서 하버드 워킹페이퍼(Fiona Chen, James Stratton) 기사를 읽었다. 700여 개 회사의 작업 기록 약 3억 건을 분석했더니, AI 코딩 에이전트를 도입한 뒤 PR은 23%, 코드 생산량은 30% 정도 늘었는데 이슈 해결률은 통계적으로 의미 있게 달라지지 않았다고 한다. 병목은 리뷰였다. PR이 머지되기까지 걸리는 시간이 49% 늘었고 수정 요청은 거의 두 배가 됐다.
내 레포도 그런지 재 봤다. 사이드 프로젝트 두 개(아래 레포 A, B)이고, 둘 다 6월 4일에 Claude와 함께 시작했다.
메시지에 Co-Authored-By: Claude 트레일러가 있으면 AI 커밋(245개), 없으면 "트레일러 없는 커밋"(65개)으로 나눴다. 지표는 제목이 fix나 revert로 시작하는 커밋의 비율이다.
| AI 커밋 | 트레일러 없는 커밋 | |
|---|---|---|
| 제목이 fix/revert로 시작 | 38% | 17% |
두 배가 넘는다. 연구와 같은 방향이다(같은 지표는 아니다). 이대로 글을 쓸 뻔했는데, 먼저 깨 보기로 했다.
확인 1: fix:는 누가 제목에 쓰나
AI 커밋의 69%가 feat:, fix: 같은 표준 형식 제목을 쓴다. 트레일러 없는 커밋은 18%다. 고칠 때마다 fix:를 붙이는 에이전트는 제목을 읽는 지표에서 더 많이 잡힌다. fix, bug, revert, 수정, 버그, 고침, 오류 중 하나가 제목 어디에든 들어간 커밋으로 넓히면 격차가 벌써 줄어든다. 43% 대 26%다.
확인 2: 트레일러 없는 커밋은 누구 것인가
이 커밋들을 사람 쪽 비교군으로 두고도 정작 열어 보지 않았다. 65개 중 57개가 6월 5일 하루에 몰려 있었다. 제목은 이런 식이다.
Phase 0-1: Add core modules (logging, pipeline, batch)Phase A: Semantic cache + batch processing integrationEnhancement Level 2: Sentiment analysis & topic classification
뼈대를 세우던 날의 커밋으로 읽힌다. 두 레포 모두 6월 4일의 첫 커밋부터 AI 트레일러가 있어서, AI가 없던 시기는 비교 기준으로 쓸 수도 없다. 이 커밋을 누가 썼는지 나는 말할 수 없다. 그게 문제다. 내 비교군은 누가 만들었는지 모르는 하루치 작업이었다.
확인 3: 커밋 크기
AI 커밋은 한 번에 건드리는 파일이 더 많다. 평균 3.1개이고 다른 쪽은 1.7개다. "같은 파일이 72시간 안에 다시 수정됐나"를 커밋 단위로 세면 큰 커밋이 겹칠 기회가 더 많다. 커밋 단위로는 74% 대 68%였다. 파일 단위로 세서 커밋 크기를 뺐다.
| 측정 방식 | AI 커밋 | 트레일러 없는 커밋 |
|---|---|---|
| 제목이 fix/revert로 시작 | 38% | 17% |
| 제목 어디든 fix·버그·수정 등 포함 | 43% | 26% |
| 건드린 파일이 72시간 안에 다시 수정됨 (파일 단위, 두 레포 합산) | 46% | 47% |
제목과 커밋 크기를 둘 다 뺀 마지막 줄에서는 차이가 없다.
그래서 이 숫자는
두 레포만 가지고는 AI 커밋이 더 자주 고쳐지는지 덜 고쳐지는지 알 수 없다. 연구가 틀렸다는 말도 아니다. 표본이 작고 인과를 보여 주는 것도 아니다. 처음 나온 2배는 의도하지 않고 잰 세 가지에서 왔다. 제목 쓰는 습관, 하루치로 이뤄진 비교군, 커밋 크기.
다음에 AI와 비AI 작업의 격차를 믿기 전에 먼저 이걸 본다.
- 비교군에 누가 들어 있고, 언제 커밋한 건가?
- 내가 세는 라벨이 측정 대상의 습관에서 나온 건 아닌가?
- 두 그룹이 같은 크기의 일을 하고 있나?
내 레포에서 재 보려면
표준 라이브러리만 쓴다. 첫 AI 커밋 이후의 커밋만 비교한다. 다른 도구를 쓴다면 co-authored-by: claude 문자열만 바꾸면 된다.
import subprocess, sys
from collections import Counter
from datetime import datetime
repo = sys.argv[1] if len(sys.argv) > 1 else "."
out = subprocess.run(
["git", "-C", repo, "log", "--no-merges", "--name-only",
"--pretty=format:\x1e%ct\x1f%B\x1f"],
capture_output=True, text=True).stdout
commits = [] # dict: 시각, AI 트레일러 여부, 제목, 건드린 파일
for chunk in out.split("\x1e")[1:]:
t, msg, files = chunk.split("\x1f")
commits.append(dict(t=int(t), ai="co-authored-by: claude" in msg.lower(),
subj=msg.strip().split("\n")[0], files=set(files.split())))
commits.sort(key=lambda c: c["t"])
if not any(c["ai"] for c in commits):
sys.exit("AI 트레일러가 붙은 커밋이 없다. 메시지에 Co-Authored-By: Claude 가 있는지 확인할 것")
start = min(c["t"] for c in commits if c["ai"]) # 첫 AI 커밋 이후만 비교
commits = [c for c in commits if c["t"] >= start]
pairs = {True: [0, 0], False: [0, 0]} # [파일 수, 72시간 안에 다시 수정된 파일 수]
for i, c in enumerate(commits):
for f in c["files"]:
pairs[c["ai"]][0] += 1
if any(c["t"] < d["t"] <= c["t"] + 72 * 3600 and f in d["files"] for d in commits[i + 1:]):
pairs[c["ai"]][1] += 1
for ai in (True, False):
g = [c for c in commits if c["ai"] == ai]
if not g:
continue
fix = sum(c["subj"].lower().startswith(("fix", "revert")) for c in g)
day, k = Counter(datetime.fromtimestamp(c["t"]).date() for c in g).most_common(1)[0]
n_f, hit = pairs[ai]
print("AI " if ai else "트레일러 없음", f"커밋 {len(g)}개 | 제목이 fix/revert로 시작 {fix / len(g):.0%}"
f" | 커밋당 파일 {sum(len(c['files']) for c in g) / len(g):.1f}개"
f" | 파일 단위 72시간 내 재수정 {hit / max(n_f, 1):.0%}"
f" | 가장 몰린 날 {k / len(g):.0%}")
내 두 레포에서는 이렇게 나왔다.
| 레포 | 그룹 | 커밋 | fix/revert 제목 | 커밋당 파일 | 72시간 내 재수정(파일) | 가장 몰린 날 |
|---|---|---|---|---|---|---|
| 레포 A | AI | 145 | 40% | 2.9 | 47% | 10% |
| 트레일러 없음 | 16 | 25% | 1.6 | 52% | 62% | |
| 레포 B | AI | 100 | 36% | 3.4 | 44% | 23% |
| 트레일러 없음 | 49 | 14% | 1.8 | 46% | 96% |
마지막 열은 처음부터 찍어 봤어야 했다. 비교군의 커밋 대부분이 하루에 몰려 있다면, 어떤 배율이든 믿기 전에 그 하루부터 열어 보는 게 낫다.
돌려 보면 "가장 몰린 날" 열이 어떻게 나오는지 궁금하다.
Top comments (0)