# task-2550+1 보고서 — .worktrees auto-cleanup policy + worktree_cleanup.py clean replacement

**상태**: 구현 + 회귀 PASS, PR 생성 대기
**Level**: Lv.4 (control-plane, security/correctness)
**Team**: dev5 마르둑
**기준 base**: origin/main `bf59e7d2c26baee692600bdfd4abd647b33748b3`
**branch**: `task/task-2550+1-dev5`
**replacement_of**: PR #100 / task-2550 (IMPLEMENTATION_SPEC_INCOMPLETE)
**original_head_unchanged**: `4067d8c43833155f5ea626255645549602a23bbb` (PR #100, 보존)

---

## 요약

PR #100 (task-2550) Gemini fresh review (head 4067d8c4) 6 unresolved findings (HIGH 1 +
medium 4 unique + duplicate 1) 의 corrected clean replacement. 같은-PR push doctrine 위반
회피 위해 fresh PR 으로 진행. PR #100 보존 (failed original evidence).

## 발사 조건 충족 어셀션

| 조건 | 상태 | 증거 |
|------|------|------|
| PR #103 (task-2549+1) MERGED | ✓ | `4e05acdd` |
| PR #108 (task-2556) MERGED | ✓ | `b6dca5d1` (executor 자동 entry point) |
| PR #106 (OWNER_TRIGGER_ONLY_CAPABILITY) main 반영 | ✓ | `cd1aeba4` |
| executor 자동 entry point verification | ✓ | PR #108 capability auto-activation |

## 6 unresolved findings → resolved 1:1

### HIGH (1건) — task_id substring 오탐

