# task-872.1 완료 보고서 — Hermes Agent 학습 도입 재검토 및 품질 검증

**작성자:** 오딘(Odin), dev2-team 팀장
**작성일:** 2026-03-24
**태스크:** task-872.1

---

## SCQA 프레임워크

**S**: task-868.1(3팀/다그다)에서 Hermes Agent 도입 최종 Phase가 완료되어 7개 신규 모듈(961줄), 156개 테스트가 추가된 상태다. 전체 utils/ 38개 모듈, 738개 테스트가 보고되었다.

**C**: 3팀 자체 보고만으로는 독립 검증이 부재하며, 보안·통합·줄 수 준수 등의 품질 기준 충족 여부가 미확인 상태다. 설계서 대비 실제 구현의 정합성, 통합 코드의 완전성, pyright 0 errors 주장에 대한 교차검증이 필요하다.

**Q**: 7개 신규 모듈과 기존 통합이 설계서 요구사항을 정확히 충족하며, 보안 결함·테스트 누락·통합 미완성 없이 품질 기준을 통과하는가?

**A**: 독립 재검토 결과 결함 5건을 발견했다. pyright 에러 2건(tests/fakes 절대 import), 경로 탈출 미방어(context_refs.py), orchestrator.py persistent_shell 통합 미완성, code-review.py patch_parser 통합 미완성. 이 중 3건(pyright 에러, 경로 탈출, 줄 수 확인)은 직접 수정했다. 수정 후 156개 신규 테스트 전체 PASS, pyright 0 errors 확인. 통합 미완성 2건은 import+플래그만 선언된 dead code로, 기능 위험은 없으나 보고서 기술 부정확.

---

## 1. Phase A 구현 검증 (7개 모듈)

### M-03 model_router.py — **PASS**

- 줄 수: 124줄 (200줄 이하 ✓)
- 테스트: 31개 (15개 이상 ✓)
- 설계서 일치: `max_simple_chars=160`, `max_simple_words=28`, frozenset 복잡 키워드, haiku→sonnet→opus 3단계 라우팅 모두 일치
- 엣지 케이스: 빈 문자열/공백 처리 정상. `task=None` 미처리(Minor, 타입 힌트로 방어)
- 비고: `engine_budget.yaml`에 `models.router.*` 키 미존재는 config_loader 연동 시 주의

### M-08 checkpoint.py — **PASS**

- 줄 수: 175줄 (200줄 이하 ✓)
- 테스트: 25개 (15개 이상 ✓)
- 설계서 일치: 타임스탬프 기반 스냅샷, 롤백, `atomic_text_write` 사용, 파일명 형식 `YYYYMMDD_HHMMSS_ffffff__label__original.ext` 모두 일치
- 엣지 케이스: FileNotFoundError 명시적 raise, 디렉토리 자동 생성, 레이블 빈값 처리 정상
- 비고: 바이너리 파일은 `read_text(errors="replace")`로 인해 손실 가능 (텍스트 전용 설계)

### M-18 patch_parser.py — **CONDITIONAL PASS**

- 줄 수: 168줄 (200줄 이하 ✓)
- 테스트: 30개 (15개 이상 ✓)
- 설계서 일치: unified diff 파싱/적용, difflib 기반, context 원본 유지, atomic_text_write 모두 일치
- 이슈: 설계서 "롤백 지원" 대비 `rollback_patch()` API 미존재. M-08 checkpoint와 역할 분리로 해석 가능하나 명시적 정의 필요
- 잠재 버그: trailing whitespace가 제거된 빈 context 라인 파싱 스킵 가능성

### M-21 persistent_shell.py — **PASS**

- 줄 수: 165줄 (200줄 이하 ✓)
- 테스트: 22개 (15개 이상 ✓)
- 설계서 일치: subprocess.Popen 단일 bash 유지, sentinel 패턴 exit code 추출, CWD/env 상태 유지 모두 일치
- 문서 불일치: 설계서 "PTY 기반"이나 실제는 PIPE 방식. 기능 동등하나 설계서 문구 정정 필요
- 비고: threading.Lock 미구현 — 멀티스레드 사용 시 호출부에서 직렬화 필요

### M-05 context_refs.py — **PASS (수정 후)**

