Skip to main content
Glama
README.md
# 위클리-검증 (weekly-verify-mcp)

작성된 주간 업무 보고(`.docx`)를 원본 데이터(`.xlsx`)와 대조해 **틀린 값 · 빠진 항목 ·
데이터와 어긋난 서술**을 찾아 사람에게 돌려주는 개인용 MCP 서버.

> **고치지 않는다. 지적만 한다.** 수정은 사람이 한다.

전체 설계는 [docs/검증프로세스.md](docs/검증프로세스.md)를 보십시오.

---

## 현재 상태 — **P0~P5 완료 · Codex 등록됨**

| 단계 | 내용 | 상태 |
|---|---|:---:|
| **P0** | 골격 + 오염 데이터셋 | ✅ |
| **P1** | 채점기 (`run_eval.py`) — 판정 엔진보다 먼저 | ✅ |
| **P2** | 원본 파서 + 판정 5종 엔진 + L3 누락 | ✅ |
| **P3** | 주장 추출 + 값 대조(L1·L2) + 서술 상충(L4) | ✅ |
| **P4** | MCP 도구 10개 래핑 + 스모크 | ✅ |
| **P5** | Codex 등록 + 기동 확인 | ✅ |

---

## 검증 4계층

이 도구의 설계 전체가 이 표에서 나옵니다.

| 계층 | 묻는 것 | 판정 | 심각도 |
|---|---|---|---|
| **L1 존재** | 이 값이 원본에 있는가 | 코드 | `error` |
| **L2 정합** | *해당 과제의 해당 필드*와 일치하는가 | 코드 | `error` |
| **L3 누락** | 원본에서 나와야 할 판정이 빠졌는가 | 코드 | `error` |
| **L4 해석** | 값은 맞지만 서술이 어긋나는가 | **사람** | `warning` 고정 |

- **L1만으로는 부족하다.** `L-18`의 값을 `L-19` 행에 옮겨 적으면 두 값 다 원본에 존재하므로
  L1은 통과한다. 과제코드까지 맞춰 보는 L2만이 잡는다.
- **L3가 이 도구의 존재 이유다.** 사람이 주간보고에서 틀리는 것은 대개 숫자가 아니라
  **안 쓴 항목**이다. 틀린 숫자는 눈에 띄지만 없는 줄은 눈에 띄지 않는다.
- **L4는 구조적으로 `error`가 될 수 없다.** `harness.Finding`이 이를 강제하고
  `tests/test_harness.py`가 잠근다. 확신하지 못하는 검사에 차단 권한을 주면 정상 보고서가
  막히고, 사람이 검사기를 꺼 버린다.

---

## 게이트 6종

`evals/weekly-verify.yaml`이 엔진에게 요구하는 것. `uv run python scripts/run_eval.py`로 채점합니다.

| ID | 요구 | 차단 |
|---|---|:---:|
| G1 검출률 | C01~C07 error급 오염을 하나도 놓치지 않는다 | ✅ |
| G2 오탐 | 정상 보고서(C10)에 error를 내지 않는다 | ✅ |
| G3 심각도 | C08·C09에 error를 내지 않는다 | ✅ |
| G4 속도 | 보고서 1건 60초 이내 | ✅ |
| G5 근거 | 모든 error가 계층별 필수 좌표를 채운다 | ✅ |
| G6 계층정확성 | 찾아낸 지적을 올바른 계층으로 분류한다 | ⬜ 관찰용 |

### 좌표 요구가 계층마다 다르다 (G5)

| 계층 | 보고서위치 | 원본위치 | 이유 |
|---|:---:|:---:|---|
| L1 | ✅ | — | 원본에 **없는** 값이라 가리킬 셀이 없다 |
| L2 | ✅ | ✅ | 양쪽에 다 있다 |
| L3 | — | ✅ | 보고서에 **없는** 항목이라 가리킬 위치가 없다 |
| L4 | ✅ | — | |

채울 수 없는 좌표를 억지로 채우면 사람이 엉뚱한 곳을 열어 보고도 확인했다고 믿게 됩니다.

### N/A는 통과가 아니다

"안 좋은 짓을 하지 마라" 류 게이트(G2·G3·G5)는 **엔진이 아무것도 내놓지 않으면 저절로
만족됩니다.** 그것을 PASS로 적으면 기능이 하나도 없는 엔진이 6개 중 5개를 통과한 것처럼
보입니다. 그래서 평가할 근거가 없으면 `N/A`로 적고, N/A는 통과로 세지 않습니다.

