# .done 감지 갭 수정 — 3-Layer Defense 구현

## 작업 개요
대화 중 팀 작업 완료를 감지하지 못하는 구조적 결함을 3-Layer Defense로 해결한다.

## 배경
- `dispatch.py`의 `_register_followup`이 데드 코드 (호출 0건)
- 아누가 "매 대화 시작 시"에만 .done 체크하여 대화 중 감지 불가
- 실제 사고: task-544.1, task-545.1 완료 2회 연속 미감지

## 미팅 결과
- 미팅 기록: `memory/meetings/2026-03-14-done-detection-gap.md`
- 참석: 헤르메스, 야누스, 로키(DA), 아르고스

---

## Task A: finish-task.sh 단일 마무리 스크립트 (L1)

**신규 파일**: `/home/jay/workspace/scripts/finish-task.sh`

기능:
1. task_id를 인수로 받음
2. .done 파일 원자적 생성 (O_EXCL — 이미 존재하면 skip)
3. task-timer end 호출
4. notify-completion.py 호출
5. 각 단계 실패 시 로그 남기고 다음 단계 진행 (블로킹 금지)

```bash
#!/bin/bash
# 사용법: finish-task.sh <task_id>
set -euo pipefail
TASK_ID="$1"
WORKSPACE="/home/jay/workspace"
EVENTS_DIR="$WORKSPACE/memory/events"
DONE_FILE="$EVENTS_DIR/${TASK_ID}.done"

# 1. .done 원자적 생성
if ! (set -C; echo '{"task_id":"'"$TASK_ID"'","completed_at":"'"$(date -Iseconds)"'"}' > "$DONE_FILE") 2>/dev/null; then
    echo "[WARN] .done already exists: $DONE_FILE"
fi

# 2. task-timer end
python3 "$WORKSPACE/memory/task-timer.py" end "$TASK_ID" 2>&1 || echo "[WARN] task-timer end failed"

# 3. notify-completion.py
source "$WORKSPACE/.env.keys" 2>/dev/null || true
python3 "$WORKSPACE/scripts/notify-completion.py" "$TASK_ID" 2>&1 || echo "[WARN] notify-completion failed"

echo "[OK] finish-task.sh completed for $TASK_ID"
```

## Task B: UserPromptSubmit Hook (.done 체크) (L2)

**수정 파일**: `/home/jay/.claude/hooks/user-prompt-submit.sh` (또는 해당 훅 설정)

훅에 .done 파일 체크 로직 추가:
```bash
# .done 체크 (매 메시지마다 실행)
DONE_FILES=$(find /home/jay/workspace/memory/events/ -maxdepth 1 -name "*.done" ! -name "*.done.*" -type f 2>/dev/null)
if [ -n "$DONE_FILES" ]; then
    TASK_LIST=$(echo "$DONE_FILES" | xargs -I{} basename {} .done | tr '\n' ', ')
    echo "⚠️ 미처리 완료 작업 발견: ${TASK_LIST%. }"
fi
```

주의사항:
- `*.done.*`은 제외 (`.done.clear`, `.done.false` 등)
- glob 대신 `find` 사용 (파일 수백 개일 때 ARG_MAX 방지)
- 비차단(non-blocking): 실패해도 exit 0 반환

현재 훅 설정 파일 확인 필요:
- 프로젝트 훅: `/home/jay/.cokacdir/workspace/autoset/.claude/hooks/UserPromptSubmit`
- 글로벌 훅: `/home/jay/.claude/hooks/UserPromptSubmit`
- 어디에 설정되어 있든 해당 위치에 추가

## Task C: systemd User Timer done-watcher (L3)

### C-1: done-watcher.sh 스크립트

**신규 파일**: `/home/jay/workspace/scripts/done-watcher.sh`

```bash
#!/bin/bash
# systemd timer로 30초마다 실행
# .done 파일이 있고, 60초 이상 경과했으며, .done.clear가 없으면 notify 호출
EVENTS_DIR="/home/jay/workspace/memory/events"
LOG="/home/jay/workspace/logs/done-watcher.log"

for done_file in $(find "$EVENTS_DIR" -maxdepth 1 -name "*.done" ! -name "*.done.*" -type f 2>/dev/null); do
    task_id=$(basename "$done_file" .done)
    clear_file="$EVENTS_DIR/${task_id}.done.clear"

    # .done.clear 이미 존재하면 skip
    [ -f "$clear_file" ] && continue

    # 60초 미만이면 L1에게 기회 양보
    file_age=$(( $(date +%s) - $(stat -c %Y "$done_file") ))
    [ "$file_age" -lt 60 ] && continue

    # O_EXCL로 done.clear 원자적 선점 시도
    if python3 -c "
import os, sys
try:
    fd = os.open('$clear_file', os.O_CREAT | os.O_EXCL | os.O_WRONLY)
    os.write(fd, b'{\"source\":\"done-watcher\",\"task_id\":\"$task_id\"}')
    os.close(fd)
except FileExistsError:
    sys.exit(1)
" 2>/dev/null; then
        echo "[$(date -Iseconds)] L3 감지: $task_id → notify 호출" >> "$LOG"
        python3 /home/jay/workspace/scripts/notify-completion.py "$task_id" 2>&1 >> "$LOG" || true
    fi
done

# heartbeat 기록
echo "$(date -Iseconds)" > /home/jay/workspace/logs/done-watcher.heartbeat
```

