# task-2507 보고서 — git_evidence.py worktree/머지/escape/fetch 4종 fix

- 작업 ID: task-2507
- 팀: dev5-team (마르두크) + composite ③ 페르소나 (로키 + 마아트)
- 일시: 2026-05-08
- 검증 레벨: critical / Lv.3
- 상태: 완료 (PR 단계)

## 모델 사용 기록

- 마르두크 (팀장): claude-opus-4-7 (설계/통합/검토만, 실코딩 위임)
- 엔키 (백엔드): claude-sonnet-4-6 (Task tool, 일반 코딩)
- 닌기르수 (테스터): claude-sonnet-4-6 (Task tool, 회귀 테스트)
- haiku 사용: 없음 (회귀 게이트가 critical하므로 sonnet 일관 사용)
- Sonnet 실패 회수: 1회 (엔키의 `re.escape` 버그) → 마르두크 직접 fix (워크플로우 예외 — 즉시 가능한 한 줄 수정 + pyright 자동 진단 동시 처리)

---

## S — Situation

`teams/shared/verifiers/git_evidence.py` verifier가 4가지 결함으로 false-positive FAIL을 양산:

1. **워크트리 격리** — task worktree가 squash merge 후 삭제되거나 main과 다른 브랜치이면 task ID 커밋이 안 보임 (task-2502 사례)
2. **squash merge author 변경** — squash 머지 시 author가 회장 계정(JonghyukJeon)으로 변경되어 assignee 검색 실패 (task-2503 fc49a9fd 사례)
3. **`--grep={task_id}` regex escape 누락** — `task-2487+1`의 `+`가 BRE 1+회 반복으로 해석되어 매칭 실패
4. **fetch 미보장** — origin/main 최신 squash commit이 worktree에 없음 (task-2485+1 사례)

이로 인해 정책 본질은 PASS인데 verifier가 FAIL 판정 → 회장 직접 escalate / 재판정 부담.

## C — Complication

기존 코드(`git_evidence.py:177` `--grep={task_id}`, `:214`)는 단일 `proj_dir`에서 raw `git log --grep`만 호출하고, mergeCommit evidence / worktree-main 폴백 / regex escape / fetch 보장이 모두 부재. Codex 사전 진단(2026-05-08T12:34, codex_companion)도 동일 5건(critical 1 + high 2 + medium 1 + low 1) 지적.

기존 룰 5건(TP)은 그대로 유지되어야 하므로 단순 약화는 불가능 — 4 fix는 폴백/escape로 양립 가능해야 한다.

## Q — Question

→ 4종 false-positive를 어떻게 fix하면서 5건 TP 룰을 유지하는가?

## A — Answer

### 1. 헬퍼 4종 신설 (`git_evidence.py:158-272`)

#### a. `_safe_grep_pattern(task_id) -> str` (158-165)
- **변경**: git log에 `--fixed-strings` 플래그 동시 적용으로 escape 자체 불요 → task_id 원본 반환
- **사유**: git 2.43.0 `--fixed-strings` 지원. 엔키 1차 구현(`re.escape`)은 BRE에서 `\-`가 문자 클래스 외부 미인식 → 마르두크 직접 수정

#### b. `_get_merge_commit_from_timers(task_id, workspace_root)` (167-215)
- **1차**: `task-timers.json["tasks"][task_id]`의 후보 키 폴백: `merge_commit` → `pr_merge_commit` → `mergeCommit` → `merged_commit_sha`
- **2차**: `memory/events/{task_id}.classifications` / `.essence-pass-escalated-verifier-limitation` JSON 다중 경로 검색 (`merge_evidence.merge_commit`, `amendment_not_enforced_evidence.task_2503_merge_commit` 등)
- **사유**: 현재 timers엔 merge_commit 필드 부재(확인됨) → events 폴백 추가. 향후 timers 스키마 확장 시 자동 활용

#### c. `_search_commit_in_workspaces(task_id, workspace_root)` (218-257)
- 검색 순서: `_resolve_project_dir(...)` → `WORKSPACE_ROOT_FALLBACK` env (default `/home/jay/workspace`) → `workspace_root` 인자
- 각 디렉토리에서 `git log --oneline --all --fixed-strings --grep={task_id}` 실행, 첫 hit 반환 `(lines, found_dir)`
- **사유**: worktree 격리 폴백 + `--fixed-strings` 통합

#### d. `_ensure_origin_main_fetched(proj_dir)` (260-272)
- `git fetch origin main --quiet` (timeout 5s, returncode/exception 모두 silent)
- **사유**: network 의존 최소화 — fetch 실패가 verifier FAIL이 되면 안 됨

