에이전트 작업 후 git status 게이트를 강제하려면?
에이전트가 패치를 남긴 뒤 워킹트리가 어떻게 남았는지를 사람이 눈으로만 보면, 다음 턴·PR·푸시에 잡음이 섞입니다. 에이전트 git status 체크는 “깨끗해 보여요”가 아니라 git status --porcelain 출력을 기계적으로 읽어 통과/중단하는 게이트입니다.
이 글은 **어디에 검사? · 더티면 중단? · 의도적 untracked는?**만 다룹니다. agent-test-gate는 테스트/린트 exit code, agent-patch-commits는 커밋 쪼개기, agent-rebase-conflict는 충돌 구간 위임입니다. 여기서는 작업 트리 상태 게이트만 정리합니다. 요금·플랜·제휴·C++ 예제는 없습니다.
근거는 git-status의 --porcelain / -s 형식과, 에이전트 *훅·Done 커맨드·pre- 훅**에 같은 스크립트를 꽂는 관행입니다.
어디에 검사를 넣나?
한 줄 답: 검사는 에이전트 턴 종료(Done/stop 훅) 에 1차로 두고, 사람이 커밋·푸시하기 전(pre-commit / pre-push) 에 2차로 둡니다. “채팅에 status를 붙여 달라”만으로는 게이트가 아닙니다.
배치 후보:
| 위치 | 무엇을 돌리나 | 언제 적합한가 |
|---|---|---|
| 에이전트 Done / stop 훅 | scripts/git-status-gate.sh (porcelain 파싱) | 턴을 끝내기 직전 필수 |
| 에디터/에이전트 커맨드 훅 | 같은 스크립트, 허용 경로만 스코프 | 파일 쓰기 배치 후 즉시 |
| pre-commit | tracked dirty·unmerged 차단 | 사람이 스테이징할 때 |
| pre-push | 브랜치에 올리기 전 최종 | 로컬만 green인 채 푸시 방지 |
| CI (선택) | checkout 후 “빌드가 트리를 더럽히지 않음” | 생성물·포맷 폭주 감시 |
실무 순서:
- 레포에 한 엔트리포인트를 둡니다. 예:
scripts/git-status-gate.sh. - Skill/Rule의 Done에 **“이 스크립트 exit 0 전에는 done/PR/다음 태스크 금지”**를 적습니다.
- 로컬
pre-commit또는pre-push에서 같은 스크립트를 호출합니다. - 게이트는 사람 읽기용
git status가 아니라--porcelain(필요하면-z)을 파싱합니다. 로케일·컬러에 흔들리지 않습니다.
복붙용 Done 게이트:
Gate (must exit 0 before claiming done):
./scripts/git-status-gate.sh
Do not open PR / start next task / push while the gate is red.
Report: full porcelain output on failure.
최소 스크립트 골격(팀 정책에 맞게 분기):
#!/usr/bin/env bash
# scripts/git-status-gate.sh — fail on unexpected dirty tree
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
# Machine-readable; stable across locales (git-status --porcelain)
mapfile -t lines < <(git status --porcelain=v1)
# Unmerged / conflict leftovers always fail
if git status --porcelain=v1 | grep -qE '^(UU|AA|DD|.U|U.)'; then
echo "git-status-gate: unmerged paths present" >&2
printf '%s\n' "${lines[@]}" >&2
exit 1
fi
# Default: any tracked change (XY not ??) fails
dirty=0
while IFS= read -r line; do
[[ -z "$line" ]] && continue
# porcelain v1: first two cols are XY; ?? = untracked
xy="${line:0:2}"
if [[ "$xy" != "??" ]]; then
dirty=1
break
fi
done <<< "$(printf '%s\n' "${lines[@]}")"
if [[ "$dirty" -eq 1 ]]; then
echo "git-status-gate: dirty tracked tree" >&2
printf '%s\n' "${lines[@]}" >&2
exit 1
fi
# Untracked policy: see next sections / ALLOWLIST file
exit 0
경계: agent-test-gate는 테스트 명령 exit code, 이 절은 워킹트리 porcelain입니다. 둘 다 Done에 둘 수 있지만 스크립트는 분리합니다.
더티 트리면 중단?
한 줄 답: 예상 밖 tracked 변경·unmerged가 있으면 중단합니다. “일단 커밋”, “나중에 stash”, 무관 파일 추가 편집으로 넘어가지 않습니다. 재시도는 같은 게이트 스크립트로만 합니다.
Porcelain에서 흔히 막는 신호(git-status short/porcelain XY):
| XY 패턴 (요지) | 의미 | 기본 게이트 |
|---|---|---|
M / M / MM 등 | tracked 수정(인덱스·워크트리) | 중단 |
A / D / R … | 추가·삭제·이름변경 | 중단(태스크 allowlist 아니면) |
U* / *U / AA / DD | unmerged | 즉시 중단 |
?? | untracked | 정책 분기(다음 절) |
!! | ignored 표시(옵션) | 보통 게이트 대상 아님 |
에이전트 중단 규칙:
On git-status-gate failure:
1. Stop. Do not start unrelated edits or open a PR.
2. Print full `git status --porcelain=v1` (and optional `-b`).
3. Either: restore unexpected paths OR commit only allowlisted intentional changes
with an explicit human/ticket note — then re-run the same gate.
4. Never `git add -A` to silence the gate.
5. Never disable the hook/script without ticket ID + owner approval.
중단 ≠ “트리가 무조건 비어 있어야 함”: 태스크가 의도적으로 남긴 커밋 대기 변경이면, 그 경로는 allowlist에 넣고 게이트가 “허용된 dirty”로 통과시키거나, 커밋까지 끝낸 뒤 clean을 요구합니다. 정책을 문서에 한 줄로 고정합니다.
Policy A (strict clean): porcelain must be empty before done.
Policy B (allowlisted dirty): only paths in GATE_ALLOWLIST may be non-??;
everything else fails. Unmerged always fails.
의도적 untracked는?
한 줄 답: ??는 전부 실패가 기본이고, 예외는 allowlist·.gitignore·명시적 keep 경로 중 하나로만 엽니다. “에이전트가 만든 임시 파일”을 조용히 무시하지 않습니다.
처리 옵션:
| 방법 | 언제 | 주의 |
|---|---|---|
.gitignore에 넣기 | 빌드·캐시·로컬 도구 산출물이 반복될 때 | 시크릿·산출물 오분류 금지 |
| 게이트 allowlist | 이번 태스크만 필요한 리포트·로그 경로 | 티켓/Done에 경로 나열 |
| 커밋 또는 삭제 | 산출물을 레포에 남기거나 정리할 때 | git add -A로 우회 금지 |
status -u 범위 | untracked 표시 방식을 맞출 때 | 게이트와 로컬 status 옵션을 동일하게 |
Allowlist 예시(게이트가 ?? 줄을 걸러 냄):
# GATE_UNTRACKED_ALLOWLIST — one pathspec/prefix per line
artifacts/agent-report.json
tmp/scratch/
# inside gate: fail on ?? not matching allowlist
while IFS= read -r line; do
[[ "${line:0:2}" == "??" ]] || continue
path="${line:3}"
ok=0
while IFS= read -r allow; do
[[ -z "$allow" || "$allow" =~ ^# ]] && continue
if [[ "$path" == "$allow"* || "$path" == "$allow" ]]; then
ok=1; break
fi
done < GATE_UNTRACKED_ALLOWLIST
if [[ "$ok" -eq 0 ]]; then
echo "unexpected untracked: $path" >&2
exit 1
fi
done <<< "$(git status --porcelain=v1)"
하지 말 것: 게이트에서 git status -uno만 써서 untracked를 영원히 숨기기. 그 설정은 사람 편의용이고, 에이전트 산출물 누수를 가립니다. 숨기려면 ignore 규칙으로 옮깁니다.
한 줄 정리: git status 게이트 = Done/훅에 porcelain 스크립트 · tracked/unmerged dirty면 중단 · ??는 allowlist·gitignore로만 예외. 테스트 게이트·커밋 쪼개기와 축이 다릅니다.
FAQ
agent-test-gate와 무엇이 다른가요?
그 글은 테스트/린트/타입 명령의 exit code입니다. 이 글은 git status --porcelain으로 워킹트리·인덱스 상태를 막습니다. Done에 둘 다 걸 수 있으며, 스크립트와 실패 메시지는 분리합니다.
왜 사람용 git status가 아니라 porcelain인가요?
롱 포맷은 번역·레이아웃이 바뀝니다. --porcelain(및 -z)은 스크립트용 안정 형식으로 문서화되어 있어 훅·CI·에이전트 커맨드에 맞습니다.
stash로 가리면 되나요?
게이트를 통과시키려고 stash만 하고 done 하면 상태 이슈를 미룬 것입니다. 허용하려면 “stash 후 티켓 ID”를 정책에 명시하고, 다음 턴 시작 전 stash 목록 검사를 추가합니다. 기본은 중단 후 정리입니다.
서브모듈·ignored는?
--ignore-submodules 기본과 ignored 표시 옵션이 결과를 바꿉니다. 게이트 스크립트에 쓰는 옵션을 고정하고, Skill에도 같은 플래그를 적습니다.
출처 (Sources)
- git-status —
--porcelain, short format XY, untracked/ignored 옵션 - 팀 관행: Done/훅/
pre-*에서 동일scripts/git-status-gate.sh호출 — 본문 표 - 인접 축: agent-test-gate(명령 exit), agent-patch-commits(커밋 단위), agent-rebase-conflict(충돌 위임)