- 줄 수: 173줄 (200줄 이하 ✓, 수정 후 +7줄)
- 테스트: 25개 (15개 이상 ✓)
- 설계서 일치: @file:, @folder:, @diff, @staged 4종, 50KB/100KB 제한, parse_refs() 공개 인터페이스 모두 일치
- **결함 수정**: `_target()` 함수에 경로 탈출(Path Traversal) 방어 로직 추가 (resolve + relative_to 검증)
- 정규식: `[^\s]+`로 공백 포함 경로 불가는 설계 제한으로 허용

### M-24 tests/fakes/ — **PASS (수정 후)**

- 줄 수: fake_llm_client.py 84줄, fake_dispatch.py 79줄, __init__.py 13줄 (모두 200줄 이하 ✓)
- 테스트: 23개 (test_fakes.py)
- 설계서 일치: FakeLLMClient 응답 순환+async 지원, FakeDispatch 위임 기록+실패 모드 모두 일치
- **결함 수정**: `__init__.py`의 절대 import → 상대 import 변경 (pyright 에러 해소)

### M-25 pyproject.toml — **PASS**

- `[project.optional-dependencies]` 6개 그룹 확인: voice, ml, dev, search, async, all
- all 그룹이 나머지 5개를 extras 문법으로 통합

---

## 2. Phase B 통합 검증

### dispatch.py + model_router — **PASS**

- L83-89: `try/except ImportError` 선택적 import ✓
- L451-457: `_MODEL_ROUTER_AVAILABLE and _route_model is not None` 이중 가드 ✓
- backward compatible: import 실패 시 완전 우회 ✓
- dispatch.py 관련 테스트 91개 전체 PASS

### orchestrator.py + checkpoint — **PASS**

- L55-61: `try/except ImportError` 선택적 import ✓
- L523-528: `snapshot_path.exists()` 확인 후 체크포인트 저장 ✓
- backward compatible ✓

### orchestrator.py + persistent_shell — **FAIL (Dead Code)**

- L63-69: import + `_PERSISTENT_SHELL_AVAILABLE` 플래그 선언은 존재
- 그러나 파일 전체(685줄)에서 이 플래그와 `_PersistentShell`을 실제 사용하는 코드가 **없음**
- 기능 위험 없음 (기존 동작 불변), 그러나 3팀 보고서의 "import 연결" 기술은 불완전 — dead code

### scripts/code-review.py + patch_parser — **FAIL (Dead Code)**

- L37-46: import + `_PATCH_PARSER_AVAILABLE` 플래그 선언은 존재
- 그러나 파일 전체(590줄)에서 `_apply_patch`/`_generate_patch`/`_PATCH_PARSER_AVAILABLE`를 실제 호출하는 코드가 **없음**
- 기능 위험 없음, 그러나 실질 통합 미완성 — dead code

---

## 3. pytest/pyright 실행 결과

### pytest (Phase A 신규 테스트)

```
156 passed, 0 failed (1.73s)
```

파일별 내역:
- test_model_router.py: 31 PASS
- test_checkpoint.py: 25 PASS
- test_patch_parser.py: 30 PASS
- test_persistent_shell.py: 22 PASS
- test_context_refs.py: 25 PASS
- test_fakes.py: 23 PASS

### pytest (전체 Hermes 모듈)

```
738 passed, 0 failed (2.83s)
```

기존 582개 + 신규 156개 = 738개 전체 PASS ✓

### pytest (프로젝트 전체)

```
1063 passed, 32 failed, 1 warning (7.84s)
```

32개 실패는 모두 task-868.1 이전 기존 실패:
- test_done_watcher.py (9건): `ModuleNotFoundError: No module named 'done_watcher'`
- test_group_chat.py (5건): 조직 구조 변경 관련
- test_hermes_audit.py (3건): audit 파일 부재
- 기타 15건: GLM/OpenClaw/QC 관련 — 3팀 작업과 무관

### pyright (수정 후)

```
0 errors, 0 warnings, 0 informations
```

수정 전: 2 errors (tests/fakes/__init__.py 절대 import resolve 실패)
수정 후: 0 errors ✓

---

## 4. 전체 Hermes 도입 최종 현황

### 구현 완료 모듈 (Hermes 설계서 기반, 32개 + 추가 6개 = 38개)