### 현재 채점 결과 (P3 시점 — 전부 통과)

```
C01 수치 조작 [OK] 1/1    C05 지연 누락  [OK] 3/3    C09 반올림 [OK] 0/0
C02 날짜 오기 [OK] 1/1    C06 미제출 은폐 [OK] 3/3    C10 정상   [OK] 오탐 0
C03 값 오배치 [OK] 1/1    C07 역행 무시  [OK] 1/1
C04 없는 과제 [OK] 1/1    C08 서술 상충  [OK] 0/0

[PASS] G1_검출률  11/11 (100%)   [PASS] G4_속도   최장 0.1초 / 제한 60초
[PASS] G2_오탐   error 0건       [PASS] G5_근거   좌표 누락 0건 / error 12건
[PASS] G3_심각도  위반 0건        [PASS] G6_계층정확성  11/11 (100%)
```

G5가 error **12건**을 검사했는데 기대는 11건입니다. C01의 오염이 두 표의 셀을 각각
바꿨기 때문이며, **두 곳 다 고쳐야 하므로 중복이 아닙니다.**

---

## 폴더

```
weekly-verify-mcp/
├─ docs/검증프로세스.md      설계 문서 (13개 절)
├─ src/weekly_verify/
│  ├─ harness.py             도구 응답 규약 · Finding · 두 좌표계
│  ├─ sources.py             원본 xlsx 파서 + DATA_ROOT 가드
│  ├─ findings.py            판정 5종 (원본만 보고 계산)
│  ├─ report.py              보고서 docx 파서
│  ├─ claims.py              주장 추출 (규칙 기반, LLM 미사용)
│  ├─ values.py              L1 존재 · L2 정합 + 억제 규칙 3종
│  ├─ completeness.py        L3 누락 검사
│  ├─ narrative.py           L4 서술 상충 (warning 고정)
│  ├─ verify.py              진입점 — 네 계층 조립 + 계층 간 중복 제거
│  ├─ record.py              검증 결과 문서 생성·저장 + 승인 토큰
│  └─ server.py              MCP 도구 10개 · 리소스 2 · 프롬프트 1
├─ scripts/
│  ├─ make_fixtures.py       오염 시나리오 10종 생성
│  ├─ check_fixtures.py      픽스처 자기검증 (30개 검사)
│  ├─ run_eval.py            채점기 (게이트 6종)
│  ├─ smoke_stdio.py         stdio 기동 + 하네스 검사 15종
│  └─ verify_registration.py 설정 파일의 절대경로로 기동 확인
├─ tests/
│  ├─ test_harness.py        하네스 규약 잠금 (16개)
│  ├─ test_eval_contract.py  채점기 검증 — 가짜 엔진 7종 (20개)
│  ├─ test_findings.py       판정 엔진 ↔ 정답지 1건씩 대조 (27개)
│  ├─ test_completeness.py   L3 누락 검사 (31개)
│  ├─ test_values.py         L1·L2 + 억제 규칙 (53개)
│  ├─ test_narrative.py      L4 서술 상충 (43개)
│  └─ test_server_contract.py docstring ↔ 실제 규칙 잠금 (63개)
├─ data/
│  ├─ 원본/                  ← 서버가 읽는 유일한 곳
│  │  ├─ 마스터_주간보고_누적.xlsx
│  │  └─ 제출_2026-W35/ (담당자 6명)
│  ├─ 보고서/ C01~C10.docx   검증 대상
│  └─ 출력/                  검증 결과 기록 (저장 산출물)
├─ config/                   등록 설정 + README
├─ templates/                검증 결과 문서 템플릿
└─ evals/                    🚫 서버 접근 금지 — evals/README.md 참조
   ├─ 정답지_2026-W35.xlsx
   ├─ fixtures_manifest.json 픽스처가 담고 있는 사실
   └─ weekly-verify.yaml     엔진에게 요구하는 정책
```

`manifest`와 `suite`를 나눈 이유는 관심사가 다르기 때문입니다 — 전자는 생성기가 만든
사실이라 docx가 바뀌면 함께 바뀌고, 후자는 사람이 정한 정책이라 docx와 무관합니다.
한 파일에 합치면 기대값이 두 곳에 생겨 드리프트합니다.

---

## 오염 시나리오 10종

`data/보고서/C01~C10.docx`. 각 파일의 기대 검출은 `evals/fixtures_manifest.json`에 있습니다.

