AI 개발 · 실무
Claude·Gemini·Codex 실전 백서 - 터미널 AI 코딩 도구를 일에 쓰는 법
세 도구 함께 쓰기 - 작성자와 검토자, worktree 로 격리
한 도구가 쓰고 다른 도구가 검토하는 분담, 같은 폴더에서 동시에 돌릴 때의 충돌, git worktree 로 작업 폴더 나누기, 작업 지시서와 결과 보고 형식 통일, 사람이 마지막에 확인할 것, 완성 코드는 작업 목록을 받아 작성 도구·검토 도구·worktree 이름을 배정하고 같은 파일을 건드리는 작업끼리 충돌을 경고하는 분배표 생성기
개발자KR · 원고 갱신
이 장에서 배우는 것
앞 장에서 작업을 작게 나누고 필요한 맥락을 전달하는 방법을 다뤘다. 작업을 나눴다면 이제 누가 어디에서 수정하고, 누가 어떤 근거로 검토할지 정해야 한다. 작은 팀에서 도구를 여러 개 쓰는 이유는 답변의 수를 늘리기 위해서가 아니다. 작성과 검토를 분리하고, 서로 다른 작업이 상대의 파일을 바꾸지 않게 하며, 사람이 확인할 결과를 일정한 형식으로 모으기 위해서다.
이 장은 Claude Code·Gemini CLI·Codex CLI를 작성자와 검토자로 배정하는 팀의 작업 흐름을 다룬다. 제품에 관한 설명은 모두 2026년 10월 기준 예시다. 실제 명령과 지원 범위는 각 도구의 공식 문서에서 확인한다. 완성 프로그램은 도구를 실행하지 않는다. 작업 목록을 읽어 역할과 작업 폴더를 배정하고, 같은 파일을 수정할 예정인 작업끼리 경고하는 분배표 생성기를 만든다.
- 작성자와 검토자의 책임을 나누고 검토 대상을 고정한다.
- 같은 작업 폴더에서 동시에 수정할 때 생기는 충돌을 설명한다.
- 깃 워크트리(git worktree)로 작업 폴더와 브랜치를 나눈다.
- 작업 지시서와 결과 보고서를 같은 형식으로 작성한다.
- 분배표의 경고와 실제 테스트·차이 검토를 구분한다.
문제 상황
작은 팀이 요청 목록을 관리하는 Python 프로그램을 고친다고 하자. 첫 작업은 상태 이름을 검증하는 함수를 추가하는 일이다. 두 번째 작업은 내보내기 결과에 상태를 포함하는 일이다. 세 번째 작업은 명령 도움말을 정리하는 일이고, 네 번째 작업은 상태 변경 이력을 남기는 일이다. 각각 짧은 요청처럼 보이므로 팀원들은 여러 터미널을 열고 서로 다른 도구에 작업을 맡긴다.
문제는 첫 번째와 두 번째 도구가 같은 폴더의 같은 파일을 읽고 있다는 데서 시작한다. 한쪽은 함수를 추가하고, 다른 쪽은 조금 전 읽은 내용을 바탕으로 파일을 다시 쓴다. 마지막 저장이 앞선 수정을 지울 수 있다. 저장이 줄 단위로 잘 섞였더라도 결과가 올바르다는 보장은 없다. 함수 이름을 바꾼 수정과 기존 이름을 호출하는 수정이 함께 남으면 실행 단계에서 실패한다.
테스트 결과도 흔들린다. 작성자는 자신이 수정한 코드가 통과했다고 보고하지만, 테스트가 실행되는 동안 다른 도구가 파일을 바꿨을 수 있다. 검토자는 변경 내용을 읽는 중인데 작성자가 추가 수정을 한다. 이렇게 얻은 “문제없음”은 어느 상태에 대한 판단인지 알기 어렵다.
팀의 첫 합의: 한 작업에는 작성자 하나와 작업 폴더 하나를 배정한다. 검토를 시작할 때는 대상 커밋을 기록하고, 검토 중 작성자가 추가 수정하면 새 커밋을 대상으로 다시 확인한다.
이 합의는 도구의 성능 비교와 관계가 없다. 사람 두 명이 같은 파일을 동시에 편집할 때도 필요한 규칙이다. AI 도구를 여러 개 쓴다고 이 문제가 사라지지 않으며, 수정 속도가 빨라지면 충돌을 알아차릴 시간이 더 짧아질 수 있다.
작성자와 검토자의 책임을 나눈다
작성자는 요구사항에 맞는 변경을 만들고 실행 근거를 남긴다. 검토자는 그 변경에서 잘못된 가정, 빠진 경계 조건, 범위를 벗어난 수정을 찾는다. 서로 다른 도구를 배정하면 다른 관점의 지적을 얻을 수 있지만, 검토가 독립적이거나 정확하다는 보장은 없다. 두 도구가 같은 요구사항을 잘못 이해하면 같은 결론에 도달할 수 있다.
따라서 검토 요청에는 작성자의 설명만 넣지 않는다. 원래 요구사항, 변경 범위, 기준 커밋과 대상 커밋, 실행한 테스트와 결과를 함께 준다. 검토자는 우선 수정하지 않고 발견 사항을 보고한다. 검토자가 직접 고쳐야 한다면 역할을 다시 정하고, 그 수정도 새로운 검토 대상으로 삼는다.
| 도구 | 작성 역할 | 검토 역할 | 운영 원칙 |
|---|---|---|---|
| Claude Code | 지시서 범위의 구현과 테스트 | 요구사항과 변경 내용 대조 | 작성·검토 폴더를 명시한다. |
| Gemini 계열 도구 | 지시서 범위의 구현과 테스트 | 경계 조건과 누락 확인 | 개인 환경의 Antigravity CLI와 기업·API 키 환경의 Gemini CLI 구분을 확인한다. |
| Codex CLI | 지시서 범위의 구현과 테스트 | 변경 내용 검토에 review 하위 명령 활용 가능 | 명령 사용 여부와 별개로 대상 커밋을 기록한다. |
표의 역할은 각 도구에만 있는 능력을 뜻하지 않는다. 모두 팀이 부여하는 책임이다. 완성 코드에서는 세 도구를 순환 배정하지만, 실제 업무에서는 언어와 저장소에 대한 팀의 경험, 검토 가능 시간, 실행 환경을 고려해 배정을 바꿀 수 있다. 도구 이름보다 중요한 것은 같은 작업에서 작성자와 검토자가 구별되는지다.
검토 보고는 칭찬이나 전체 요약보다 재현 가능한 발견 사항을 앞에 둔다. 각 항목에 파일 위치, 발생 조건, 예상 동작과 실제 동작의 차이를 쓴다. 발견한 문제가 없다면 “문제없음”만 쓰지 않고 무엇을 읽고 실행했는지와 확인하지 못한 범위를 함께 남긴다.
검토 요청 예시: 기준 커밋과 대상 커밋 사이의 변경을 검토하라. 상태 검증과 내보내기 요구사항을 기준으로 오류와 회귀 가능성을 찾아라. 파일은 수정하지 말고 발견 사항, 재현 조건, 확인한 범위, 남은 불확실성을 보고하라.
읽기 중심의 검토라고 해서 테스트 실행에 부수 효과가 없는 것은 아니다. 테스트는 임시 파일이나 캐시를 만들 수 있다. 검토용 폴더에서도 저장소가 정한 테스트 방법을 따르고, 실행 전후의 작업 상태를 확인한다. 여기서는 권한 설정 자체보다 누가 어느 상태를 검토했는지에 초점을 둔다.
작업 폴더를 나누고 통합 순서를 정한다
워크트리는 한 저장소에 연결된 별도의 작업 폴더를 만든다. 각 폴더는 자신의 체크아웃 상태와 인덱스를 가지므로 서로 다른 브랜치의 파일을 따로 편집할 수 있다. 다만 저장소의 객체와 일부 관리 정보는 공유한다. 작업 폴더가 나뉘었다고 저장소와 실행 환경 전체가 독립된 것은 아니다.
팀은 작업마다 다른 브랜치와 폴더를 배정한다. 기존 저장소에서 기준 커밋을 확인한 뒤 다음 명령을 실행하는 상황을 생각해 보자. 아래 명령은 설명용이며 완성 프로그램이 실행하지 않는다. 폴더와 브랜치가 이미 있으면 상태를 먼저 확인한다.
git worktree add -b task/t01 ../wt-t01 <기준커밋>
git worktree add -b task/t02 ../wt-t02 <기준커밋>
git worktree list
첫 두 명령은 같은 기준에서 서로 다른 브랜치와 작업 폴더를 만드는 예다. 각 도구는 자신에게 배정된 폴더에서 실행한다. 요청문에 폴더 이름을 쓰는 것에 더해, 실행 직전에 현재 위치와 체크아웃된 브랜치를 확인한다. 다른 도구를 켰다는 사실만으로 작업 위치가 달라지지는 않는다.
검토용 폴더도 따로 만들 수 있다. 작성자가 결과를 커밋한 뒤, 아래처럼 특정 커밋을 분리된 상태로 체크아웃하면 검토 대상을 고정하기 쉽다. 검토 중 작성 브랜치가 앞으로 이동해도 검토용 폴더의 대상은 자동으로 바뀌지 않는다.
git worktree add --detach ../review-t01 <검토대상커밋>
git diff <기준커밋> <검토대상커밋>
git status --short
워크트리가 해결하는 것은 동시에 같은 작업 파일을 덮어쓰는 문제다. 서로 다른 브랜치에서 같은 파일을 수정했다면 나중에 통합할 때 충돌할 수 있다. 서로 다른 파일을 고쳐도 함수 계약이나 데이터 형식이 어긋날 수 있다. 따라서 폴더 격리와 통합 계획을 함께 세워야 한다.
예를 들어 상태 검증 함수가 먼저 만들어져야 내보내기 작업이 그 함수를 호출할 수 있다면 의존 순서를 정한다. 두 작업이 같은 파일을 수정하지만 서로 독립된 부분이라면 동시에 작성한 뒤 통합 담당자가 차이를 검토할 수도 있다. 파일 중복 경고는 병렬 작업 금지 판정이 아니라 조정이 필요한 지점을 알려 주는 신호다.
폴더 밖의 자원도 확인한다. 두 작업의 테스트가 같은 포트를 사용하거나 같은 로컬 데이터 파일에 쓰면 워크트리를 나눠도 간섭한다. 이 장의 프로그램은 선언된 파일 경로만 비교하므로 이런 실행 자원 충돌은 찾지 못한다.
작업 지시서와 결과 보고를 같은 형식으로 만든다
도구를 바꿀 때마다 요청 형식을 새로 만들면 비교할 수 있는 근거가 줄어든다. 팀은 지시서에서 목적과 완료 조건을 분리하고, 수정 허용 범위를 구체적으로 적는다. “관련 파일 정리”처럼 범위가 넓은 표현 대신 예상 파일 목록과 범위를 넓혀야 할 때의 보고 방법을 정한다.
| 기록 | 필수 항목 | 좋은 내용의 기준 |
|---|---|---|
| 작업 지시서 | 작업 ID, 목적, 완료 조건, 파일 범위, 기준 커밋, 작업 폴더 | 다른 사람이 성공 여부를 확인할 수 있다. |
| 작성 결과 | 대상 커밋, 변경 요약, 실행 명령과 결과, 미완료 사항 | 실제로 실행한 결과와 예상 결과를 구분한다. |
| 검토 결과 | 대상 커밋, 발견 사항, 재현 조건, 검토 범위 | 지적을 코드와 입력으로 확인할 수 있다. |
| 통합 기록 | 통합 순서, 충돌 해결 내용, 재실행 결과, 사람의 판단 | 통합된 상태에서 확인한 근거가 남는다. |
작업 지시서 예시: T01. 목적은 알 수 없는 상태 이름의 저장을 막는 것이다. 완료 조건은 허용된 상태의 저장 성공과 그 밖의 상태의 명시적 거부다. 예상 수정 파일은 src/status.py와 tests/test_status.py다. 작업 폴더는 ../wt-t01이다. 기준 커밋은 실행 전에 기록한다. 추가 파일 수정이 필요하면 이유와 범위 변경을 보고한다.
작성 결과 예시: 대상 커밋을 기록했다. 상태 검증과 두 경계 조건 테스트를 추가했다. 실행한 테스트 명령과 종료 상태를 첨부했다. 내보내기 작업과의 통합은 아직 확인하지 않았다.
사람은 마지막에 세 가지를 직접 확인한다. 첫째, 지시한 요구사항이 실제 사용자 동작으로 이어지는지 본다. 둘째, 검토한 커밋과 통합하려는 커밋이 같은지 확인한다. 셋째, 통합된 코드에서 테스트를 다시 실행하고 최종 차이(diff)를 읽는다. 각각의 작업이 통과했다는 보고를 합쳐도 통합된 결과의 통과를 대신할 수 없다.
다음 프로그램은 이 기록의 출발점인 분배표를 만든다. 입력 순서가 바뀌어도 작업 ID 순서로 배정하며, 작성자와 검토자는 서로 다르게 정한다. 작업 폴더 이름은 작업 ID에서 만들고, 같은 파일을 선언한 작업을 모아 경고한다. 결과를 실행 근거와 함께 읽는 연습을 위해 작은 자체 검사도 포함한다.
완성 코드
아래 내용을 main.py로 저장한다. Python 3.12 이상에서 표준 라이브러리만 사용한다. 입력 파일은 임시 폴더에 직접 생성하고 읽은 뒤 자동으로 정리한다. 네트워크에 연결하거나 실제 브랜치·워크트리를 만들지 않는다. 배정 규칙과 경고 결과를 살펴보는 모형이다.
import json
import re
import tempfile
from collections import defaultdict
from pathlib import Path, PurePosixPath
TOOLS = ("Claude Code", "Gemini CLI", "Codex CLI")
SAMPLE = [
{
"id": "T03",
"title": "도움말 정리",
"files": ["src/cli.py", "tests/test_cli.py"],
},
{
"id": "T01",
"title": "상태 검증",
"files": ["src/status.py", "tests/test_status.py"],
},
{
"id": "T04",
"title": "변경 이력",
"files": ["src/status.py", "src/history.py"],
},
{
"id": "T02",
"title": "내보내기",
"files": ["src/export.py", "./src/status.py"],
},
]
def normalize_file(value):
if not isinstance(value, str) or not value.strip():
raise ValueError("파일 경로는 빈 문자열일 수 없다.")
if "\\" in value:
raise ValueError("파일 경로에는 /만 사용한다.")
path = PurePosixPath(value)
if path.is_absolute() or ".." in path.parts:
raise ValueError("저장소 내부의 상대 경로만 허용한다.")
if path.as_posix() == ".":
raise ValueError("파일 경로가 필요하다.")
return path.as_posix()
def validate_tasks(raw):
if not isinstance(raw, list):
raise ValueError("작업 목록은 배열이어야 한다.")
tasks = []
seen = set()
for item in raw:
if not isinstance(item, dict):
raise ValueError("각 작업은 객체여야 한다.")
task_id = item.get("id")
if not isinstance(task_id, str) or not re.fullmatch(
r"T[0-9]{2}", task_id
):
raise ValueError("작업 ID는 T와 두 자리 숫자로 쓴다.")
if task_id in seen:
raise ValueError("작업 ID가 중복되었다.")
seen.add(task_id)
title = item.get("title")
if not isinstance(title, str) or not title.strip():
raise ValueError("작업 제목이 필요하다.")
if any(char in title for char in "\r\n\t"):
raise ValueError("작업 제목은 한 줄로 쓴다.")
files = item.get("files")
if not isinstance(files, list) or not files:
raise ValueError("수정 파일 목록이 필요하다.")
tasks.append({
"id": task_id,
"title": title.strip(),
"files": sorted({normalize_file(name) for name in files}),
})
return sorted(tasks, key=lambda task: task["id"])
def make_plan(tasks):
rows = []
owners = defaultdict(list)
for index, task in enumerate(tasks):
rows.append({
**task,
"writer": TOOLS[index % len(TOOLS)],
"reviewer": TOOLS[(index + 1) % len(TOOLS)],
"worktree": "wt-" + task["id"].lower(),
})
for name in task["files"]:
owners[name].append(task["id"])
conflicts = {
name: ids
for name, ids in sorted(owners.items())
if len(ids) > 1
}
return rows, conflicts
def self_check():
tasks = validate_tasks(SAMPLE)
rows, conflicts = make_plan(tasks)
if any(row["writer"] == row["reviewer"] for row in rows):
raise AssertionError("작성자와 검토자가 같다.")
if len({row["worktree"] for row in rows}) != len(rows):
raise AssertionError("작업 폴더 이름이 겹친다.")
if conflicts != {"src/status.py": ["T01", "T02", "T04"]}:
raise AssertionError("예상한 파일 중복을 찾지 못했다.")
repeated = [{
"id": "T01",
"title": "중복 경로 검사",
"files": ["src/a.py", "./src/a.py"],
}]
if make_plan(validate_tasks(repeated))[1]:
raise AssertionError("한 작업의 중복 경로를 충돌로 셌다.")
for bad_path in ("../secret", "/tmp/file", "src/../file"):
try:
normalize_file(bad_path)
except ValueError:
continue
raise AssertionError("금지한 경로를 허용했다.")
if make_plan(validate_tasks(list(reversed(SAMPLE)))) != (
rows, conflicts
):
raise AssertionError("입력 순서에 따라 배정이 달라졌다.")
def main():
self_check()
with tempfile.TemporaryDirectory(prefix="task-plan-") as folder:
input_path = Path(folder) / "tasks.json"
input_path.write_text(
json.dumps(SAMPLE, ensure_ascii=False),
encoding="utf-8",
)
raw = json.loads(input_path.read_text(encoding="utf-8"))
rows, conflicts = make_plan(validate_tasks(raw))
print("작업 분배표")
print("ID | 작업 | 작성자 | 검토자 | worktree")
for row in rows:
print(
f"{row['id']} | {row['title']} | {row['writer']} | "
f"{row['reviewer']} | {row['worktree']}"
)
print()
print("파일 중복 경고")
if conflicts:
for name, ids in conflicts.items():
print(f"- {name}: {', '.join(ids)}")
else:
print("- 없음")
print()
print("자체 검사: 통과")
print("주의: 선언한 파일 경로만 비교한다.")
print("실제 worktree 생성과 AI 실행은 수행하지 않았다.")
if __name__ == "__main__":
main()
줄별 해설
가져오기와 TOOLS. json은 작업 목록을 파일로 저장하고 읽는다. tempfile은 실행마다 사용할 임시 폴더를 제공한다. defaultdict는 파일별 담당 작업을 모으는 데 쓴다. Path는 실제 입력 파일을 다루고 PurePosixPath는 선언된 경로 문자열을 다룬다. 두 역할을 구분해야 존재하지 않는 예제 파일을 읽으려 하지 않는다.
SAMPLE. 입력은 일부러 T03부터 시작한다. 정렬 규칙이 작동하는지 출력으로 확인하기 위해서다. T02의 ./src/status.py는 T01과 T04의 src/status.py와 같은 선언으로 처리해야 한다. 이름이 조금 다르게 적혔다고 중복 경고가 빠지지 않게 하는 예다.
normalize_file. 빈 경로와 역슬래시를 거부하고, 절대 경로와 상위 폴더 이동을 거부한다. 이어서 ./ 같은 표기를 정리한 문자열을 돌려준다. 여기서 정규화는 파일 시스템 접근 권한을 보장하지 않는다. 심볼릭 링크를 따라가지 않고 실제 파일의 존재도 검사하지 않는다. 분배표에서 사용할 저장소 상대 경로의 표기 규칙을 정하는 함수다.
validate_tasks. 입력 전체와 각 항목의 자료형을 확인한 뒤 작업 ID를 검사한다. ID를 T와 숫자 두 자리로 제한하므로 이 예제는 T00부터 T99까지 표현할 수 있다. ID 중복을 거부해야 같은 폴더 이름이 두 작업에 배정되지 않는다. 제목의 줄바꿈과 탭도 거부해 출력 표의 행이 깨지지 않게 한다.
파일 목록의 집합 변환. 한 작업이 src/a.py와 ./src/a.py를 함께 적었어도 담당 작업은 하나다. 정규화한 경로를 집합으로 만든 뒤 정렬하면 이를 한 번만 센다. 작업 전체도 ID로 정렬해 입력 파일의 나열 순서가 배정 결과를 바꾸지 않게 한다. 다만 새 작업을 중간에 추가하면 뒤 작업의 순번과 도구 배정은 달라질 수 있다. 운영 중 확정한 배정을 유지하려면 별도의 배정 기록이 필요하다.
make_plan. 작성자는 순번을 도구 수로 나눈 나머지로 고른다. 검토자는 바로 다음 도구를 선택하므로 현재 세 도구 목록에서는 작성자와 다르다. worktree 값은 실제 경로가 아니라 제안 이름이다. rows에는 역할을 넣고 owners에는 각 파일을 수정한다고 선언한 작업 ID를 모은다.
conflicts. 파일별 작업 수가 둘 이상인 항목만 남긴다. 세 작업이 같은 파일을 만져도 경고는 파일당 한 줄이다. 작업 쌍을 모두 펼치는 방식보다 무엇을 조정해야 하는지 읽기 쉽다. 경로와 작업이 정렬되어 있으므로 경고 순서도 일정하다.
self_check. 작성·검토 역할의 중복, 폴더 이름 중복, 예상 경고, 한 작업 안의 경로 중복, 금지 경로, 입력 순서 변경을 확인한다. 단순히 출력문을 되풀이하는 검사보다 배정 규칙의 실패를 찾는 데 초점을 둔다. 실패하면 AssertionError로 실행을 중단한다. 이 검사는 팀의 실제 프로그램 테스트를 대신하지 않는다.
main. 예제 입력을 JSON 파일로 쓴 뒤 다시 읽는다. 임시 폴더의 무작위 이름은 출력하지 않으므로 화면 결과는 결정적이다. 폴더를 정리한 뒤 분배표와 경고를 출력한다. 마지막 두 문장은 결과의 범위를 명시한다. 분배표 생성 성공을 도구 실행 성공이나 실제 충돌 해소로 오해하지 않게 한다.
실행 결과
main.py를 저장한 폴더에서 문법 검사와 실행을 차례로 수행한다. 다음은 터미널 명령 예시다. 문법 검사가 성공하면 별도 출력 없이 끝난다.
python3 -W error -m py_compile main.py
python3 main.py
두 번째 명령의 예상 출력은 다음과 같다.
작업 분배표
ID | 작업 | 작성자 | 검토자 | worktree
T01 | 상태 검증 | Claude Code | Gemini CLI | wt-t01
T02 | 내보내기 | Gemini CLI | Codex CLI | wt-t02
T03 | 도움말 정리 | Codex CLI | Claude Code | wt-t03
T04 | 변경 이력 | Claude Code | Gemini CLI | wt-t04
파일 중복 경고
- src/status.py: T01, T02, T04
자체 검사: 통과
주의: 선언한 파일 경로만 비교한다.
실제 worktree 생성과 AI 실행은 수행하지 않았다.
도구 이름과 역할은 2026년 10월 기준 예시다. 출력의 Gemini CLI는 배정표의 예제 이름이며 실제 호출을 뜻하지 않는다. 개인 환경에서 Antigravity CLI를 쓰는 팀이라면 도구 목록의 이름을 실제 환경에 맞게 바꾼다. 이 변경은 경로 중복 검사 방식에 영향을 주지 않는다.
경고를 본 팀은 T01·T02·T04의 공통 파일에 대해 순서를 정한다. 상태 검증의 함수 계약을 먼저 합의하고, T01을 통합한 뒤 나머지 작업이 그 상태를 반영하도록 할 수 있다. T03은 선언된 파일이 겹치지 않지만 실제 의존 관계까지 독립적이라고 판정된 것은 아니다.
예상 출력과 다르면 먼저 저장한 코드와 실행 파일이 같은지 확인한다. 결과가 같아도 입력 검사가 충분한지 살펴본다. 실제 프로젝트에 적용하기 전에는 작업 목록을 바꾸고 경고가 의도대로 변하는지 실행하며, 코드 변경 차이를 사람이 읽어야 한다.
실무에서 자주 틀리는 것
같은 도구를 작성자와 검토자에 그대로 넣는다
다음 코드는 실행되지만 작성 역할의 값을 검토 역할에 복사한다. 두 필드가 있다고 역할이 분리되는 것은 아니다. 독립된 검토 절차를 의도했다면 배정 결과 자체를 확인해야 한다.
틀린 코드다.
tools = ("Claude Code", "Gemini CLI", "Codex CLI")
index = 0
writer = tools[index]
reviewer = tools[index]
print(writer == reviewer)
고친 코드다. 다음 도구를 고르고 두 역할이 다른지 검사한다. 도구 이름은 2026년 10월 기준 예시다.
tools = ("Claude Code", "Gemini CLI", "Codex CLI")
index = 0
writer = tools[index % len(tools)]
reviewer = tools[(index + 1) % len(tools)]
if writer == reviewer:
raise ValueError("검토자를 다시 배정해야 한다.")
print(writer != reviewer)
작업 폴더의 전체 경로로 중복을 비교한다
서로 다른 워크트리의 전체 경로는 다르다. 그러나 통합할 때 비교해야 하는 대상은 각 폴더 안의 저장소 상대 경로다. 다음 코드는 같은 논리 파일을 다른 파일로 판단한다.
틀린 코드다.
left = "wt-t01/src/status.py"
right = "wt-t02/src/status.py"
print(left == right)
고친 코드다. 폴더와 수정 파일을 별도 필드로 관리하고, 중복은 수정 파일 필드로 판정한다.
left = {"worktree": "wt-t01", "file": "src/status.py"}
right = {"worktree": "wt-t02", "file": "src/status.py"}
print(left["file"] == right["file"])
실제 경로 문자열에서 임의로 첫 부분을 잘라 상대 경로를 얻는 방식은 피한다. 작업 폴더 구조가 바뀌면 오판할 수 있으므로 입력 형식부터 두 값을 분리한다.
리스트를 곧바로 세어 자기 자신과 충돌시킨다
한 작업이 같은 파일을 두 번 적었다고 두 작업이 충돌한 것은 아니다. 아래 코드는 경로의 표기도 정리하지 않고 항목 수만 센다.
틀린 코드다.
files = ["src/a.py", "./src/a.py"]
print(len(files))
고친 코드다. 완성 프로그램에서는 경로 유효성 검사까지 수행하지만, 여기서는 표기 정리와 중복 제거만 보여 준다.
from pathlib import PurePosixPath
files = ["src/a.py", "./src/a.py"]
unique_files = {PurePosixPath(name).as_posix() for name in files}
print(len(unique_files))
파일 중복이 없으면 통합 가능하다고 보고한다
파일 목록은 위험 신호를 찾는 자료다. 실제 코드를 실행하거나 함수 계약을 확인한 결과가 아니다. 다음 판단은 분배표가 보장하지 않는 결론을 출력한다.
틀린 코드다.
conflicts = {}
if not conflicts:
print("통합 가능")
고친 코드다. 확인된 사실까지만 보고하고 후속 검증을 남긴다.
conflicts = {}
if not conflicts:
print("선언된 파일의 중복 없음")
print("통합된 코드의 테스트와 변경 검토 필요")
도구의 검토 결과도 같은 원칙으로 읽는다. 오류를 발견하지 못했다는 보고와 오류가 없다는 증명은 다르다. 사람이 요구사항, 실행 결과, 최종 변경 내용을 함께 확인해야 한다.
한눈에 보기
| 단계 | 팀의 약속 | 남길 근거 | 남는 한계 |
|---|---|---|---|
| 작업 배정 | 작성자와 검토자를 구별한다. | 작업 ID와 역할표 | 도구가 다르다고 판단이 정확해지지는 않는다. |
| 폴더 분리 | 작업마다 브랜치와 워크트리를 나눈다. | 폴더·브랜치·기준 커밋 | 포트와 외부 파일은 공유될 수 있다. |
| 중복 조정 | 같은 파일의 변경 순서를 정한다. | 파일별 경고와 조정 결정 | 다른 파일의 의미 충돌은 놓친다. |
| 검토 | 대상 커밋을 고정한다. | 발견 사항과 검토 범위 | 추가 수정은 다시 확인해야 한다. |
| 통합 | 사람이 최종 결과를 확인한다. | 통합 후 테스트와 최종 차이 | 개별 작업의 통과만으로 판단할 수 없다. |
다음 장에서는 이 흐름을 팀 도입 기준으로 확장한다. 여기서 만든 분배표와 결과 보고 형식은 누가 무엇을 했는지 추적하는 출발점이 된다. 실제 운영 규칙은 도구 이름보다 작업 위치, 검토 대상, 검증 근거를 중심으로 유지한다.
연습 문제
- T05 작업을 추가하라. 제목은 “내보내기 테스트”, 수정 파일은 tests/test_export.py로 한다. 작성자·검토자·작업 폴더와 파일 중복 경고의 변화를 실행 전에 예상하라.
- T02의 파일 목록에 src/status.py를 한 번 더 추가하라. 경고의 T02가 두 번 나오지 않는 이유를 설명하고 실행해서 확인하라.
- 두 작업이 서로 다른 파일에서 같은 함수 계약을 바꾼다. 파일 중복 경고가 없는데도 오류가 생기는 사례를 하나 만들고, 지시서에 추가할 항목과 통합 후 확인 방법을 쓰라.
- 검토자가 대상 커밋을 읽은 뒤 작성자가 수정 커밋을 추가했다. 기존 검토 결과를 어떻게 기록하고 어떤 범위를 다시 확인할지 설명하라.
정답과 해설
- T05는 정렬 후 다섯 번째이므로 작성자는 Gemini CLI, 검토자는 Codex CLI, 폴더 이름은 wt-t05다. 도구 이름은 2026년 10월 기준 예시다. tests/test_export.py는 다른 작업의 선언에 없으므로 기존 src/status.py 경고만 남는다. 실행 전 예상과 실제 출력을 대조한다.
- normalize_file이 ./src/status.py와 src/status.py를 같은 문자열로 만들고, validate_tasks의 집합 변환이 한 작업 안의 중복을 제거한다. 따라서 파일별 담당 목록에는 T02가 한 번만 들어간다. 자체 검사에도 같은 성질을 확인하는 사례가 있다.
- 예를 들어 생산자가 상태를 문자열 대신 정수로 반환하도록 바꾸고, 소비자는 문자열 비교를 유지하면 파일이 달라도 동작이 어긋난다. 지시서에 공통 함수의 입력·출력 계약과 의존 작업을 기록한다. 통합 후 생산자와 소비자를 함께 호출하는 테스트를 실행하고 실제 출력까지 확인한다.
- 기존 보고에는 원래 대상 커밋을 그대로 남긴다. 추가 커밋이 이전 검토에 포함된 것처럼 기록하지 않는다. 새 대상 커밋을 정하고 추가 변경과 기존 코드에 미치는 영향을 다시 검토한다. 변경이 계약이나 제어 흐름에 영향을 주면 관련 테스트와 검토 범위를 넓힌다. 사람은 최종 통합 대상과 검토 대상이 일치하는지 확인한다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.