**Phase 0 (기반 인프라) — 8개:**
- S-01 `redact.py` (208줄, ⚠️ 200줄 초과)
- M-01 `atomic_write.py` (145줄)
- M-02 `prompt_cache.py` (81줄)
- M-04 `usage_pricing.py` (145줄)
- M-06 `model_metadata.py` (142줄)
- M-17 `fuzzy_match.py` (115줄)
- M-19 `interrupt.py` (53줄)
- M-20 `robots_policy.py` (84줄)

**Phase 1 (보안) — 3개:**
- S-02 `injection_guard.py` (166줄)
- S-03 `approval.py` (178줄)
- M-16 `clarify.py` (270줄, ⚠️ 200줄 초과)

**Phase 2 (설정/저장소) — 5개 + 추가 2개:**
- S-14 `config_loader.py` (127줄)
- S-09 `session_store.py` (166줄)
- M-22 `event_hooks.py` (179줄)
- 추가: `event_hooks_loader.py` (90줄)
- 추가: `audit_logger.py` (128줄)
- 추가: `session_store_search.py` (182줄)

**Phase 3 (핵심 기능) — 8개 + 추가 1개:**
- S-06 `memory_manager.py` (178줄)
- S-11 `skill_loader.py` (168줄)
- M-03 `model_router.py` (124줄) ★ 신규
- M-05 `context_refs.py` (173줄) ★ 신규, 수정됨
- M-08 `checkpoint.py` (175줄) ★ 신규
- M-18 `patch_parser.py` (168줄) ★ 신규
- M-21 `persistent_shell.py` (165줄) ★ 신규
- 추가: `skill_parser.py` (163줄)

**Phase 4 (분석/라우팅) — 3개 + 추가 2개:**
- S-07 `aux_llm_router.py` (156줄)
- S-08 `insights_engine.py` (198줄)
- S-13 `context_compressor.py` (191줄)
- 추가: `context_summarizer.py` (124줄)
- 추가: `session_search.py` (178줄)

**Phase 5 (고난이도) — 4개:**
- S-04 `skill_guard.py` (189줄) + `skill_guard_patterns.py` (141줄)
- S-05 `pre_exec_scan.py` (145줄) + `pre_exec_patterns.py` (65줄)

**테스트 인프라 — 4개:**
- M-24 `tests/fakes/` (fake_llm_client 84줄, fake_dispatch 79줄, __init__ 13줄) ★ 신규, 수정됨
- M-25 `pyproject.toml` 선택적 의존성 ★ 신규
- 추가: `delegate_controller.py` (120줄) + `delegate_runner.py` (84줄)

### 40개 원본 항목 분류

- 구현 완료: **32개** (S-01~S-15 중 10개, M-01~M-25 중 22개)
- 미선별: **8개** (M-09, M-10, M-11, M-12, M-13, M-14, M-15, M-23)

### 200줄 초과 모듈 (2건, 이전 Phase에서 발생)

- `utils/clarify.py`: 270줄 (M-16, Phase 1에서 구현)
- `utils/redact.py`: 208줄 (S-01, Phase 0에서 구현)

이 2건은 task-868.1 범위 외(이전 Phase)이므로 본 재검토에서는 기록만 하고 수정하지 않음.

### Health Score

- pytest 통과율: 738/738 (100%) ✓
- pyright 에러: 0건 (수정 후) ✓
- 구현 모듈: 38개 파일 (설계서 32항목 + 추가 6개 보조 모듈)
- 신규 테스트: 156개 추가 (총 738개)
- 200줄 이하: Phase A 7개 모두 준수 ✓ (전체 중 2개 초과 — 이전 Phase 산출물)
- backward compatible: dispatch.py, orchestrator.py 통합 try/except ImportError ✓

---

## 5. 미선별 8개 향후 판단

