Files
llm-wiki/docs/superpowers/specs/2026-06-10-claude-harness-audit-report.md
T

175 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-06-10 — `.claude/` 문서관리 하네스 전수 감사 보고
- **의뢰**: "하네스가 잘 동작하긴 하는데 한번씩 하네스대로 동작하지 않고 결과물을 내는 경우가 있다. agents·command·hooks·skills 를 하나씩 분석해 극한으로 동작시키기 위한 개선점을 찾아라."
- **범위**: `.claude/agents/` 10 · `.claude/commands/` 22 · `.claude/hooks/` 8 · `settings*.json` 2 · `.claude/skills/wiki-workflow/SKILL.md` 1 (+ 교차참조: `CLAUDE.md`, `rules/`, `templates/`, 3-플랫폼 미러)
- **방법**: 집행 레이어(hooks·settings)는 컨트롤러 직접 정독, agents/commands 는 병렬 research lane 4개(agents / capture·transform commands / output·invest commands / Claude Code 훅 스키마 공식 문서 검증), 핵심 가설 3건은 **라이브 세션 실측**으로 확정.
- **Verdict: NOT-READY** (개선 필요 — P0 6건 / P1 9건 / P2 7건). 골격은 견고하나 배선 결함 3종이 증상을 직접 유발.
---
## §0. 한 줄 결론
하네스의 골격(2단 게이트 파이프라인 + 훅 4종)은 잘 설계되어 있고 **실제로 작동 중**이다. 그러나 "한번씩 하네스대로 동작하지 않는" 증상의 주범은 *모델의 우회*가 아니라 — **① 훅↔에이전트 출력 계약 불일치로 인한 오차단→이탈 재시도, ② 진입점(SKILL.md) 라우팅 공백으로 인한 폴스루, ③ 파생·invest 레이어의 결정론 게이트 부재** — 즉 하네스 자체의 배선 결함 3종이다.
## §1. 실측으로 확인한 사실 (라이브 세션 재현)
훅에 임시 이벤트 덤프를 넣고 probe subagent 를 돌려 검증 (실험 후 원복 완료, `git diff` 클린 확인):
| 검증 항목 | 결과 |
|---|---|
| `SubagentStop.last_assistant_message` 존재 여부 | ✅ 존재 — 게이트는 살아있음 (공식 문서엔 스키마 미기재였음) |
| `SubagentStart` `additionalContext` 주입 | ✅ subagent transcript 에서 주입 문자열 확인 |
| **증상 재현** | ✅ **재현됨** — probe(Explore 에이전트)가 `Verdict: COMPLETE` 라고 말하자 게이트가 차단 → probe 는 본래 임무 출력 대신 **사과문 형태의 이탈된 재시도 출력**을 반환 |
| `stop_hook_active=true` 재시도 | ✅ 재시도 메시지는 **무검증 통과** (one-retry enforcement) |
| 게이트 적용 범위 | ⚠️ 이벤트에 `agent_type` 필드가 있는데도 게이트가 **모든 subagent**(Explore/general-purpose/Plan 포함)에 무차별 적용 |
| PreToolUse `matcher: "*"` | ⚠️ Read/Grep 포함 **모든 도구 호출마다** python 프로세스 1개 스폰 확인 |
→ 사용자가 보는 "하네스 미준수 출력"의 상당수는 **게이트가 안 돌아서가 아니라, 게이트가 잘못된 대상을 차단한 뒤 그 이탈 재시도가 무검증으로 통과**하는 메커니즘.
확인된 SubagentStop 이벤트 필드 (실측): `session_id, transcript_path, cwd, permission_mode, agent_id, agent_type, hook_event_name, stop_hook_active, agent_transcript_path, last_assistant_message, background_tasks, session_crons`
## §2. 근본 원인 체인 5개
### RC1 — 훅↔출력계약 불일치 (오차단 → 이탈 → 무검증 통과) [최우선]
`wiki_claim_gate.py:162-179` 의 SubagentStop 게이트와 에이전트 출력 템플릿의 충돌:
| 에이전트 | 충돌 | 결과 |
|---|---|---|
| `wiki-adversarial-reviewer.md:102` | `**Verdict:** COMPLETE` 를 강제하면서 출력 템플릿이 요구 문자열 3종(`Claim ID`/`Decision Evidence Map`/`UNSUPPORTED_DECISION`) 출력을 보장하지 않음 (`UNSUPPORTED_DECISION` 은 파일 전체에 부재) | COMPLETE 선언 시 상시 차단 위험 |
| `wiki-research-lane.md:122` | 동일 — 절차(step 5)엔 3종이 있으나 출력 템플릿엔 없음. official-doc 슬라이스처럼 3종이 정당하게 무관한 corpus 에서 확정 차단 | 동일 |
| `wiki-diagram-reviewer.md:195` | `NEEDS_FIX`(60≤점수<95, hard-stop 0) → `verdict: not-ready` + `blocking: 0` 인데 훅은 `not-ready ∧ blocking<1` 을 모순으로 차단 (`wiki_rules.py:245-246`) | **가장 흔한 채점 등급에서 결정론적 오차단** |
| 게이트 전체 | `agent_type` 무시 → 위키와 무관한 범용 에이전트도 `Verdict: COMPLETE` 한 마디에 차단 (§1 재현) | 무관 작업의 이탈 |
| 전 에이전트 공통 | `wiki-verdict`/`wiki-stats` 기계 블록이 출력 템플릿 바깥(부록 위치)에 정의됨 + 훅은 마커 부재 시 통과(presence-conditional) → 템플릿만 충실히 재현하면 블록 누락 = 게이트 silent never-fire | 미검증 통과 |
| 전 에이전트 공통 | `verdict: blocked` 경로 미정의 (입력 불량 시 즉흥 대응) + 예시 블록이 happy-path 리터럴 값(anchor-copy 위험) | 스키마 이탈 |
### RC2 — 진입점 라우팅 공백 (게이트 우회의 구조적 원인)
`SKILL.md` dispatch tree 가:
- 10개 에이전트 중 4개를 모름 (`wiki-diagram-reviewer`, `branch-depth-auditor`, `coverage-auditor`, `project-readiness-auditor`)
- `/branch-spec`·`/depth`·`/coverage`·`/project-spec` 게이트 파이프라인으로 라우팅하지 않음
- **파생 산출물(/interviewize·/blogify·/explain·portfolio) 분기가 아예 없음** → 자연어 요청이 "메인 에이전트 직접 응답"으로 폴스루 = status 게이트 미적용
- dispatch 시 필수 입력 3개 에이전트분 과소 전달 (adversarial-reviewer corpus path, doc-author mode/claim-evidence, source-summarizer source_type/정당화 결정) → `BLOCKED`/`NEEDS_CONTEXT` 왕복이 "flaky"처럼 보임
### RC3 — main agent 는 무검증 지대
- **Stop 훅 없음** — `/ingest`·`/lint`·`/branch-spec` 등 main agent 인라인 명령의 최종 보고(`## Stats` funnel, 종합 판정)는 어떤 훅도 검증 안 함. `validate_stats_block` 검증기가 있는데 subagent 에만 배선
- `/lint``wiki_structure_lint.py --all` 을 한 번도 호출하지 않고 깨진 링크·frontmatter 검사를 LLM 수기 재연 (`lint.md:44-53`)
- `wiki_quorum.py` 는 어느 명령도 호출하지 않는 dead tooling (유일 참조: adversarial-reviewer N=3 고위험 경로, 기본 N=1)
### RC4 — 결정론 게이트의 경로 공백
`CLAIM_REQUIREMENTS`(`wiki_rules.py:152-171`) 커버 prefix 는 5개뿐:
| 미커버 경로 | 위험 |
|---|---|
| `wiki/projects/` | canonical 절반이 증거 등급 구조 무게이트 — interview/portfolio 가 인용해야 할 바로 그 층 |
| `wiki/interview/`·`wiki/blog/`·`wiki/portfolio/`·`wiki/explainer/` | §11 최대 금지(canonical 우회·status 미달 파생)가 주 1회 LLM lint 에만 의존. 쓰기 시점 차단 가능한데 안 함 |
| `raw/invest-daily/`·`raw/invest-ledger/`·`wiki/invest-plan/` | **실제 돈 경로**가 narrative 신뢰 + LLM 산술로 운영. 환각 수치가 모든 훅 통과 |
| branch-note 파일명 | numbered-hierarchy 금지(§11)가 결정론 검사 없음 |
### RC5 — SSOT drift (복붙 재진술 엔진)
- `CLAUDE.md` 인벤토리: `/coverage`·`/migrate-claims` 누락, `/explain` "예정" 표기(실존+3플랫폼 미러 완료)
- `depth.md:2` description "2차 LLM 의미 게이트는 P2" — 본문은 이미 2차 dispatch 함 (stale)
- drift 실증 2건: `/ingest` source_type 어휘(daily-task 특수 절차 부재), `blogify.md:36` 인라인 섹션 리스트 vs 템플릿
- `migrate-claims.md:92` **`rg -L` 버그** — ripgrep `-L``--follow`(symlink)이지 files-without-match 가 아님. 유일한 결정론 검증 단계가 거짓 데이터 산출
- 명령 22개 전부 `allowed-tools`/`model` frontmatter 부재 — 판정 명령(/depth /coverage /query)과 실돈 명령(/invest-decide)이 전권 보유
- Claude 본판 에이전트에 Antigravity 포트의 G1(Pre-Read Proof)·G3(`{{ }}` placeholder 스키마)·G4(STOP 조건 열거) 하드 게이트 부재 — 본판이 포트보다 약한 역전 상태
## §3. 우선순위 수정안
### P0 — 오차단 제거 (증상의 직접 원인)
1. SubagentStop 게이트에 `agent_type` 스코핑 — 위키 에이전트 10종 외 즉시 allow
2. `wiki-diagram-reviewer.md:195` blocking 재정의 — blocking = 95점 미만 다이어그램 수(NEEDS_FIX 시 최소 1), hard-stop 수는 prose 로
3. COMPLETE 어휘 에이전트 2개(adversarial-reviewer, research-lane)에 `## Claim Traceability Check` 고정 섹션 — 3문자열을 "none found" 라도 항상 literal 출력
4. 기계 블록을 출력 템플릿 안으로 이동 + `verdict: blocked` 경로 + 예시 값 `{{ }}` placeholder 화
5. SKILL.md dispatch tree 보강 (누락 4 에이전트 + 게이트 파이프라인 + 파생 분기 + Required Inputs)
6. `migrate-claims.md` `rg -L``rg --files-without-match` (3 copies)
### P1 — 결정론 커버리지 확장
7. `CLAIM_REQUIREMENTS` 확장 (+6 엔트리): `wiki/projects/` · 파생 4종(`## Sources` 에 canonical wikilink 요구, portfolio 는 projects 필수) · `raw/invest-daily/`(수치 행 URL+날짜 토큰)
8. 파생 status 게이트 결정론화 — `check_markdown_write` 에서 Sources 의 canonical 링크 frontmatter `status:` 검사 (explainer 면제)
9. Stop 훅 추가 — main agent 최종 메시지의 wiki-stats/Verdict 를 동일 validator 로 검증
10. `/lint` 1단계 = `wiki_structure_lint.py --all` 실행 + §15.5 "(예정)" 3검사 구현
11. `/coverage` 1차 기계화 (`--coverage-pre` 모드) + branch-note 파일명 regex 검사
12. branch-spec 루프 천장 2회 (project-spec 과 정렬) + 최종 보고 wiki-stats funnel
13. PreToolUse claim_gate matcher 를 `Bash|Write|Edit|MultiEdit|NotebookEdit` 로 축소 (성능)
14. `allowed-tools` frontmatter 도입 (판정 명령 read-only, /invest-decide 최소권한)
15. CLAUDE.md 인벤토리 동기화 + `depth.md` description 갱신
### P2 — 구조 개선
16. G1/G3/G4 하드 게이트 Claude 본판 역이식
17. invest 경화: `invest_ledger_check.py`(근거 링크 실존·staleness·주간 거래 수·row 스키마·P&L 재계산) + quorum 검증 invest 수치 opt-out 기본값 반전 + 임계값 strategy.md 일원화
18. 복붙 재진술 제거 (명령 내 템플릿 섹션 리스트 → "템플릿 Read 가 SSOT", §6 등급표·명명 규칙 → 참조 1줄)
19. `wiki_quorum.py` 배선 (`/lint --fix-plan` CRITICAL ≥5건 시 adversarial N=3 + quorum) 또는 폐기 명시
20. 판정 에이전트 3종(adversarial/depth/readiness) 모델 상향 검토 (sonnet→opus)
21. `wiki-decision-researcher` 모순 해소 — "controller 에 dispatch 요청 방출"로 재서술 + `Write` 도구 제거
22. one-retry 완화 검토 — `stop_hook_active` 시 스키마 오류 재차단 허용 (Claude Code 자체 8회 연속 차단 cap 존재; 단 P0 1~4 선행 후)
## §4. 잘 되어 있는 것 (유지)
- `/depth`·`/project-spec` 의 "1차 결정론 스크립트 → 2차 의미 감사" 파이프라인 — 22개 명령 중 모범
- `wiki_structure_lint.py` 의 C2 링크 검사(코드 스팬·fence·escaped pipe)와 projected-content 방식
- 완성-선언 시에만 C1/C3 를 켜는 `is_completeness_checkable` 설계 (false-positive 소음 차단)
- Bash 직접 쓰기 차단 + Write/Edit 강제 경유 — 새 CLAIM_REQUIREMENTS 엔트리가 진짜로 우회 불가능해지는 토대
## §5. Claim Traceability Check (본 감사의 추적성 자기검사)
- **Claim ID / Claims Extracted 검사**: 본 감사는 raw source 를 신규 생성하지 않음 — `Claims Extracted` 표 생성 의무 해당 없음. 근거는 전부 hooks/agents/commands 파일의 file:line 인용으로 추적 가능.
- **Decision Evidence Map 검사**: branch-note 를 생성/수정하지 않음 — 해당 없음. (게이트 자체의 `Decision Evidence Map` 요구 로직은 `wiki_rules.py:157-160` 에서 검증함.)
- **UNSUPPORTED_DECISION 검사**: 본 보고의 권고 중 외부 근거 없이 사용자 trade-off 로 결정해야 하는 항목 — P2-20(모델 상향: 비용 증가 vs 판정 신뢰), P2-22(one-retry 완화: 루프 위험 vs 집행 강도) — 은 측정 근거가 아니라 설계 판단이며, 채택 전 사용자 결정 필요.
- **실측 증거**: §1 표의 6개 항목은 2026-06-10 라이브 세션에서 직접 재현·관측한 1차 증거 (probe agent transcript + 이벤트 덤프).
## §6. P0 구현 추적
| # | 항목 | 상태 (2026-06-10) |
|---|---|---|
| P0-1 | SubagentStop agent_type 스코핑 (`WIKI_AGENT_TYPES` SSOT in `wiki_rules.py` + `subagent_stop_gate` 분기) | ✅ 완료 — 테스트 5건 추가, 4개 suite 전부 OK |
| P0-2 | diagram-reviewer blocking 재정의 (= 95점 미만 다이어그램 수, hard-stop 수는 prose) | ✅ 완료 |
| P0-3 | Claim Traceability Check 고정 섹션 (adversarial-reviewer · research-lane) + bare-token/공백금지/BLOCKED 무방출 규칙 | ✅ 완료 |
| P0-4 | 기계 블록 템플릿 내장(4-backtick 외곽 fence) + `verdict: blocked` 경로 + `{{ }}` placeholder — 7개 에이전트 | ✅ 완료 |
| P0-5 | SKILL.md dispatch tree 보강 (4 에이전트 + 게이트 파이프라인 + 파생 분기 + Required Inputs + STOP 14항) | ✅ 완료 |
| P0-6 | migrate-claims `rg -L``rg --files-without-match` (3 copies) | ✅ 완료 |
| 후속 | 3-플랫폼 미러 동기화: SSOT `.agents/plugins/wiki-superpowers/agents/` 6 + Antigravity `agent.json` 6 (JSON 검증 OK) + Codex `.md`/`.toml` 12 (TOML 검증 OK, body 패리티 확인). platform-aware — Codex/Antigravity 에 SubagentStop 문구 미도입, 기존 G3/hard-gate 어휘 사용 | ✅ 완료 |
검증: `test_wiki_claim_gate`·`test_wiki_rules`·`test_wiki_structure_lint`·`test_wiki_quorum` 전부 OK / 편집된 에이전트 7파일 4-backtick fence 짝 균형 / 본 보고서 `wiki_structure_lint --file` PASS.
## §7. P1 구현 추적 (2026-06-10)
| # | 항목 | 상태 |
|---|---|---|
| P1-7 | `CLAIM_REQUIREMENTS` +5 엔트리: `wiki/projects/`(실제 구현 내용+Sources, named-hub 면제) · `wiki/interview\|blog\|explainer`(Sources+canonical 링크 `content_regex`) · `wiki/portfolio/`(projects 링크 필수) · `raw/invest-daily/`(고정 체크리스트+출처 섹션) + 수치행 출처/조사시점 정책(`invest_daily_numeric_failures`) | ✅ 기존 파일 전수 dry-run — 오차단 1건(2026-06-06 invest-daily, 템플릿 도입 전)은 light-touch 마이그레이션으로 해소 |
| P1-8 | 파생 status 게이트 결정론화: `derived_source_status_failures``## Sources` 의 canonical 링크 frontmatter `status` 전부 ∈ {reviewed, verified, published-ready} 필요 (explainer 면제) | ✅ |
| P1-9 | main agent `Stop` 훅 (`--main-stop` 플래그): fenced `wiki-stats` 만 검증 — COMPLETE trap/wiki-verdict 는 메타 대화 오차단 위험으로 제외. Antigravity native `Stop`(subagent 의미) 라우팅과 플래그로 분리 | ✅ (신규 세션부터 활성 — 훅 설정은 세션 시작 시 스냅샷) |
| P1-10 | `/lint` 개정: 절차 1단계 `--all`·2단계 `--stale`(신규 린터 모드) 결정론 선행 + §A1(§15.5 3검사) + C/D군 린터 출력 SSOT 화 + E군 write-time backstop 주석 | ✅ |
| P1-11 | 린터 `--coverage-pre`(0 PASS/1 FAIL/3 EXEMPT) + `NAMING_VIOLATION`(신규 생성만 차단, 기존 파일 편집 허용) + `/coverage` 1차 스크립트화 | ✅ 기존 branch-note 파일명 위반 0건 사전 확인 |
| P1-12 | `/branch-spec` 루프 천장 2회(§8c) + §9 `wiki-stats` funnel(Stop 훅 검증) + §8b `--coverage-pre` 연결 | ✅ |
| P1-13 | PreToolUse claim_gate matcher `*``Bash\|Write\|Edit\|MultiEdit\|NotebookEdit` (동작 동일, 프로세스 스폰 감소) | ✅ |
| P1-14 | `disallowed-tools`: depth/query(Write·Edit·NotebookEdit 금지) · coverage/lint(NotebookEdit·WebSearch·WebFetch — 정당한 쓰기 경로 보존) · invest-decide(WebSearch·WebFetch — 결정 시점 즉흥 조사 차단) | ✅ /coverage·/lint 전면 쓰기 금지는 §Coverage 기록·--fix-plan 적용을 깨뜨려 제외 |
| P1-15 | CLAUDE.md 동기화(§1 흐름·§2 인벤토리·branch-spec 항목·§15 /explain 해제·§15.5 "(예정)" 해제) + depth description 갱신 + **depth.md 의 환각된 `--fix --apply` 플래그 참조 제거** (덤 발견) | ✅ |
| 후속 | 명령 미러 동기화: lint·coverage·branch-spec·depth × Codex skill + Antigravity workflow (8파일, platform-aware — Stop 훅 문구 일반화, disallowed-tools 미전파, invest 절 미전파) | ✅ |
검증: 4개 test suite 전부 OK (123 tests, 신규 ~30 포함) / `settings.json` JSON valid / `--coverage-pre`·`--stale` 실데이터 스모크 PASS / 신규 게이트 전수 dry-run 오차단 0건.
## §8. P2 구현 추적 (2026-06-10 — 22개 권고 전체 완료)
| # | 항목 | 상태 |
|---|---|---|
| P2-16 | G1 Pre-Read Proof(10/10) + G4 STOP 조건 열거(10/10, 기존 blocked 규칙 참조 통합) + G2(쓰기 2종 Post-Write Validation, read-only 판정자 self-grep 증거 규칙) Claude 본판 역이식 — 에이전트당 ≤40줄, G3 는 P0-4 기완료 | ✅ |
| P2-17 | `invest_ledger_check.py` 신규(--check: 11열 스키마·근거 실존·staleness 24h/90d·주간 거래 수 / --report: P&L 기계 재계산) + 테스트 8건 + `/invest-decide` §5 배선·인라인 임계값 제거(strategy.md ①~⑤ SSOT) + `/invest-review` §3 배선 + ledger CLAIM_REQUIREMENTS 엔트리 + **quorum 기본값 반전**: invest-daily/research 고위험 수치 = read-only 검증 subagent ×3 + `wiki_quorum.py` 합산이 기본(opt-out 명시제, Workflow 는 상위 옵션 유지) | ✅ |
| P2-18 | 재진술 제거 15파일(blogify·interviewize·explain·projectize·branch × SSOT+미러 2): 템플릿 섹션 리스트 → "템플릿 Read 가 SSOT", §6 등급 열거 → 참조, 명명 규칙 → 1줄+린터 위임. drift 실증 문자열 잔존 0건 grep 확인 | ✅ |
| P2-19 | `wiki_quorum.py` 배선 — `/lint --fix-plan` CRITICAL ≥5건 시 adversarial N=3 + `/tmp/lint-vote-*` + 결정론 합산(KILL 제외/UNVERIFIED 보류/DOWNGRADE 하향). dead tooling 해소 (+미러 2) | ✅ |
| P2-20 | 판정 3종(adversarial/depth/readiness) `model: opus` 상향 — **사용자 승인** | ✅ |
| P2-21 | decision-researcher 모순 해소: description "dispatch 요청 방출(실 dispatch 는 controller)" + `Write` 제거 + SKILL.md/CLAUDE.md 정렬 + 변형 4벌(plugin SSOT·agent.json toolNames·codex toml sandbox read-only·codex md) 동기화 | ✅ |
| P2-22 | one-retry 폐지(위키 에이전트 한정) — `stop_hook_active` 무검증 통과 제거, Claude Code 8회 cap 이 무한루프 방지. main-stop 은 one-retry 유지 — **사용자 승인** | ✅ |
최종 검증: **5개 test suite 전부 OK** (test_invest_ledger_check 신규 포함) / G1 Pre-Read Proof 10/10 / 4-backtick fence 균형 / 본 보고서 lint PASS.
**잔여 follow-up (P0~P2 범위 밖, 기록만)**: ① Stop 훅·재차단 정책은 신규 세션부터 활성(훅 설정 세션 스냅샷) ② strategy ③ 주간 거래상한 N 미정 — 사용자가 정하면 `/invest-decide``--weekly-cap` 에 반영 ③ Bash claim gate 의 명령 문자열 path-매칭 false-positive(작업 중 2회 재현)는 redirect 타깃 기반 검사로 정밀화 후보 ④ concept source_type drift(메모리 2a) 별도 마이그레이션.
**Stats**: 분석 대상 45 파일 / 병렬 lane 4 / 실측 검증 3건 / findings 60+ → 권고 22건 (P0 6 · P1 9 · P2 7)