| 코드 | 유형 | 심는 내용 | 기대 |
|---|---|---|---|
| C01 | 수치 조작 | L-01 금주 진척률 80 → 90 | `error` L2 |
| C02 | 날짜 오기 | L-08 계획완료일 하루 밀기 | `error` L2 |
| C03 | **값 오배치** | L-18의 60을 L-19 행에 (정답 40) | `error` L2 |
| C04 | 없는 과제 | L-31 행 추가 | `error` L1 |
| C05 | **지연 누락** | 지연 6→3건, 요약 건수도 함께 조정 | `error` L3 ×3 |
| C06 | 미제출 은폐 | 미제출 표 삭제 + 전주 값으로 채움 | `error` L3 ×3 |
| C07 | 역행 무시 | 역행 1건 미언급 | `error` L3 |
| C08 | 서술 상충 | 64일 지연을 "순조롭게 진행 중" | `warning` L4 |
| C09 | 반올림 | 62.2% → 62% | `warning` |
| C10 | **정상** | 오염 없음 | `error` **0건** |

### C05 · C06 · C07이 핵심이다

이 셋은 오염된 보고서의 **내부 정합을 유지한다.** 지연 3건을 지우면서 요약표 건수도
6→3으로 함께 고쳤다. 그래서 보고서만 읽어서는 아무 모순이 없고, **원본과 대조해야만**
드러난다. 삭제한 L-02 · L-08 · L-19는 담당자가 `이슈리스크` 칸을 비워둔 바로 그 3건이다.

### C09 · C10도 그만큼 중요하다

**오탐 방지 테스트**다. 정상 보고서에 `error`를 내는 검사기는 아무도 쓰지 않는다.

---

## 억제 규칙 — 한 번의 실수를 한 건으로 보고한다

오염이 한 곳만 바뀌어도 파생 값이 연쇄로 틀립니다. C02는 `계획완료일`만 하루 밀었는데
`경과일`도 함께 틀리고, C03은 `금주진척률`만 바꿨는데 `증감`도 틀립니다. 그대로 지적하면
한 번 잘못 적은 것이 지적 두 건이 되어, 사람이 두 곳을 찾아다니고 지적 개수로 심각도를
가늠할 수도 없게 됩니다.

| # | 규칙 | 억제되지 않는 경우 |
|---|---|---|
| 1 | 과제가 원본에 없으면 그 행의 다른 값은 대조하지 않는다 | — (L1 지적 1건으로 끝) |
| 2 | 미제출 과제의 금주 값은 대조하지 않는다 | 전주 값은 그대로 대조한다 |
| 3 | 파생 값은 재료가 이미 지적되었으면 건너뛴다 | 재료가 맞으면 파생 값 오류를 지적한다 |
| 4 | L3가 다룬 구분의 집계 건수 지적을 누른다 | 항목은 맞고 건수만 틀리면 지적한다 |

**억제는 무조건이 아닙니다.** 각 규칙마다 억제되는 쪽과 억제되지 않는 쪽을 짝으로
테스트했습니다 — 억제 규칙은 "이것도 지적해 주면 좋겠다" 싶은 순간 조용히 무너집니다.

## 검증하지 않는 열은 선언한다

`claims.미검증_열`에 5종을 이유와 함께 적어 두었습니다 (`상태`는 판정 라벨이므로 L3 소관,
역행 표의 `O (완료 2026-08-20)` 형태는 자유 서식이라 대조가 불안정, 등). 조용히
건너뛰면 "전부 검증했다"로 읽히는데 실제로는 보지 않은 열이 있습니다.

---

## 실행

```bash
uv sync --extra dev
```

```bash
uv run python scripts/make_fixtures.py
```

```bash
uv run python scripts/check_fixtures.py
```

```bash
uv run python scripts/run_eval.py
```

```bash
uv run python -m pytest -q
```

```bash
uv run python scripts/smoke_stdio.py
```

```bash
uv run python scripts/verify_registration.py
```

---

## MCP 도구 10개

| # | 도구 | 단계 | 쓰기 |
|---|---|---|:---:|
| 1 | `list_verification_targets` | DISCOVER | |
| 2 | `read_source_data` | SOURCE | |
| 3 | `compute_baseline_findings` | BASELINE | |
| 4 | `read_report` | REPORT | |
| 5 | `extract_report_claims` | CLAIM | |
| 6 | `verify_claims` | VERIFY (L1·L2) | |
| 7 | `check_completeness` | COMPLETE (L3) | |
| 8 | `flag_narrative_risks` | NARRATE (L4) | |
| 9 | `preview_verification_report` | PREVIEW | |
| 10 | `save_approved_verification` | SAVED | ✅ |