| ID | 이름 | 판단 | 사유 |
|----|------|------|------|
| M-09 | Honcho 사용자 모델링 | **향후 구현** | S-09 SQLite 기반으로 로컬 사용자 프로필 자체 구현 가치 있음. 외부 SaaS 의존 제거한 설계로 수정 필요 |
| M-10 | MoA 앙상블 | **영구 스킵** | dispatch 기반 멀티에이전트가 이미 유사 기능 제공. API 비용 대비 ROI 불명확 |
| M-11 | 스킬 허브 연동 | **영구 스킵** | 내부 스킬 53개로 충분. 외부 스킬 보안 검증 부담 대비 활용 빈도 극히 낮음 |
| M-12 | 도구 레지스트리 | **영구 스킵** | MCP 서버가 이미 도구 관리 역할 수행. 현 utils/ 38개 규모에서 레지스트리 패턴은 오버엔지니어링 |
| M-13 | 터미널 다중 환경 | **영구 스킵** | Docker/SSH 격리 실행은 현 워크플로우에서 불필요. worktree 격리로 충분 |
| M-14 | 코드 실행 샌드박스 | **영구 스킵** | M-13 미구현으로 의존성 충족 불가. 보안 영역은 로키 소관 |
| M-15 | STT (Faster Whisper) | **향후 구현** | GPU 환경 구축 시 구현. 기존 whisper 스크립트를 통합 도구로 승격 가능 |
| M-23 | CI/CD (GitHub Actions) | **향후 구현** | 가장 실용적 가치 높음. 현재 수동 CI(`scripts/ci.sh`)를 자동화하면 테스트 회귀 방지에 즉시 기여 |

**우선순위:** M-23(CI/CD) > M-09(사용자 모델링) > M-15(STT)

---

## 6. 발견 이슈 및 해결

### 자체 해결 (3건)

1. **pyright 에러 2건 (tests/fakes/__init__.py)** — 절대 import를 상대 import로 변경
   - 수정 파일: `tests/fakes/__init__.py:10-11`
   - `from tests.fakes.fake_dispatch` → `from .fake_dispatch`
   - 검증: pyright 0 errors ✓, pytest 23 passed ✓

2. **context_refs.py 경로 탈출(Path Traversal) 미방어** — `_target()` 함수에 `resolve() + relative_to()` 검증 추가
   - 수정 파일: `utils/context_refs.py:64-72`
   - `@file:../../etc/passwd` 시도 시 `PermissionError` 발생으로 방어
   - 검증: pytest 25 passed ✓

3. **checkpoint.py 줄 수 확인** — 실제 175줄로 확인, 수정 불필요
   - 3팀 보고서와 일치 (176줄 오인은 카운트 방식 차이)

### 범위 외 미해결 (2건)

1. **orchestrator.py persistent_shell 통합 미완성** — import+플래그만 선언, 실제 호출 코드 없음. 3팀 보고서에는 "import 연결"로 기술되었으나 실질 dead code. 기능 위험 없으나 향후 실제 연결 필요
   - 범위 외 사유: orchestrator.py 내부 로직 수정은 아키텍처 결정 필요

2. **scripts/code-review.py patch_parser 통합 미완성** — import+플래그만 선언, 실제 호출 코드 없음
   - 범위 외 사유: 코드 리뷰 파이프라인 설계 변경이 수반됨

### 3팀 보고서 부정확 사항 (기록)

- orchestrator.py persistent_shell 통합을 "import 연결 (`_PERSISTENT_SHELL_AVAILABLE`)"로 기술했으나, 실제로는 사용 코드 없는 dead code
- code-review.py patch_parser 통합을 "apply_patch/generate_patch 선택적 import"로 기술했으나, import만 있고 호출 코드 없음
- 전체적으로 "4건 통합" 보고 중 실질 동작 통합은 2건(dispatch+model_router, orchestrator+checkpoint)

---

## 7. 수정 파일 목록

- `tests/fakes/__init__.py` — 상대 import로 변경 (pyright 에러 수정)
- `utils/context_refs.py` — `_target()` 경로 탈출 방어 추가 (보안 수정)

---

## QC 자동 검증 결과

```json
{
  "task_id": "task-872.1",
  "verified_at": "2026-03-24T01:03:36",
  "overall": "PASS",
  "checks": {
    "api_health": "SKIP",
    "file_check": "PASS (3/3 checks passed)",
    "data_integrity": "PASS",
    "test_runner": "PASS (25 passed in 0.09s)",
    "tdd_check": "SKIP (파일 변경 없음)",
    "schema_contract": "SKIP",
    "pyright_check": "PASS (0 errors, 0 warnings)",
    "style_check": "PASS (black OK, isort OK)",
    "scope_check": "SKIP",
    "critical_gap": "PASS",
    "spec_compliance": "PASS",
    "duplicate_check": "PASS (최대 유사도 24.3% — task-868.1)"
  },
  "summary": "8 PASS, 4 SKIP"
}
```

Gate PASS → .done 파일 자동 생성 완료.