- **위치**: `anu_v2/worktree_cleanup.py:147` (PR #100 baseline 기준)
- **issue**: `task_id in str(headRefName)` substring 매칭 → task-25 가 task-2550-dev5 에 매칭
- **fix**: `_matches_task_id_strict` 두 단 게이트
  1. regex 경계 `(?:^|[^A-Za-z0-9+])task-{num}(?:$|[^A-Za-z0-9+])`
  2. `_extract_task_id(headRef) == task_id` 동등 비교 (replacement chain task-2550 vs task-2550+1 분리)
- **test**: `test_high_boundary_*` (8 sub-tests) + `test_safety_2_strict_match_*` (3) + `test_pr100_high_unresolved_regression_bug_blocked`

### medium #1 — hash 비결정론

- **위치**: `anu_v2/worktree_cleanup.py:418`
- **issue**: `abs(hash(path)) % 10**8` — PYTHONHASHSEED 의존
- **fix**: `hashlib.sha256(path.encode()).hexdigest()[:8]` 결정론
- **test**: `test_medium_1_log_filename_uses_deterministic_sha256` + `test_medium_1_different_paths_yield_different_hashes` + `test_concurrency_log_filename_stable_under_pythonhashseed`

### medium #2 — pgrep CWD 누락

- **위치**: `anu_v2/worktree_cleanup.py:227`
- **issue**: `pgrep -f` 는 argv 매칭만 — CWD 사용 process 미감지
- **fix**: `lsof +D <path>` 추가 AND 게이트 (pgrep + lsof 두 단)
- **test**: `test_medium_2_safety_5_lsof_*` (4 sub-tests)

### medium #3 — cleanup_candidates dry-run 가시성

- **위치**: `anu_v2/post_merge_smoke_runner.py:541`
- **issue**: `r.all_safe` 는 dry-run 에서 `apply_explicit` FAIL → 항상 False → candidate 0
- **fix**: `is_safe_ignoring_apply(r)` helper — safety 1~5 PASS + not main + not dirty 로 산정
- **test**: `test_medium_3_is_safe_ignoring_apply_*` (4) + `test_medium_3_dry_run_cleanup_candidates_*` (2) + `test_high_boundary_integration_task25_does_not_match_task2550_in_smoke_cleanup`

### medium #4 — duplicate dedupe

- MED-3 와 동일 location/issue (Gemini 5번째 findings). MED-3 와 통합 처리.

## expected_files (11) 1:1

| # | 파일 | 상태 |
|---|------|------|
| 1 | `anu_v2/worktree_cleanup.py` | NEW (PR #100 baseline + fix) |
| 2 | `anu_v2/post_merge_smoke_runner.py` | MODIFIED (cleanup 통합 + MED-3 산정 fix) |
| 3 | `anu_v2/tests/test_worktree_cleanup_2550plus1.py` | NEW (HIGH boundary + medium 4 회귀, 24 tests) |
| 4 | `anu_v2/tests/test_post_merge_smoke_worktree_2550plus1.py` | NEW (cleanup_candidates 통합 회귀, 7 tests) |
| 5 | `memory/reports/task-2550+1.md` | NEW (본 보고서) |
| 6 | `memory/events/task-2550+1.dispatch-decision.json` | NEW (authoritative source) |
| 7 | `memory/events/task-2550+1.replacement-lineage.json` | NEW |
| 8 | `memory/events/task-2550+1.gemini_triage_decision.json` | NEW |
| 9 | `memory/plans/tasks/task-2550+1/plan.md` | NEW |
| 10 | `memory/plans/tasks/task-2550+1/context-notes.md` | NEW |
| 11 | `memory/plans/tasks/task-2550+1/checklist.md` | NEW |

## 회귀 결과

- **anu_v2 전체 pytest**: 432 baseline + 31 신규 = **463/463 PASS**
- **pyright**: 0 errors / 0 warnings (4 changed files)
- **신규 회귀 분포**:
  - HIGH boundary: 8 sub-tests + 3 strict_match + 1 PR100 BUG 재현 = 12
  - MED-1 sha256: 3
  - MED-2 lsof: 4
  - MED-3 helper: 4 (helper) + 2 (smoke 통합) + 1 (HIGH 통합) = 7
  - smoke schema/integration: 5
  - 총 31

## long polling doctrine 상속

- 본 task 는 control-plane (Lv.4).
- `anu_v2/polling_policy.py` 상수 1:1 상속:
  - `MAX_SINGLE_SLEEP_SECONDS = 900` (15min)
  - `FIRST_TIMEOUT_SECONDS = 1800` (30min)
  - `MAX_RECHECKS = 1`
  - 5min~15min cooldown × 30min first timeout × 1회 recheck × 종료
- long polling 0 — dispatch_decision.json `no_long_polling_required=true` 명시.

## forbidden 18 어셀션

| # | 금지 항목 | 상태 |
|---|----------|------|
| 1 | PR #100 commit/push/merge/close/reopen | 0 |
| 2 | force/rebase/empty commit | 0 |
| 3 | bot /gemini review | 0 (외부 채널만) |
| 4 | owner PAT live | 0 (capability runner OWNER_GEMINI_TRIGGER_TOKEN 만) |
| 5 | GH_TOKEN fallback | 0 |
| 6 | md/report 만 PASS | 0 (모든 code change 회귀 검증 완료) |
| 7 | 다른 PR 혼입 | 0 |
| 8 | 자동 task-2550+2 chain 발행 | 0 |
| 9 | worktree 격리 위반 | 0 (`.worktrees/task-2550plus1-dev5`) |
| 10 | 회장 수동 /gemini review | 0 |
| 11 | `.worktrees/` 실제 삭제 | 0 (dry-run only — apply=False default + safety_6) |
| 12 | long polling | 0 (polling_policy 상수 강제) |

## 분리: STATE_FILE_MISSING (별도 follow-up)

`task-2550.done.escalated` marker 의 state file 누락 (finish-task.sh state file 생성 로직
누락) 은 본 task 와 분리된 별도 operational defect. 본 task 에서는 처리 X.

## 다음 단계

1. PR 생성 (BOT identity, base=main)
2. Gemini fresh review (capability auto-invocation FIRST_MISSING/STALE 시 가능)
3. unresolved 0 + CI 11/11 SUCCESS + CLEAN
4. BOT squash merge
5. smoke/reconcile + lifecycle markers + PR #100 head 4067d8c4 unchanged 어셀션
