에이전트 작업 후 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-committracked dirty·unmerged 차단사람이 스테이징할 때
pre-push브랜치에 올리기 전 최종로컬만 green인 채 푸시 방지
CI (선택)checkout 후 “빌드가 트리를 더럽히지 않음”생성물·포맷 폭주 감시

실무 순서:

  1. 레포에 한 엔트리포인트를 둡니다. 예: scripts/git-status-gate.sh.
  2. Skill/Rule의 Done에 **“이 스크립트 exit 0 전에는 done/PR/다음 태스크 금지”**를 적습니다.
  3. 로컬 pre-commit 또는 pre-push에서 같은 스크립트를 호출합니다.
  4. 게이트는 사람 읽기용 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 / DDunmerged즉시 중단
??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(충돌 위임)