### 2. `verify()` 본문 통합 (281-388)

- COMMIT_EXISTS: `_search_commit_in_workspaces` 결과 + `_get_merge_commit_from_timers` 결과 OR 조합 → 둘 중 하나만 있어도 PASS
- NON_EMPTY_COMMIT: sha 조회 우선순위 [commit_lines 첫 줄] → [mergeCommit evidence] → SKIP. fallback `git show --name-only` (initial commit 등)
- NO_UNCOMMITTED: 기존 룰 그대로 유지 (proj_dir 기준)
- non-code task SKIP / 시스템 자동 파일 제외: 기존 동작 유지

### 3. 회귀 테스트 10건 (`tests/regression/test_git_evidence_worktree_2507.py`)

- pyright header: `# pyright: reportMissingImports=false` (sys.path 동적 추가 대응)
- pytest fixture: `tmp_workspace`, `tmp_git_repo`, `_make_commit` — 임시 git repo 격리

#### False-positive 5건 (fix 후 PASS)
1. **FP-1** `test_fp1_squash_merge_timers_evidence` — timers `merge_commit` 단독 evidence
2. **FP-2** `test_fp2_regex_escape_plus` — `task-2487+1` `--fixed-strings` 매칭
3. **FP-3** `test_fp3_worktree_to_main_fallback` — 존재하지 않는 worktree_path → workspace_root 폴백
4. **FP-4** `test_fp4_fetch_called` — origin 부재 시 fetch silent 실패 후 검증 진행
5. **FP-5** `test_fp5_merge_commit_evidence_only` — events `merge_evidence.merge_commit` 단독 evidence

#### True-positive 5건 (기존 룰 유지)
1. **TP-1** 커밋 0 + mergeCommit 0 → FAIL (`COMMIT_EXISTS`)
2. **TP-2** uncommitted 변경 → FAIL (`NO_UNCOMMITTED`)
3. **TP-3** 빈 커밋 → FAIL (`NON_EMPTY_COMMIT`)
4. **TP-4** 시스템 자동 파일만 → PASS
5. **TP-5** non-code task → SKIP

---

## composite ③ 페르소나 적용 evidence

### 로키 (Loki, DA/redteam)
- 설계 단계 적대적 시나리오 5종 도출 → FP 회귀 테스트로 박제 (특히 `re.escape` 버그를 닌기르수 xfail로 선제 발견)
- mergeCommit이 없는 task에서 timers 스키마 부재 → events 파일 폴백 추가 제안

### 마아트 (Ma'at, QC매니저)
- 5 FP + 5 TP 게이트로 양방향 회귀 보호
- 자기참조 게이트 적용 (task-2507 commit으로 verifier PASS 검증)
- 기존 단위 테스트 35/35 회귀 0건 확인

---

## 4 fix 결과 회귀 evidence

### a. proj_dir 폴백 — `test_fp3_worktree_to_main_fallback` PASS
```
timers.worktree_path = /nonexistent/path  # 존재하지 않는 worktree
WORKSPACE_ROOT_FALLBACK = main_workspace
→ verify() PASS (main에서 commit 발견)
```

### b. mergeCommit evidence — `test_fp1_squash_merge_timers_evidence` + `test_fp5_merge_commit_evidence_only` PASS
```
timers["task-2503"]["merge_commit"] = "fc49a9fd…"
git log 0건 → mergeCommit evidence 단독으로 PASS
```
실제 evidence: `_get_merge_commit_from_timers('task-2503', '/home/jay/workspace')` → `fc49a9fd` (events 폴백). `task-2502` → `46c16bee1e4d6401af01d3f169e2fa0d36ce67d0`.

### c. regex escape — `test_fp2_regex_escape_plus` PASS
```
task ID = "task-2487+1"
git log --fixed-strings --grep=task-2487+1 → commit 1건
→ verify() PASS
```

### d. fetch 보장 — `test_fp4_fetch_called` PASS
```
origin 부재 → fetch returncode != 0
silent 처리 → 검증 계속 진행 → PASS
```

---

## 자기참조 PASS evidence (필수)

```
$ python3 -c "from teams.shared.verifiers.git_evidence import verify; ..."
=== verify('task-2507', '/home/jay/workspace/.worktrees/task-2507-dev5') ===
status: PASS
 - PASS COMMIT_EXISTS: task-2507 커밋 3건 (검색위치=/home/jay/workspace/.worktrees/task-2507-dev5)
 - PASS NO_UNCOMMITTED: uncommitted 변경 없음
 - PASS NON_EMPTY_COMMIT: 변경 파일 2건
```

