Claude Code 실전 - 계획 모드·세션 이어가기·하위 에이전트·훅
이 장에서 배우는 것
작은 팀이 터미널 AI 코딩 도구를 함께 쓰기 시작하면, 코드를 빨리 만드는 일보다 변경을 설명하고 다시 확인하는 일이 중요해진다. 요청을 받은 즉시 편집하게 두면 조사 중 발견한 제약이 구현에 반영되지 않을 수 있다. 대화를 이어 간다고 해서 저장소의 현재 상태까지 자동으로 설명되는 것도 아니다. 이 장에서는 조사와 계획을 먼저 검토하고, 편집 뒤 검사를 붙여 작업을 끝까지 확인하는 흐름을 만든다.
제품 기능과 명령은 모두 2026년 10월 기준 예시다. Claude Code 2.1.x에서 확인된 범위만 사용한다. 완성 프로그램은 Claude Code를 실행하지 않는다. Python 3.12 이상 표준 라이브러리로 파일 변경을 감지하고 문법 검사를 수행하여, 편집 뒤 훅이 어떤 역할을 맡는지 재현한다.
- 계획 모드(plan mode)에서 조사 결과와 구현 계획을 먼저 받아 검토한다.
- 최근 대화와 지정한 세션을 이어 쓰되, 현재 파일 상태를 다시 확인한다.
- 하위 에이전트(subagent)에 범위가 분명한 조사를 맡기고 근거를 회수한다.
- 훅(hook)으로 검사 실행을 연결하되, 검사 성공과 작업 완료를 구분한다.
- 조사→계획→구현→검증→커밋의 흐름을 작은 감시기로 연습한다.
문제 상황
세 사람이 작은 Python 저장소를 관리한다고 하자. 한 사람은 파일 처리 기능을 고치고, 다른 사람은 테스트를 보완하며, 나머지 사람은 변경 내용을 검토한다. 각자 AI 도구를 쓰지만 작업을 끝냈다는 기준은 같지 않다. 어떤 사람은 답변에 “검사를 통과했다”는 문장이 있으면 끝으로 보고, 다른 사람은 실제 실행 결과를 요구한다.
어느 날 AI가 Python 파일을 편집한 뒤, 이전 대화에서 실행했던 검사를 근거로 작업 완료를 보고한다. 그러나 마지막 편집은 그 검사 이후에 이루어졌다. 다른 작업자는 어제의 세션을 이어 받아 이미 바뀐 함수 이름을 기준으로 수정을 시작한다. 조사 범위를 정하지 않고 맡긴 보조 작업에서는 관련 없는 파일까지 변경된다. 개별 행동은 작아 보여도, 팀에서는 어떤 상태를 검증했는지 알기 어려워진다.
팀은 우선 한 가지 규칙을 세운다. “파일을 편집하면 그 변경에 대한 검사 결과가 있어야 한다.” 모든 검사를 한꺼번에 자동화하려 하지 않고 Python 문법 검사부터 붙인다. 문법 검사는 동작의 정답을 보장하지 않지만, 마지막 편집 이후에도 코드가 컴파일되는지는 빠르게 확인할 수 있다.
이번 작업의 납품물은 main.py 한 파일이다. 프로그램이 임시 폴더에 검사 대상을 만들고, 정상 코드·문법 오류 코드·수정된 코드를 차례로 저장한다. 감시기는 각 저장 직후 폴더를 확인하고, 내용이 바뀐 파일만 검사한다. 실패 기록도 남기며, 마지막에는 파일이 그대로인 경우 검사를 반복하지 않는지 확인한다. 이처럼 입력과 종료 조건을 고정하면 팀원이 같은 예제를 실행해 같은 결과를 비교할 수 있다.
계획과 세션을 작업의 경계로 삼기
2026년 10월 기준 예시로 Claude Code의 --permission-mode plan은 계획 모드를 선택하는 옵션이다. 시작 예시는 다음과 같다. 계획을 받는 단계에서는 요청문에도 편집 금지와 조사 범위를 적는다. 앞 장에서 정한 권한 경계는 그대로 적용하고, 계획에 동의했다는 이유로 모든 후속 명령까지 허용했다고 해석하지 않는다.
claude --permission-mode plan
현재 저장소에서 Python 파일을 저장한 뒤 문법 검사를 실행할 지점을 조사하라. 지금은 파일을 편집하지 말라. 관련 파일과 확인한 근거를 제시하고, main.py 한 파일로 재현할 구현 계획과 검증 항목을 작성하라. 외부 패키지와 네트워크는 사용하지 않는다.
좋은 계획에는 변경할 파일, 변경 이유, 실패 처리, 검증 방법이 함께 들어 있다. “감시기를 만든다”만으로는 부족하다. 무엇을 변경으로 볼지, 첫 확인에서 기존 파일을 검사할지, 오류가 난 파일도 다음 확인에서 건너뛸지까지 정해야 구현과 검증이 연결된다.
이번 예제에서는 파일 내용의 SHA-256 요약값으로 변경을 판정한다. 처음 발견한 파일은 검사한다. 내용이 같으면 건너뛴다. 문법 오류가 나더라도 해당 내용을 확인한 사실은 기억한다. 따라서 같은 오류를 매번 중복 기록하지 않으며, 파일이 다시 수정되면 다시 검사한다. 파일 삭제와 검사 도중의 동시 편집은 이번 구현 범위에서 제외한다.
| 항목 | 이번 작업의 선택 | 확인 방법 |
|---|---|---|
| 대상 | 임시 폴더 바로 아래의 Python 파일 | 대상 파일 목록을 정렬한다. |
| 변경 판정 | 파일 내용의 요약값 비교 | 같은 내용의 재확인을 건너뛴다. |
| 검사 | py_compile로 문법 검사 | 정상·오류·수정 입력을 차례로 넣는다. |
| 기록 | 실행 순번과 결과를 JSON 줄로 저장 | 기록 수와 결과 순서를 검사한다. |
2026년 10월 기준 예시로 Claude Code의 -c 또는 --continue는 최근 대화를 이어 가고, -r 또는 --resume은 세션을 지정해 이어 간다. 최근 작업 하나만 진행했다면 -c가 편리하다. 여러 작업을 번갈아 했다면 이어 갈 세션을 구분하는 편이 낫다. 다음 두 명령은 서로 다른 선택이다. 두 번째 명령의 세션 식별자는 실제 값으로 바꾼다.
claude -c
claude -r "세션 식별자"
세션을 이어 받았을 때 첫 요청은 “계속 구현하라”보다 현재 상태의 재확인으로 시작하는 편이 좋다. 대화에 담긴 계획과 파일의 실제 내용은 서로 다른 근거다. 다른 사람이 커밋했거나 사용자가 직접 편집했다면 이전 대화의 설명이 낡았을 수 있다.
앞선 계획을 요약한 뒤 현재 브랜치와 변경 내역을 다시 확인하라. 계획 이후 달라진 파일이 있으면 먼저 설명하라. 검증 결과는 실행한 명령과 당시 변경 상태를 구분해 보고하라.
이 요청은 대화 기록을 버리라는 뜻이 아니다. 대화는 작업 의도와 판단의 배경을 보존하고, 저장소는 현재 구현 상태를 보여 준다. 둘을 맞춰 본 다음 다음 행동을 정한다. 새 세션을 열 때도 합의한 범위, 남은 문제, 마지막으로 확인한 결과를 짧게 전달하면 같은 원칙을 적용할 수 있다.
조사 위임과 외부 도구 연결의 범위 정하기
2026년 10월 기준 예시로 Claude Code에는 하위 에이전트 기능이 있으며 --agents로 사용자 정의 하위 에이전트를 지정할 수 있다. 정의 형식은 설치한 버전의 공식 문서에서 확인한다. 여기서는 설정 형식을 외우기보다, 위임할 일을 어떻게 잘라 전달할지에 집중한다.
이번 작업에서는 구현자와 조사자의 책임을 구분한다. 조사자는 현재 저장소에서 검사 관례와 관련 파일을 찾아 보고한다. 구현자는 그 보고를 확인하고 계획에 반영한다. 조사자가 제시한 결론만 받지 말고 파일 경로와 판단 근거도 받는다. 보고서의 문장이 그럴듯한지보다 실제 파일 내용과 맞는지를 확인해야 한다.
하위 에이전트에 조사만 맡겨라. Python 검사와 관련된 기존 파일을 찾아 경로, 현재 실행 방식, 이번 감시기에 적용할 제약을 보고하게 하라. 파일 편집과 커밋은 맡기지 말라. 확인하지 못한 항목은 추정이라고 표시하게 하라.
반환 형식을 미리 정하면 조사 결과를 통합하기 쉽다. “파일 경로 / 확인한 사실 / 구현에 주는 영향 / 미확인 사항” 정도면 충분하다. 주 작업자는 이를 읽고 필요한 파일을 다시 확인한다. 서로 다른 구현을 동시에 진행해야 한다면 작업 폴더를 git worktree로 나누되, 그 운영 자체는 이 장에서 확장하지 않는다.
2026년 10월 기준 예시로 세 도구에서 연결과 실행을 지정하는 방식은 아래처럼 다르다. 이 표는 동일한 권한 동작을 보장한다는 뜻이 아니다. 이름이 비슷해도 모드와 연결의 실제 범위는 각 도구의 공식 문서에서 확인해야 한다.
| 도구 | 계획·실행 경계 | MCP 연결 진입점 |
|---|---|---|
| Claude Code | --permission-mode plan | --mcp-config |
| Antigravity CLI | --mode plan | mcp 하위 명령 |
| Gemini CLI | 모드 옵션은 공식 문서에서 확인 | ~/.gemini/settings.json |
| Codex CLI | --sandbox와 --ask-for-approval로 경계 지정 | mcp 하위 명령 |
모델 문맥 프로토콜(MCP)은 외부 기능을 도구로 연결하는 접점이다. 연결할 수 있다는 사실과 해당 도구를 호출해도 된다는 판단은 구분한다. 예를 들어 저장소와 연결된 검색 도구가 있어도, 이번 감시기 구현에는 외부 조회가 필요하지 않다. 필요한 작업만 로컬에서 끝내면 재현할 때 연결 상태에 의존하지 않는다.
2026년 10월 기준 예시로 Claude Code는 --mcp-config로 MCP 서버 설정 파일을 지정할 수 있다. 아래 명령은 팀이 검토한 설정 파일이 이미 있는 경우의 형태다. 설정 파일의 서버 실행 명령, 접근 대상과 인증 정보 취급을 확인한 뒤 사용한다. 이 장의 프로그램 실행에는 이 파일도, MCP 서버도 필요하지 않다.
claude --mcp-config ./team-mcp.json
설정의 전체 구조와 서버 구성은 별도 연결 작업에서 다룰 내용이다. 여기서는 “이 작업에 연결이 필요한가”를 계획 단계에서 결정하면 된다. Claude Code 공식 문서는 기능과 설정을 확인하는 출발점으로 사용한다. 문서의 예제를 그대로 가져오기보다 현재 작업의 입력·권한·검증 조건에 맞춰 구성을 검토한다.
편집 뒤 검사를 작업 완료와 연결하기
2026년 10월 기준 예시로 Claude Code는 settings.json을 사용하며 훅 기능을 제공한다. 실제 편집 후 훅의 이벤트 이름, 입력 형식, 종료 상태 처리와 설정 키는 설치한 버전의 공식 문서에서 확인해야 한다. 이 장에서는 확인되지 않은 설정을 제시하지 않고, 파일 변경 뒤 검사를 호출하는 구조를 Python으로 재현한다.
실제 편집 이벤트에 연결된 훅은 이벤트를 계기로 실행된다. 이번 감시기는 호출될 때 폴더를 확인하는 폴링(polling) 방식이다. 무한 반복이나 대기 시간 없이, 파일을 저장한 직후 scan을 호출한다. 따라서 실행이 바로 끝나면서도 “변경 감지→검사→기록”이라는 핵심 연결을 관찰할 수 있다.
검사 실패는 기록하고 작업 완료 판단에 반영해야 한다. 실패 메시지를 출력하기만 하고 마지막에 성공으로 요약하면 자동화가 오히려 혼동을 만든다. 반대로 실패한 내용을 매번 새 변경으로 취급하면 같은 오류 기록만 쌓인다. 마지막으로 본 내용과 마지막 검사 결과를 구분해 생각하면 이 두 문제를 함께 피할 수 있다.
이번 작업의 실제 진행은 다음처럼 정리한다. 조사 결과를 받은 뒤 계획의 범위를 확정하고, 구현 후에는 아래 고정 시나리오를 실행한다. 그다음 변경 내역을 검토하여 의도하지 않은 파일이나 설정이 섞이지 않았는지 확인한다. 마지막 수정 이후의 실행 결과가 확보되었을 때 커밋한다.
- 조사: 기존 검사 방식과 변경 대상 파일을 확인한다.
- 계획: 내용 비교, 문법 검사, 실패 기록, 종료 조건을 합의한다.
- 구현: 외부 의존성 없이 main.py를 작성한다.
- 검증: 직접 실행하고 내부 검증과 출력, 변경 내역을 확인한다.
- 커밋: 검증한 변경을 기록하며 문법 검사만 확인했다는 한계도 설명한다.
변경 검토에는 다음 명령을 사용한다. 이 예제에서는 실제 커밋 명령을 프로그램에 넣지 않는다. 커밋은 검사 결과와 변경 내용을 확인한 작업자가 수행한다.
git diff
완성 코드
다음 코드를 main.py로 저장한다. 생성되는 대상 파일, 컴파일 결과와 검사 기록은 모두 임시 폴더 안에 둔다. 프로그램이 끝나면 임시 폴더도 정리된다. 실제 시각과 임시 경로는 출력하지 않고 검사 순번만 사용하므로 출력이 일정하다.
import hashlib
import json
import py_compile
import tempfile
from pathlib import Path
class Watcher:
def __init__(self, root: Path) -> None:
self.root = root
self.cache = root / "_compiled"
self.cache.mkdir()
self.log = root / "checks.jsonl"
self.seen: dict[str, str] = {}
self.sequence = 0
def scan(self) -> int:
checked = 0
for path in sorted(self.root.glob("*.py")):
digest = hashlib.sha256(path.read_bytes()).hexdigest()
if self.seen.get(path.name) == digest:
continue
record = self.check(path)
with self.log.open("a", encoding="utf-8") as stream:
stream.write(json.dumps(record, ensure_ascii=False) + "\n")
self.seen[path.name] = digest
checked += 1
print(
f'{record["sequence"]}: {record["file"]} '
f'{record["status"]} ({record["detail"]})'
)
return checked
def check(self, path: Path) -> dict[str, object]:
self.sequence += 1
status = "PASS"
detail = "문법 정상"
try:
py_compile.compile(
str(path),
cfile=str(self.cache / f"{path.name}.pyc"),
dfile=path.name,
doraise=True,
)
except py_compile.PyCompileError as error:
if not isinstance(error.exc_value, SyntaxError):
raise
status = "FAIL"
detail = f"SyntaxError, 줄 {error.exc_value.lineno}"
return {
"sequence": self.sequence,
"file": path.name,
"status": status,
"detail": detail,
}
def main() -> None:
scenarios = [
("처음 저장", "def double(value):\n return value * 2\n"),
("오류 저장", "def double(value):\n return * 2\n"),
("수정 저장", "def double(value):\n return value + value\n"),
]
with tempfile.TemporaryDirectory(prefix="edit-check-") as directory:
root = Path(directory)
target = root / "sample.py"
watcher = Watcher(root)
counts = []
for label, source in scenarios:
print(f"[{label}]")
target.write_text(source, encoding="utf-8")
counts.append(watcher.scan())
print("[변경 없음]")
unchanged = watcher.scan()
print(f"검사한 파일: {unchanged}")
records = [
json.loads(line)
for line in watcher.log.read_text(encoding="utf-8").splitlines()
]
statuses = [record["status"] for record in records]
sequences = [record["sequence"] for record in records]
if counts != [1, 1, 1] or unchanged != 0:
raise RuntimeError("변경 감지 검증 실패")
if statuses != ["PASS", "FAIL", "PASS"]:
raise RuntimeError("검사 결과 검증 실패")
if sequences != [1, 2, 3]:
raise RuntimeError("기록 순서 검증 실패")
if records[1]["detail"] != "SyntaxError, 줄 2":
raise RuntimeError("오류 위치 검증 실패")
passed = statuses.count("PASS")
failed = statuses.count("FAIL")
print(f"기록: {len(records)}건, PASS={passed}, FAIL={failed}")
print("검증: 통과")
if __name__ == "__main__":
main()
줄별 해설
앞부분의 다섯 가져오기 문장은 내용 비교, 기록, 문법 검사, 임시 폴더, 경로 처리를 준비한다. 외부 패키지는 사용하지 않는다. Path를 사용하면 경로를 문자열로 이어 붙일 필요가 줄어들고, 검사 대상과 기록 파일의 위치도 명확해진다.
Watcher의 초기화 부분은 검사 결과물의 위치와 기억할 상태를 정한다. _compiled는 컴파일 결과를 넣는 하위 폴더이고 checks.jsonl은 한 줄에 하나의 JSON 객체를 저장하는 기록 파일이다. seen에는 파일 이름별로 마지막으로 확인한 내용의 요약값을 보관한다. sequence는 시각 대신 사용할 검사 순번이다.
scan의 checked는 이번 호출에서 검사한 파일 수다. root.glob("*.py")는 이번 범위인 바로 아래 Python 파일을 고르고, sorted는 여러 파일이 있을 때 처리 순서를 일정하게 만든다. 하위 폴더에 둔 컴파일 결과는 이 대상에 포함되지 않는다. 대상과 결과물을 분리하는 것은 감시기가 자기 결과물에 다시 반응하는 일을 피하는 기본 설계다.
read_bytes로 읽은 내용에서 요약값을 구한 다음 seen과 비교한다. 수정 시각을 비교하지 않으므로 짧은 간격으로 저장해도 내용이 달라졌다면 검사한다. 반대로 같은 내용을 다시 저장하면 검사하지 않는다. 요약값 비교는 파일 내용을 읽는 비용이 있으므로, 큰 저장소에 적용할 때는 감시 범위와 읽기 비용을 별도로 검토해야 한다.
check가 반환한 결과는 JSON으로 기록한 뒤 seen을 갱신한다. 여기서 seen은 “성공한 코드”의 목록이 아니다. 문법 오류도 이미 확인한 내용으로 기억한다. 실패가 해결되었다는 뜻은 아니며, 같은 내용에 대한 중복 검사를 생략한다는 뜻이다. 기록 파일에 쓰지 못하는 문제는 숨기지 않고 예외로 드러나게 둔다.
check 안의 py_compile.compile은 Python 소스를 컴파일하지만 대상 모듈의 코드를 실행하지 않는다. doraise=True는 컴파일 오류를 예외로 전달하도록 한다. cfile로 결과 위치를 지정하고 dfile로 진단에 쓸 파일 이름을 지정한다. 임시 폴더의 임의 경로를 사용자 출력에 포함하지 않는 데에도 도움이 된다.
예외 처리에서는 문법 오류만 예상된 검사 실패로 바꾼다. 오류 문장 전체 대신 SyntaxError와 줄 번호를 기록하므로 진단 문구의 세부 차이에 덜 의존한다. 그 밖의 컴파일 관련 오류는 다시 발생시킨다. 파일 접근 실패까지 문법 오류라고 기록하면 운영 문제의 원인을 잘못 설명하게 된다.
main의 scenarios는 세 번의 편집을 고정한다. 첫 코드는 정상이고 두 번째 코드는 둘째 줄에 문법 오류가 있다. 세 번째 코드는 다른 정상 표현으로 고친다. 이 문자열은 검사 입력 데이터이며 main.py 자체의 문법 오류가 아니다. 따라서 예제를 실행하는 프로그램과 검사 대상의 실패를 구분할 수 있다.
마지막 검증 부분은 기록 파일을 다시 읽는다. 메모리에 기대한 결과를 만들어 놓고 그것만 출력하는 대신, 실제로 쓴 기록을 확인한다. 변경 때마다 한 건씩 검사했는지, 변경이 없을 때는 건너뛰었는지, 성공·실패·성공 순서와 오류 위치가 맞는지를 검사한다. 조건이 틀리면 RuntimeError로 끝나므로 마지막 성공 문장에 도달하지 않는다.
이 프로그램에는 검사와 대상 파일 편집이 동시에 일어나지 않는다. 운영용 감시기에서는 내용 비교 직후 다른 프로세스가 파일을 바꾸는 상황도 고려해야 한다. 읽은 내용과 검사한 내용의 일치를 보장하거나 다시 확인하는 장치가 필요하다. 이 한계를 기록해 두면 예제를 그대로 운영 환경에 옮기는 일을 피할 수 있다.
실행 결과
macOS 또는 Linux에서 Python 3.12 이상으로 다음 명령을 실행한다. 별도 입력이나 설치 단계는 없다. 아래는 코드의 고정 시나리오에 대한 예상 출력이다.
python3 main.py
[처음 저장]
1: sample.py PASS (문법 정상)
[오류 저장]
2: sample.py FAIL (SyntaxError, 줄 2)
[수정 저장]
3: sample.py PASS (문법 정상)
[변경 없음]
검사한 파일: 0
기록: 3건, PASS=2, FAIL=1
검증: 통과
중간의 FAIL은 예제에서 의도한 입력에 대한 결과다. 마지막 “검증: 통과”는 실패를 숨겼다는 뜻이 아니라, 감시기가 예상한 실패를 기록하고 수정 뒤 다시 검사했다는 뜻이다. 독자는 직접 실행한 출력과 이 예상 출력을 비교해야 한다. AI의 설명에 성공이라고 적혀 있다는 사실은 실행 증거를 대신하지 못한다.
또한 PASS는 문법이 정상이라는 의미다. 함수가 요구한 값을 반환하는지, 경계 입력을 처리하는지, 기존 동작이 유지되는지는 별도 테스트가 필요하다. 커밋 전에 main.py의 변경 내역을 읽고 외부 명령 실행이나 예상하지 못한 경로 접근이 추가되지 않았는지도 확인한다.
실무에서 자주 틀리는 것
내용 대신 파일 크기만 비교한다
다음 코드는 실행할 수 있지만 변경 판정에는 맞지 않는다. 서로 다른 코드가 같은 바이트 수를 가질 수 있기 때문이다. 출력이 True라고 해서 내용까지 같은 것은 아니다.
before = b"value = 1\n"
after = b"value = 2\n"
print(len(before) == len(after))
내용을 기준으로 비교하도록 고친다. 아래 코드는 변경되었으므로 True를 출력한다. 완성 코드에서는 이 요약값을 파일 이름별로 저장한다.
import hashlib
before = b"value = 1\n"
after = b"value = 2\n"
changed = hashlib.sha256(before).digest() != hashlib.sha256(after).digest()
print(changed)
검사 결과를 마지막 파일 하나로 덮어쓴다
여러 파일을 검사할 때 성공 여부를 매번 대입하면 앞선 실패가 뒤의 성공에 가려진다. 다음 잘못된 예는 True를 출력한다.
results = [False, True]
all_passed = True
for result in results:
all_passed = result
print(all_passed)
전체 완료 조건이라면 모든 결과를 모아 판정한다. 다음 코드는 False를 출력한다. 다만 완성 코드의 시나리오는 실패 감지 자체도 검증하므로, 모든 기록이 성공이어야 한다는 조건 대신 예상한 결과 순서를 확인한다.
results = [False, True]
all_passed = all(results)
print(all_passed)
문법 검사를 동작 테스트로 해석한다
다음 코드는 문법상 정상이다. 그러나 이름으로 기대한 동작과는 다르게 값을 세 배로 만든다. 컴파일 성공만 보고 이 함수를 승인하면 요구사항 오류를 놓친다.
def double(value: int) -> int:
return value * 3
compile("def double(value):\n return value * 3\n", "sample.py", "exec")
print("문법 검사 통과")
구현을 수정하고 기대 결과를 검사한다. 문법 검사와 동작 테스트는 서로 다른 질문에 답한다. 최종 검토에서는 테스트가 요구사항을 제대로 표현했는지도 읽어야 한다.
def double(value: int) -> int:
return value * 2
if double(4) != 8 or double(-2) != -4:
raise RuntimeError("동작 검증 실패")
print("동작 검사 통과")
한눈에 보기
| 단계 | 남길 근거 | 완료 판단 |
|---|---|---|
| 조사 | 파일 경로, 확인한 사실, 미확인 사항 | 구현에 필요한 제약을 설명할 수 있다. |
| 계획 | 변경 범위, 실패 처리, 검증 항목 | 무엇을 검사할지 합의했다. |
| 세션 재개 | 이전 의도와 현재 변경 내역의 차이 | 현재 파일에 맞는 다음 행동을 정했다. |
| 편집 후 검사 | 파일별 결과와 실패 정보 | 최신 편집에 대한 검사 결과가 있다. |
| 커밋 | 직접 실행한 결과와 검토한 차이 | 검증 범위와 남은 한계를 설명할 수 있다. |
2026년 10월 기준 예시로 이 장에서 사용한 Claude Code의 접점은 계획 모드, -c/-r 세션 재개, 하위 에이전트, 훅, --mcp-config다. 접점의 이름보다 중요한 것은 각 행동을 어떤 근거로 시작하고 어떤 결과로 끝낼지 정하는 일이다. 설정과 모드의 세부 동작이 바뀌어도 이 검토 구조는 적용할 수 있다.
연습 문제
- 오류 코드를 저장한 직후 내용을 바꾸지 않고 scan을 한 번 더 호출하라. 추가 기록이 생겨야 하는지 설명하고 검사 수를 검증하라.
- 처음 저장하는 파일을 sample.py와 helper.py 두 개로 늘려라. helper.py는 정상 코드로 유지한다. 첫 검사 순서와 전체 기록 수를 예상한 뒤 실행 결과로 확인하라.
- 기록 객체에 검사 종류를 나타내는 check 필드를 추가하고 값을 syntax로 고정하라. 모든 기록에 해당 값이 저장되었는지 기록 파일을 다시 읽어 확인하라.
- 정상 코드에 동작 오류를 넣어 문법 검사가 놓치는 경우를 설계하라. 문법 검사 결과와 별도 동작 테스트 결과를 어떻게 보고할지 설명하라.
정답과 해설
- 추가 scan의 반환값은 0이어야 하며 기록도 늘어나지 않아야 한다. seen은 성공 여부가 아니라 마지막으로 확인한 내용을 기억한다. 따라서 같은 문법 오류를 다시 발견해도 중복 기록하지 않는다. 이후 내용을 수정하면 요약값이 달라져 다시 검사한다. 이 정책을 적용하려면 실패 기록을 사용자가 확인할 수 있어야 한다.
- 정렬된 파일 이름에 따라 첫 확인에서는 helper.py가 sample.py보다 먼저 검사된다. 첫 확인에서 두 건, 이후 sample.py의 오류와 수정에서 각각 한 건이 생겨 전체 네 건이다. helper.py가 바뀌지 않으면 다시 검사하지 않는다. 기존 순번·결과 검증도 새 시나리오에 맞게 바꿔야 한다. 출력 문장만 고치고 검증 조건을 그대로 두면 프로그램은 실패해야 한다.
- check의 반환 사전에 check 필드를 넣고 syntax를 저장한다. 다시 읽은 모든 기록에서 이 값이 syntax인지 검사한다. 이후 다른 검사를 추가할 때도 PASS라는 상태만으로 무엇을 확인했는지 추측하지 않아도 된다. 검사 종류와 결과를 함께 남기면 기록의 의미가 분명해진다.
- 예를 들어 double이 입력에 2를 곱해야 하는데 3을 곱하도록 작성한다. 문법 검사에는 통과하지만 double(4)가 8인지 확인하는 테스트는 실패한다. 보고에는 문법 검사 통과와 동작 테스트 실패를 함께 적고 작업을 완료로 처리하지 않는다. 코드를 고친 뒤 마지막 변경에 대해 두 검사를 다시 실행하고 변경 내역까지 검토한다.