### C-2: systemd service + timer

**신규 파일**: `~/.config/systemd/user/done-watcher.service`
```ini
[Unit]
Description=.done 파일 처리 스크립트

[Service]
Type=oneshot
EnvironmentFile=/home/jay/workspace/.env.keys
WorkingDirectory=/home/jay/workspace
ExecStart=/home/jay/workspace/scripts/done-watcher.sh
StandardOutput=append:/home/jay/workspace/logs/done-watcher.log
StandardError=append:/home/jay/workspace/logs/done-watcher.log
```

**신규 파일**: `~/.config/systemd/user/done-watcher.timer`
```ini
[Unit]
Description=.done 파일 감지 폴러 (30초)

[Timer]
OnActiveSec=30s
OnUnitActiveSec=30s
AccuracySec=1s

[Install]
WantedBy=timers.target
```

등록 명령:
```bash
systemctl --user daemon-reload
systemctl --user enable --now done-watcher.timer
```

## Task D: 보안 수정 (즉시)

**수정 파일**: `/home/jay/workspace/scripts/notify-completion.py`

현재 (L16):
```python
def get_anu_key() -> str:
    return os.environ.get("COKACDIR_KEY_ANU") or "c119085addb0f8b7"
```

수정:
```python
def get_anu_key() -> str:
    key = os.environ.get("COKACDIR_KEY_ANU")
    if not key:
        print("[FATAL] COKACDIR_KEY_ANU 환경변수가 설정되지 않았습니다.", file=sys.stderr)
        sys.exit(1)
    return key
```

## Task E: 멱등성 강화

**수정 파일**: `/home/jay/workspace/scripts/notify-completion.py`

일반 작업(non-chain) 경로에도 done.clear O_EXCL 원자적 생성 적용.

현재: `done_clear_path.exists()` 체크 → race condition 취약
수정: `os.open(O_CREAT | O_EXCL)` 시도 → 실패하면 이미 처리됨으로 skip

## Task F: 데드 코드 및 정리

1. `dispatch.py` L243-272: `_register_followup` 함수 삭제
2. `dispatch.py` L512-513: 주석 업데이트 → "3-Layer Defense로 대체 (L1:finish-task.sh, L2:Hook, L3:systemd Timer)"
3. `.done.false` 파일 2건: 원인 조사 후 수동 처리
4. `.done.clear2/3` 파일 6건: 정리 (rm)

## Task G: 팀장 프롬프트 수정

**수정 파일**: `/home/jay/workspace/prompts/team_prompts.py`

팀장 완료 시 호출 명령을 통일:
- 기존: "notify-completion.py 호출" + "task-timer end" + ".done 생성" (분산)
- 변경: `finish-task.sh <task_id>` 한 줄 (통합)

**수정 파일**: `/home/jay/workspace/prompts/DIRECT-WORKFLOW.md`
- Step 6-8을 `finish-task.sh` 한 단계로 통합

**수정 파일**: `/home/jay/workspace/teams/dev3/GLM-WORKFLOW.md`
- 완료 시 `finish-task.sh` 호출로 통일

## 우선순위 순서
1. Task D (보안) → 즉시
2. Task E (멱등성) → Task D와 같은 파일
3. Task A (finish-task.sh) → 핵심 L1
4. Task B (Hook) → L2
5. Task C (systemd) → L3
6. Task G (프롬프트 수정) → L1 완성
7. Task F (정리) → 마무리

## 검증 기준
- [ ] finish-task.sh 실행 → .done + .done.clear + 아누 세션 예약 확인
- [ ] Hook 작동: .done 존재 시 아누 응답에 경고 표시
- [ ] systemd Timer: 30초 간격 정상 실행, heartbeat 갱신 확인
- [ ] 멱등성: 같은 task에 L1+L2+L3 동시 반응 시 아누 세션 1회만 생성
- [ ] 보안: 환경변수 미설정 시 에러 종료 확인
- [ ] 기존 체인 작업 흐름 정상 작동 (리그레션 없음)