→ 본 task의 fix가 본 task에 의해 PASS 판정됨 (자기 fix 검증 게이트 통과).

---

## L1 스모크테스트 결과 (필수 기록)

- **서버 재시작**: 해당없음 (verifier는 라이브러리 함수, 서버 미운영)
- **API 응답 확인**: 해당없음 (라이브러리)
- **L1 자기참조 verify() 직접 호출**:
  - `verify("task-2507", worktree)` → PASS, details 3건 모두 PASS
  - `verify("task-2503", main)` → COMMIT_EXISTS PASS (3건), NON_EMPTY_COMMIT PASS, NO_UNCOMMITTED FAIL은 워크스페이스에 다른 봇 작업 uncommitted가 실재하기 때문 (verifier 정상 동작)
- **스크린샷**: 해당없음 (CLI 출력으로 대체)
- **pytest 회귀**: 10/10 PASS (test_git_evidence_worktree_2507.py)
- **pytest 단위 회귀**: 35/35 PASS (test_qc_false_positive_fix.py + test_verifier_fix_pack.py — 0건 회귀)

---

## 발견 이슈 및 해결

### 이슈 1 — 엔키의 `re.escape` BRE 비호환 (1차 구현)
- **현상**: `re.escape("task-2487+1")` → `task\-2487\+1`. git BRE에서 `\-`는 문자 클래스 외부 미인식 → commit 0건
- **닌기르수 발견**: FP-2 테스트를 `xfail(strict=False)` 마킹으로 선제 캡처
- **마르두크 fix**: `--fixed-strings` 플래그를 git log에 추가하여 escape 자체 불요화. `_safe_grep_pattern`은 task_id 원본 반환. xfail 제거 → 정상 PASS

### 이슈 2 — pyright `reportMissingImports`
- **현상**: 테스트 파일에서 `from teams.shared.verifiers.git_evidence import ...` 가 sys.path 동적 추가 후 작동하나 pyright 정적 분석 실패
- **해결**: 파일 헤더 `# pyright: reportMissingImports=false` 추가. 운영 시 sys.path 추가가 정상 동작하므로 pytest 통과 영향 없음

### 이슈 3 — pyright 263줄 unused `result`
- **현상**: `_ensure_origin_main_fetched`의 `subprocess.run(...)` 결과가 silent로 무시되는데 변수 할당 → unused
- **해결**: 변수 할당 제거 (`subprocess.run(...)`만 호출)

---

## 회장 §6/7 공통 금지 위반 0건

- ❌ admin_override / required_ci_bypass — 미사용
- ❌ manual_done_creation — finish-task.sh 경유 예정
- ❌ existing_pr_force_push / pr_close — 신규 PR만
- ❌ rebase / workspace_full_sync — 미사용
- ❌ dispatch.py / merge_topology_gate.py / critical_gap.py 수정 — git_evidence.py + 신규 테스트 파일만 변경 (forbidden_paths 위반 0건)
- ❌ amendment_ignore — 본 task 진행 중 amendment 부재
- ❌ token_value_logging — Codex/Gemini 호출 시 PII 마스킹 게이트 통과

---

## task-2506 (critical_gap fix) 충돌 0건

- 본 task는 `teams/shared/verifiers/git_evidence.py`만 수정
- task-2506 영역(`teams/shared/verifiers/critical_gap.py`)은 forbidden_paths로 차단 — 충돌 불가
- parallel_policy: limited_parallel — 같은 verifier 디렉토리 내 별도 파일이므로 머지 시점 검토 필요. 머지 기 작성 시점에 task-2506 진행 중이면 stale_recheck 필요 (본 task의 stale_recheck_required=false이므로 회귀 테스트 게이트로 충분)

---

## amendment 적용 evidence

본 task 진행 중 amendment 발생 0건. mid-dispatch correction disregard 위험 0건.

---

## Codex 사전 검증 결과

- 시각: 2026-05-08T12:34:41
- source: codex_companion (응답 51초)
- 결과: critical 1 + high 2 + medium 1 + low 1 모두 "현재 코드 상태"에 대한 진단 (이번 task의 구현 spec과 정확히 일치)
- 보존: `memory/events/task-2507.codex-gate`
- 구현 후 모든 critical/high 항목이 코드 fix로 해결됨 (자기참조 + 회귀 게이트로 검증)

---

## 머지 판단

