ADR / WORKTREE ISOLATION

수정 전에 먼저,
어디에 쓰는가

worktree-guard는 ‘현재 브랜치가 main인가’를 묻지 않습니다. 지금 파일이 속한 Git 작업공간이 독립된 worktree인지 판별해 잘못된 checkout을 쓰는 일을 차단합니다.

01 / PROBLEM

문제 상황: 같은 저장소에 여러 작업이 동시에 열린다

AI 에이전트가 main checkout에서 바로 파일을 수정하면 다른 작업의 변경과 섞이고, 어떤 PR에 속한 diff인지 추적하기 어려워집니다. 특히 여러 세션과 sub-agent가 병렬로 실행되면 “현재 터미널의 브랜치”만 보고는 파일의 실제 소유 작업공간을 보장할 수 없습니다.

그래서 이 레포의 규칙은 모든 작업을 별도 worktree에서 시작하게 하고, hooks/worktree-guard.sh는 Write·Edit·MultiEdit 직전에 그 경계를 검사합니다.

02 / LIMITATION

직관적 접근의 한계: cwd와 브랜치명만 믿기

CURRENT CWD

세션의 현재 디렉터리

부모 세션의 cwd를 읽으면 nested sub-agent가 실제 파일을 수정하는 worktree를 놓칠 수 있습니다.

BRANCH NAME

main인지 확인

브랜치 이름만으로는 linked worktree의 Git 메타데이터 구조를 설명하지 못하고, detached 상태도 안전하게 다루기 어렵습니다.

FAIL OPEN

도구 오류면 통과

jq가 없거나 probe payload가 비어 있을 때 조용히 통과하면 정책이 설치 환경에 따라 사라집니다.

03 / SOLUTION

해결 구조: Git의 공통 디렉터리를 경계로 삼기

01파일 경로payload에서 대상 파일을 추출
02작업공간show-toplevel에서 Git 위치를 정규화
03판별git-dir와 git-common-dir 비교
04정책main은 deny, worktree는 allow
05복구기존 worktree 목록과 실행 방법 안내
`git-dir == git-common-dir` → main checkout · 다르면 linked worktree
# hooks/lib/worktree-detect.sh
git_dir="$(git rev-parse --git-dir)"
git_common="$(git rev-parse --git-common-dir)"

[ "$git_dir" = "$git_common" ] && WORKTREE_DETECT=main
[ "$git_dir" != "$git_common" ] && WORKTREE_DETECT=worktree

판별 로직은 hooks/lib/worktree-detect.sh에 한 번만 두고, guard가 이를 재사용합니다. main checkout에서는 deny JSON과 함께 기존 worktree 목록·필요한 설정·다음 명령을 안내합니다. jq가 없으면 payload를 해석할 수 없으므로 guard 자체는 fail-closed로 거부합니다.

이 구조의 핵심은 “안전한 디렉터리 이름”을 추측하는 것이 아니라 Git이 관리하는 메타데이터의 관계를 검사하는 데 있습니다.

ADR TAKEAWAY

경계는 작업 전에 결정되어야 한다

수정 권한은 에이전트의 의도가 아니라 파일이 속한 worktree에서 나온다.

이 판별을 Write/Edit hook에 배치하면 이후의 plan·build·repair 단계가 모두 같은 작업공간 계약을 공유할 수 있습니다.