**빠른 경로**: 9번 하나로 네 계층을 모두 돌립니다. 중간 도구들은 "왜 그렇게 판정했는지"를
사람에게 보여줄 때 씁니다.

리소스 `template://verification` · `report://{보고서}`, 프롬프트 `verify_weekly_report`.

### 저장은 '지적 기록'이다 — error가 막지 않는다

설계 초안에는 "`error`가 하나라도 있으면 저장을 거부한다"고 적혀 있었는데 **틀린
규칙이었습니다.** 저장하는 것은 지적 기록이고, error가 있을 때가 기록이 가장 필요한
때입니다. 그때 막으면 도구가 쓸모없어집니다.

막아야 하는 것은 다른 것이었습니다.

| # | 거부 조건 | 이유 |
|---|---|---|
| 1 | 승인토큰이 현재 지적 내용과 불일치 | 사람이 확인하지 않은 내용이 파일로 남는 것을 막는다 |
| 2 | `error`가 있는데 결론이 '통과' | 기록이 아니라 거짓이 된다 |

결론은 `통과 · 보류 · 반송` 중 하나입니다. 승인토큰은 지적 내용에서 계산되므로,
지적이 하나라도 달라지면 토큰이 어긋나 저장이 거부됩니다.

이 두 조건은 `save_approved_verification`의 **도구 설명에 명시**되어 있고,
`tests/test_server_contract.py`가 설명과 실제 코드를 대조해 잠급니다 — 모델은 코드를
읽지 않으므로, 설명에 없는 규칙은 모델에게 존재하지 않습니다.

---

## 등록 (P5)

**Codex 등록 완료** — `~/.codex/config.toml` 에 `[mcp_servers.weekly-verify]` 가
들어가 있습니다 (백업: `config.toml.bak-2026-08-26`). Codex를 재시작하면 도구가 뜹니다.

Claude Desktop은 `%APPDATA%\Claude` 가 부분 차단 경로라 도구가 써 넣지 못합니다.
[config/claude_desktop_config.example.json](config/claude_desktop_config.example.json)
내용을 직접 병합하십시오 — 의도된 차단이며 우회하지 않습니다.

`command`는 반드시 **절대경로**여야 합니다. 데스크톱 앱은 로그인 셸의 PATH를
물려받지 않아서, `uv`라고만 적으면 터미널에서는 되는데 앱에서는 서버가 뜨지 않습니다.

```bash
uv run python scripts/verify_registration.py
```

예시 설정 2종과 **실제 등록된 라이브 설정**까지 3개를 확인합니다. 예시가 맞아도
실제 등록이 틀리면 앱에서는 안 뜨기 때문입니다.

---

## 데이터에 대하여

`data/`의 모든 데이터는 **실습용 가상(허구) 데이터**입니다. 담당자명은 고전소설 인물명,
과제는 실재하지 않는 물류 자동화 주제입니다. 사내 실데이터·개인정보는 일절 포함되어
있지 않으며, **실제 사내 파일을 이 폴더에 넣지 마십시오.**

원본 데이터는 `mx-agentic-ai-day1-prd`에서 가져왔습니다.

TDQS

A4.6/5.0

Scored across 10 tools

Disambiguation5/5

Each tool owns a distinct stage or verification level (L1/L2 vs L3 vs L4), and the descriptions explicitly cross-reference when another tool should be used instead. The aggregate preview tool is clearly positioned as a convenience wrapper over the intermediate tools, so there is no real ambiguity.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern with specific, informative verbs: list, read, compute, extract, verify, check, flag, preview, save. The naming style is uniform and each name accurately predicts the tool's operation.

Tool Count5/5

Ten tools map cleanly onto the stages of the verification pipeline: discover targets, load source, compute expected findings, parse report, extract claims, verify values, check completeness, flag narrative risks, preview, and save. The count is well-scoped and every tool has a clear purpose.

Completeness5/5

The set covers the full verification lifecycle from target discovery through source normalization, report parsing, claim extraction, multi-level checks, all-level preview, and an approval-gated save. There are no obvious dead ends: the only write tool is intentionally gated, and every intermediate tool feeds into the preview/save flow.

Maintenance

ActivityMaintained
ResponsivenessNo issues