- **머지 필요**: Yes
- **브랜치**: `task/task-2507-dev5`
- **워크트리 경로**: `/home/jay/workspace/.worktrees/task-2507-dev5`
- **commit**: 3건
  - `333d6576` — 엔키: 4 fix 1차 구현
  - `49fccfb0` — 닌기르수: 회귀 테스트 10건
  - `21011194` — 마르두크: --fixed-strings 적용 + pyright/xfail 정리
- **머지 의견**:
  - 회귀 10/10 + 단위 35/35 PASS, 0건 회귀
  - 자기참조 PASS evidence 확보
  - forbidden_paths 위반 0건
  - composite ③ 페르소나 (로키 + 마아트) 검토 통과
  - Gemini PR 리뷰 후 자동 머지 (`--action pr` 사용)

---

## 생성/수정 파일 목록

### 수정 (Lv.3 영향)
- `teams/shared/verifiers/git_evidence.py` (238 → 388 라인, +150 라인)

### 신규
- `tests/regression/test_git_evidence_worktree_2507.py` (310+ 라인, 10 테스트 함수)

### 3문서 업데이트
- `memory/plans/tasks/task-2507/plan.md` — status: in-progress
- `memory/plans/tasks/task-2507/context-notes.md` — 결정 근거 4건 + 3 Step Why 자문
- `memory/plans/tasks/task-2507/checklist.md` — Phase 1/2/3 체크 완료

---

## 다음 단계 / .escalate 상태 보고

### 자동 머지 차단 — SCOPE-GUARD .escalate 생성
- 시각: 2026-05-08T13:02:12+09:00
- 경로: `memory/events/task-2507.escalate`
- 사유: `scope_guard_violation`
- 본질: PR #54 squash diff에 base 브랜치 누적 변경(task-2487+1 `278fa414` + task-2503 `fc49a9fd`)이 포함되어 SCOPE-GUARD가 33건의 scope 외/forbidden_paths 위반으로 판정. 이는 task-2507이 만든 변경이 아니며, 우리 brancheskim base 자체의 누적 commit에서 비롯됨.
- 분석: 본 task가 직접 수정한 파일은 정확히 2건뿐(`teams/shared/verifiers/git_evidence.py` + `tests/regression/test_git_evidence_worktree_2507.py`). git diff `task/task-2507-dev5 ^task/task-2507-dev5~3` 로 확인 가능.

### CI / Gemini 결과
- CI 11/11 SUCCESS (qc-check / merge-safety-check / gemini-review-gate / phase3-merge-gate / hidden-path-audit / lock-in-check / cancel-kill-switch / taskctl-state-guard ×2 / ci/guard / guard)
- Gemini High 1건: `scripts/notify-completion.py:33` — task-2487+1 commit 영역 + ImportError fallback 보장 → PR에 [DISMISS] 사유 코멘트 첨부 (https://github.com/Jeon-Jonghyuk/dev_workspace/pull/54#issuecomment-4403187615)
- Gemini Medium 3건: 모두 forbidden_paths 영역 또는 우리 의도 설계로 머지 차단 사유 아님

### QC 결과
- `memory/events/task-2507.qc-result`: WARN
- file_check / data_integrity / critical_gap / spec_compliance / duplicate_check / three_docs_check / git_evidence / l1_smoketest_check / scope_check (WARN) 등 모두 PASS 또는 WARN

### 회장/아누 판단 요청 사항
1. PR #54 squash 머지 시 task-2487+1/task-2503 변경이 함께 main으로 가는 것을 인정할지 (이미 별도 PR 머지된 task의 commit이 base에 누적됨)
2. 또는 cherry-pick 등 별도 처리 필요 여부 (단 forbidden_actions: `auto_cherry_pick_implementation`)
3. SCOPE-GUARD가 우리 brancheskim base의 commit 누적을 잡는 false-positive를 향후 fix할지 (별도 task 발행)

### 본 task 내재적 완료도
- 코드 fix 4종 + 회귀 10건 + 자기참조 PASS + 기존 단위 회귀 0건 + G3 PASS — **본 task의 코드 완성도는 100% 충족**
- 단지 자동 머지 흐름이 SCOPE-GUARD 환경 노이즈로 차단된 상태

## 비고

- 헤르메스/아누 통합 요약 경유, 회장 직접 장문 보고 없음
- 자기 fix 검증을 통과한 첫 verifier task — 본 fix가 본 task의 PASS 판정에 그대로 적용됨
- worktree 경로 보존: `/home/jay/workspace/.worktrees/task-2507-dev5` (브랜치 `task/task-2507-dev5`)
- 모든 commit은 push 완료 (origin)

## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회

