Files
llm-wiki/docs/superpowers/specs/2026-06-06-spec-c-funnel-stats-no-silent-truncation-design.md
T

169 lines
9.0 KiB
Markdown

---
title: "Spec C — funnel stats + no-silent-truncation 계약 (G4)"
source_type: llm-generated
status: draft
confidence: medium
tags: [harness, claude-code, hooks, agents, design, automation]
last_reviewed: 2026-06-06
---
# Spec C — funnel stats + no-silent-truncation
> 상위 감사: [[2026-06-06-harness-audit-report]] §3 G4. Spec B 다음 슬라이스. 확정 결정: **Lean(계약+경량 funnel)**, **승인 완료**.
## 1. 문제 (감사에서)
- **G4 (P4)**: funnel stats 가 거의 어디에도 없다. `wiki_structure_lint.py``fail_by_type/fail_by_rule` 만 유일. 어떤 judge·command 도 deep-research funnel(후보→처리→탈락→확정)을 안 냄. **`/ingest` 가 promotable 항목을 silent 누락했는지 알 수 없고**, `coverage-auditor` 가 관심사를 몇 개 열거했는지 보이지 않는다.
## 2. 핵심 원리 / 비목표
- B 와 동일 철학: 결정론 코어(파싱·검증)는 `wiki_rules.py`(단일 SSOT), enforcement 는 SubagentStop hook(Claude·Codex)·G3 in-prompt(Antigravity). dispatch/계수는 controller/agent(self-report).
- self-report stats 는 완벽한 진실이 아니지만, **funnel 균형 검증**(found = processed + dropped)과 **dropped_reason 필수**가 "조용한 누락"을 *보이게* 만든다 — 그게 G4 의 목표(관측가능성), 정확한 회계가 아니다.
- **비목표**: 모든 agent/command 에 stats 강제(YAGNI) → 핵심 4 agent + /ingest 만. branch-spec/project-spec Workflow → Spec D.
## 3. 설계 결정 (확정)
### DD1 — Lean 범위
- 결정론 코어: `wiki_rules.validate_stats_block` + `claim_gate` SubagentStop 검증(B 메커니즘 재사용).
- 계약: `rules/reporting-standards.md` 에 no-silent-truncation 절.
- funnel 방출: 핵심 4 agent(`coverage-auditor`·`branch-depth-auditor`·`wiki-decision-researcher`·`wiki-research-lane`) + `/ingest`(command, advisory).
### DD2 — funnel 균형이 teeth
`found == processed + dropped` 가 깨지면 오류. `dropped>0` 인데 `dropped_reason` 비면 오류. 이 두 검사가 "조용한 truncation" 을 차단한다.
## 4. 아키텍처
```
.claude/hooks/
wiki_rules.py ← MOD. validate_stats_block() + 상수
wiki_claim_gate.py ← MOD. subagent_stop_gate 가 wiki-stats 마커 시 검증
test_wiki_rules.py ← MOD. TestStatsBlock
rules/reporting-standards.md ← MOD. "No silent truncation" 계약 절
.claude/agents/{coverage-auditor,branch-depth-auditor,wiki-decision-researcher,wiki-research-lane}.md
← MOD. Output 에 ## Stats wiki-stats 블록
.claude/commands/ingest.md ← MOD. funnel(found→promoted→skipped+이유) 출력 계약
```
3-플랫폼 미러: 4 agent 의 Antigravity(`.agents/plugins/.../*.md` G3 통합) + Codex(`.codex/agents/*.md`+`*.toml`). (`wiki-decision-researcher`·`wiki-research-lane` 도 shared 9 agent 에 포함 → 미러 대상.)
### 4.1 `wiki-stats` 기계 블록
````
```wiki-stats
agent: coverage-auditor
found: 12
processed: 10
dropped: 2
dropped_reason: 2 out-of-scope (governing §4)
```
````
- `found` = 후보로 식별한 총 항목(관심사/claim/후보/파일).
- `processed` = 실제 **판정한** 수 — *결과 무관*. covered·delegated·**missing**·verified·promoted 등 어떤 판정이든 "다뤘으면" processed. (coverage 의 missing 은 drop 이 아니라 *판정된 gap* 이므로 processed 에 포함된다.)
- `dropped` = **판정하지 않고** 의도적으로 제외한 수(범위 밖/bound 초과). `dropped>0` → `dropped_reason` 필수.
- 불변식: `found = processed + dropped`. (모든 식별 항목은 *판정됨* 이거나 *제외됨* — 제3의 침묵 누락이 없다.)
### 4.2 `wiki_rules.py` 추가
```python
STATS_FENCE_RE = re.compile(r"```wiki-stats\s*\n(.*?)\n```", re.S)
def parse_stats_block(text):
m = STATS_FENCE_RE.search(text or "")
if not m:
return None
out = {"agent": None, "kv": {}}
for line in m.group(1).splitlines():
line = line.strip()
if not line:
continue
kv = re.match(r"([a-z_]+):\s*(.+)$", line)
if kv:
k, v = kv.group(1), kv.group(2).strip()
if k == "agent":
out["agent"] = v
else:
out["kv"][k] = v
return out
def validate_stats_block(text):
"""(parsed, errors). parsed None → 마커 없음(통과). errors → SubagentStop 차단."""
parsed = parse_stats_block(text)
if parsed is None:
return None, []
errors = []
if not parsed["agent"]:
errors.append("wiki-stats 블록에 `agent:` 누락")
nums = {}
for k in ("found", "processed", "dropped"):
try:
nums[k] = int(parsed["kv"].get(k, ""))
except ValueError:
errors.append(f"wiki-stats `{k}` 는 정수여야 함 (funnel 필수 필드)")
if len(nums) == 3:
if nums["found"] != nums["processed"] + nums["dropped"]:
errors.append(
f"funnel 불균형: found({nums['found']}) ≠ processed({nums['processed']}) "
f"+ dropped({nums['dropped']}) — 조용한 누락 의심"
)
if nums["dropped"] > 0 and not parsed["kv"].get("dropped_reason", "").strip():
errors.append("dropped>0 인데 `dropped_reason` 누락 (no-silent-truncation 위반)")
return parsed, errors
```
### 4.3 SubagentStop 강제 (`wiki_claim_gate.subagent_stop_gate` 확장)
verdict 검사 *뒤에* 추가(같은 패턴):
```python
sparsed, serr = wiki_rules.validate_stats_block(message)
if sparsed is not None and serr and not event.get("stop_hook_active"):
emit_block("wiki-stats 블록 오류:\n- " + "\n- ".join(serr))
```
마커 없는 출력 영향 없음. settings.json 무변경.
### 4.4 `rules/reporting-standards.md` — No silent truncation 계약
새 절 추가:
> **No silent truncation.** 출력이 캡/슬라이스/top-N/skip 으로 coverage 를 bound 하면 **드롭한 수 + 이유**를 반드시 보고한다. funnel 은 균형해야 한다: `found = processed + dropped`. agent 출력은 `wiki-stats` 블록으로(SubagentStop 검증), command 는 `## Stats` 절로 보고한다. 침묵 누락은 "전부 다뤘다" 는 거짓 신호다.
### 4.5 agent `.md` 편집 (4, 3-플랫폼 미러)
각 Output 에 `## Stats` 의 `wiki-stats` 블록 추가. funnel 의미 매핑:
- coverage-auditor: found=governing 관심사 수, processed=covered+delegated, dropped=명시 제외(있으면 이유).
- branch-depth-auditor: found=점검한 claim/결정 수, processed=판정 완료, dropped=범위 밖(이유).
- wiki-decision-researcher: found=식별 후보 수, processed=archive 한 수, dropped=bound 초과 제외(이유).
- wiki-research-lane: found=슬라이스 파일 수, processed=정독+추출, dropped=무관/제외(이유).
Antigravity 는 G3 Output Schema 에 `{{ }}` 스타일로 통합("형식 외 응답 금지").
### 4.6 `/ingest` 출력 계약 (advisory)
출력 끝에 `## Stats` funnel: `found promotable 항목 / promoted(canonical 경로별) / skipped(항목+이유)`. daily/branch 특수처리는 추출/미추출 카운트를 명시(이미 "추출 안 한 항목 raw 보존" 규칙 있음 — 카운트만 추가).
## 5. 위험
| 위험 | 완화 |
|---|---|
| agent 가 found/dropped 를 부정확 self-report | funnel 균형 검증이 *내부 모순*은 잡음(완벽한 회계는 아님 — 관측가능성이 목표). |
| SubagentStop 가 비-stats subagent 오차단 | `parsed is None`(마커 부재) 시 통과 — opt-in. |
| 3-플랫폼 미러 drift | B 와 동일 4 agent, grep-verify; 생성기 부재로 수기. |
| dropped_reason 강제가 noise | dropped==0 이면 reason 불요 — 실제 누락 시에만. |
## 6. 수용 기준 (검증 가능)
1. `validate_stats_block`: 균형 블록 → errors=[]. `found=12 processed=10 dropped=0` → 불균형 오류. `dropped=2` + reason 없음 → 오류. 마커 부재 → (None, []).
2. `subagent_stop_gate`: wiki-stats 불균형 마커 → exit 2. 균형 마커 → exit 0. 마커 없음 → exit 0.
3. `rules/reporting-standards.md` 에 "No silent truncation" 절 존재(grep).
4. 4 agent `.md` 각 Output 에 `wiki-stats` 블록 존재 + 3-플랫폼 미러(grep parity).
5. `/ingest.md` 에 `## Stats` funnel 계약 존재(grep).
6. 기존 4 스위트 + 신규 stats 테스트 전부 green.
## 7. 구현 순서 (writing-plans)
1. `wiki_rules.py` `parse_stats_block`/`validate_stats_block` (TDD) + `test_wiki_rules` TestStatsBlock.
2. `claim_gate.subagent_stop_gate` 확장 + subprocess 회귀(균형/불균형/마커없음).
3. `rules/reporting-standards.md` no-silent-truncation 절.
4. 4 agent `.md` (Claude) ## Stats 블록.
5. `/ingest.md` funnel 계약.
6. 3-플랫폼 미러(4 agent) + grep parity.
7. 전체 스위트 + §6 스모크.
## 8. 메모
- `wiki_rules.py` 가 A(구조)·B(verdict)·C(stats) 의 공유 SSOT 로 계속 성장 — 전부 *기계장치*(스키마/검증), 정책은 agent `.md`+rules.
- self-report 한계는 인정 — Spec C 는 "조용한 누락을 보이게" 가 목표(deep-research 의 결정론 funnel 은 Workflow=Spec D 에서만 가능).