From 00bf67419e8f3545de603df0e511140d86e7de07 Mon Sep 17 00:00:00 2001 From: amnotyoung Date: Sun, 2 Aug 2026 12:03:53 +0900 Subject: [PATCH 1/2] =?UTF-8?q?release:=200.10.0=20=E2=80=94=20Claude?= =?UTF-8?q?=C2=B7Codex=20=EA=B3=B5=EC=9A=A9=20=EC=84=A4=EC=B9=98=ED=98=95?= =?UTF-8?q?=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9E=AC=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 평가자가 이 저장소 안이 아니라 자기 작업 폴더에서 쓰도록, 저장소 자체를 두 호스트가 함께 읽는 설치형 플러그인으로 만든다. 워크플로는 `skills/`에 있고 `agents/`가 역할을 담으며, 문서의 옛 `.claude/agents/` 경로 표기를 새 레이아웃으로 정리한다. - Codex 매니페스트 추가 — `.codex-plugin/plugin.json`, `.agents/plugins/marketplace.json`. Claude 매니페스트와 같은 name/version을 써서 한 플러그인 정체성을 유지한다 - `.claude-plugin/plugin.json` 0.9.0 → 0.10.0, `$schema`·`displayName` 추가. 마켓플레이스 항목의 중복 version 필드는 제거(매니페스트가 정본) - 라이선스 표기를 `MIT AND CC-BY-4.0`으로 정정 — 코드는 MIT, 에이전트 지침· 다이제스트·템플릿 등 콘텐츠는 CC BY 4.0이라는 실제 구성을 반영한다 - README(국·영문)에 Codex·Claude Code 설치 절차와 `$`/`/` 호출 문법 대조표 - `AGENTS.md`(+영문 미러)를 복제 저장소 직접 실행용 순차 폴백으로 재정의 — 설치형 경로는 `skills/`가 담당한다 - `skills/evaluate`·`impact-review`에 호스트 호환 절 추가, frontmatter의 when_to_use를 description으로 통합(트리거 문구·비적용 사례 포함) - CONTRIBUTING·LICENSE-CONTENT·do-no-harm·DPG 신청서·platform-independence· standards의 경로 표기 갱신, `agents/quality-verifier.md`의 상호참조를 실재하는 `report-quality-inspector`(v2, 합격선 60점)로 교정 Co-Authored-By: Claude Fable 5 --- .agents/plugins/marketplace.json | 20 +++++++++++++ .claude-plugin/marketplace.json | 3 +- .claude-plugin/plugin.json | 6 ++-- .codex-plugin/plugin.json | 44 +++++++++++++++++++++++++++ AGENTS.md | 4 +-- CONTRIBUTING.md | 2 +- LICENSE-CONTENT | 4 +-- README.ko.md | 45 ++++++++++++++++++++-------- README.md | 48 ++++++++++++++++++++++-------- agents/quality-verifier.md | 2 +- docs/do-no-harm.md | 2 +- docs/dpg-application.md | 4 +-- docs/en/AGENTS.md | 4 +-- docs/en/agents/quality-verifier.md | 2 +- docs/platform-independence.md | 8 ++--- docs/standards.md | 3 +- skills/evaluate/SKILL.md | 24 ++++++++++----- skills/impact-review/SKILL.md | 13 +++++--- 18 files changed, 180 insertions(+), 58 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .codex-plugin/plugin.json diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..0e25603 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "deveval-agents", + "interface": { + "displayName": "DevEval Agents" + }, + "plugins": [ + { + "name": "deveval", + "source": { + "source": "local", + "path": "./" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Education & Research" + } + ] +} diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 02bca10..23c3bc4 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,12 +10,11 @@ "name": "deveval", "source": "./", "description": "KOICA/OECD-DAC 기준 ODA 사업평가 보조 에이전트팀 — 기준별 병렬 평정, 평가보고서 품질심사, 영향평가 방법론 검토, 근거 기반 보고서 작성.", - "version": "0.9.0", "author": { "name": "Wooyoung Kim", "url": "https://github.com/amnotyoung" }, - "license": "MIT", + "license": "MIT AND CC-BY-4.0", "keywords": ["oda", "evaluation", "koica", "oecd-dac", "digital-public-good"] } ] diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 7a44a18..3b932c8 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,14 +1,16 @@ { + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "deveval", + "displayName": "DevEval Agents", "description": "KOICA/OECD-DAC 기준 ODA 사업평가 보조 에이전트팀 — 기준별 병렬 평정, 평가보고서 품질심사, 영향평가 방법론 검토, 근거 기반 보고서 작성. 근거 없으면 등급 없음·최종 확정은 사람.", - "version": "0.9.0", + "version": "0.10.0", "author": { "name": "Wooyoung Kim", "url": "https://github.com/amnotyoung" }, "homepage": "https://github.com/amnotyoung/dev-eval-agents", "repository": "https://github.com/amnotyoung/dev-eval-agents", - "license": "MIT", + "license": "MIT AND CC-BY-4.0", "keywords": [ "oda", "evaluation", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..e43fdc9 --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,44 @@ +{ + "name": "deveval", + "version": "0.10.0", + "description": "KOICA/OECD-DAC 기준 ODA 사업평가, 평가보고서 품질심사, 영향평가 방법론 검토를 위한 근거 기반 워크플로.", + "author": { + "name": "Wooyoung Kim", + "url": "https://github.com/amnotyoung" + }, + "homepage": "https://github.com/amnotyoung/dev-eval-agents", + "repository": "https://github.com/amnotyoung/dev-eval-agents", + "license": "MIT AND CC-BY-4.0", + "keywords": [ + "oda", + "evaluation", + "koica", + "oecd-dac", + "development-cooperation", + "digital-public-good" + ], + "skills": "./skills/", + "interface": { + "displayName": "DevEval Agents", + "shortDescription": "근거 기반 ODA 사업평가", + "longDescription": "Evaluate ODA projects against KOICA and OECD-DAC criteria, review evaluation-report quality, inspect impact-evaluation methodology, and draft evidence-linked reports with a mandatory human decision gate.", + "developerName": "Wooyoung Kim", + "category": "Education & Research", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "websiteURL": "https://github.com/amnotyoung/dev-eval-agents", + "privacyPolicyURL": "https://github.com/amnotyoung/dev-eval-agents/blob/main/PRIVACY.md", + "defaultPrompt": [ + "이 ODA 사업을 OECD DAC 기준으로 평가해줘.", + "이 평가보고서의 품질을 KOICA 기준으로 검토해줘.", + "이 영향평가 보고서의 인과추론 방법론을 검토해줘." + ], + "brandColor": "#1B3A57", + "composerIcon": "./assets/logo.svg", + "logo": "./assets/logo.png", + "screenshots": [] + } +} diff --git a/AGENTS.md b/AGENTS.md index 4104ef2..ac1f013 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # DevEval Agents — Codex 지침 (AGENTS.md) > Codex가 이 파일을 자동 로드한다(= Claude Code의 `CLAUDE.md`에 대응하는 Codex판). -> **공용 지식은 `reference/`에 있고 모든 하네스가 공유한다**(Claude Code·Codex·오픈웨이트 러너 `scripts/open_runner.py`). Claude Code판 워크플로는 `CLAUDE.md` + `.claude/agents/` 참조. +> **공용 지식은 `reference/`에 있고 모든 하네스가 공유한다**(Claude Code·Codex·오픈웨이트 러너 `scripts/open_runner.py`). 설치형 Claude/Codex 플러그인의 워크플로는 `skills/` + `agents/`에 있고, 이 파일은 복제한 저장소를 Codex에서 직접 실행할 때 쓰는 폴백이다. > 평가 기준·척도·규정은 실제 KOICA 자료 기반: `reference/`. --- @@ -10,7 +10,7 @@ 너는 **KOICA 사업평가 보조 시스템의 "평가총괄"**이다. ODA 사업을 OECD DAC 기준으로 평가하는 일을 보조한다. -⚠️ **Codex 하네스 특성**: Codex는 Claude Code식 서브에이전트 병렬 위임이 네이티브가 아니다. 따라서 **너 자신이 각 기준을 하나씩 순차로, 서로 독립적으로 평정**한다 — 한 기준을 평가할 때 다른 기준의 결론·점수에 동조하지 말고, **그 기준의 근거만으로** 판단하라. (Claude Code판에서 5명의 평가관이 병렬·블라인드로 하던 "독립성"을, 너는 순차적 자기규율로 재현한다.) +⚠️ **직접 실행 폴백**: 설치형 Codex 플러그인은 `skills/`에서 서브에이전트를 위임하지만, 이 `AGENTS.md` 경로는 의존성이 적은 순차 폴백으로 유지한다. **너 자신이 각 기준을 하나씩 순차로, 서로 독립적으로 평정**한다 — 한 기준을 평가할 때 다른 기준의 결론·점수에 동조하지 말고, **그 기준의 근거만으로** 판단하라. ## 절대 원칙 (NON-NEGOTIABLE) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68806db..d4176a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ evaluation. Contributions of all sizes are welcome. ## What we welcome / 환영하는 기여 - **Agent improvements** — sharper rubrics, clearer instructions, better output - formats for the agents in `.claude/agents/`. + formats for the agents in `agents/`. - **Reference corrections** — fixes/updates to the `reference/` digests **with a citation** to the published KOICA/KIEP source (name + article/page). - **Harness adapters** — new ways to run the same agents (e.g. additional diff --git a/LICENSE-CONTENT b/LICENSE-CONTENT index 1fcb1ec..7a089a4 100644 --- a/LICENSE-CONTENT +++ b/LICENSE-CONTENT @@ -26,8 +26,8 @@ SCOPE OF THIS LICENSE CC BY 4.0 applies to the CONTENT components of this project, namely: - - All Markdown agent instructions (`CLAUDE.md`, `AGENTS.md`, - `.claude/agents/*.md`, and their English counterparts under `docs/en/`) + - All Markdown agent instructions (`CLAUDE.md`, `AGENTS.md`, `agents/*.md`, + and their English counterparts under `docs/en/`) - The KOICA methodology digests under `reference/` - Templates under `templates/` and samples under `samples/` - Project documentation under `docs/` and the README files diff --git a/README.ko.md b/README.ko.md index c55ddb6..3f0fd64 100644 --- a/README.ko.md +++ b/README.ko.md @@ -123,31 +123,52 @@ KOICA 평가는 **유형이 다르다.** 이 시스템은 두 유형을 구분 > 처음이라면 동봉된 가상 샘플 [`samples/sample-evaluation-report.md`](samples/sample-evaluation-report.md)로 종료평가를 돌려 보라 — 일부 성과지표를 **일부러 비워 둬**서, "근거 없으면 평가 불가" 게이트가 실제로 작동하는 걸 볼 수 있다. -**Claude Code — 플러그인으로 설치** (권장: 이 저장소 안이 아니라 *자기 작업 폴더*에서 쓴다): +### 플러그인으로 설치 + +플러그인을 한 번 설치한 뒤, 이 저장소 안이 아니라 평가 자료가 있는 자기 작업 +폴더에서 사용한다. + +**Codex**(터미널): + +```bash +codex plugin marketplace add amnotyoung/dev-eval-agents --ref main +codex plugin add deveval@deveval-agents +``` + +설치 후 새 Codex 작업을 시작한다. Codex는 동봉 훅을 별도로 검토하므로 완료 엔진을 +사용하려면 `/hooks`에서 내용을 확인하고 신뢰한다. + +**Claude Code**(대화형 세션 안): + ```bash /plugin marketplace add amnotyoung/dev-eval-agents /plugin install deveval@deveval-agents /reload-plugins ``` -그다음, 평가 자료가 있는 폴더 어디서든: -| 스킬 | 하는 일 | -|------|---------| -| `/deveval:evaluate` | 사업평가 — 5~6기준 병렬 평정 → 종합점수·등급(안) | -| `/deveval:quality-review` | 평가보고서 품질심사 — 24문항/100점/A~D | -| `/deveval:impact-review` | 영향평가 방법론 검토 — 5축/10질문 | -| `/deveval:write-report` | 보고서 작성 — 작성→수치검사→서술검증→사람 | +이후 같은 네 워크플로를 호스트별 문법으로 호출한다. + +| 워크플로 | Codex | Claude Code | 하는 일 | +|----------|-------|-------------|---------| +| 사업평가 | `$deveval:evaluate` | `/deveval:evaluate` | 5~6기준 독립 평정 → 종합점수·등급(안) | +| 품질심사 | `$deveval:quality-review` | `/deveval:quality-review` | 24문항/100점/A~D 평가보고서 품질심사 | +| 영향평가 검토 | `$deveval:impact-review` | `/deveval:impact-review` | 5축/10질문 인과 방법론 검토 | +| 보고서 작성 | `$deveval:write-report` | `/deveval:write-report` | 작성→수치검사→서술검증→사람 | -작업 산출물(`.omo/eval-plan.md`, `.omo/draft-report*.md`)은 **사용자 폴더**에 남는다 — 플러그인 디렉토리는 읽기전용 지식이다. 플러그인이 켜져 있는 동안 동봉된 `deveval-consistency-check` 명령이 `PATH`에 오른다. +작업 산출물(`.omo/eval-plan.md`, `.omo/draft-report*.md`)은 **사용자 폴더**에 남는다 — 플러그인 디렉토리는 읽기전용 지식이다. Claude Code는 동봉된 `deveval-consistency-check` 명령을 `PATH`에 올리고, 다른 호스트에서는 공용 스킬이 같은 검사기를 절대경로로 실행한다. + +Claude 플러그인을 설치 없이 써 보거나 개발하려면: -설치 없이 써 보려면(또는 개발하려면): ```bash git clone https://github.com/amnotyoung/dev-eval-agents claude --plugin-dir ./dev-eval-agents ``` -**Codex** (`AGENTS.md` 단일 에이전트 순차 독립 평정): +Codex에서 플러그인 설치 없이 복제한 저장소를 직접 실행하려면 `AGENTS.md` +폴백을 쓴다. + ```bash +cd dev-eval-agents codex exec "samples/sample-evaluation-report.md 이 사업을 DAC 기준으로 평가해줘" ``` **오픈웨이트 모델** (독점 API 없이 — [Ollama](https://ollama.com) + 오픈 가중치): @@ -181,7 +202,7 @@ DevEval의 증거 게이트는 "근거 없으면 등급 없음"이다. 같은 실제로 작동하고 실제 KOICA 평가와 부합하는지의 기록 → **[`docs/validation-log.md`](docs/validation-log.md)** -- **실물 e2e 4회** — Claude Code(`claude -p`, `.claude/agents/*`) 3회 + **Codex(`codex exec`, `AGENTS.md`) 1회** (시뮬레이션 아님) +- **실물 e2e 4회** — Claude Code(`claude -p`, `agents/*`) 3회 + **Codex(`codex exec`, `AGENTS.md`) 1회** (시뮬레이션 아님) - **실제 보고서 4건 대조** — 캄보디아(등급 일치)·미얀마(기준별 방향 일치)·파키스탄(약점 방향 일치)·베트남(평가 유형 구분) - **게이트 실증** — 근거 없으면 평가 불가·종합 보류·사람 게이트가 실제로 작동 - ⚠️ 학습·실험 자체검증(표본 소수). 전문가 교차검증·표본 확대는 진행 과제. diff --git a/README.md b/README.md index 15100f2..9b748d8 100644 --- a/README.md +++ b/README.md @@ -145,34 +145,56 @@ New here? Run the bundled fictional sample some result indicators are deliberately left blank, so you can watch the **"no evidence → no grade"** gate fire. -**Claude Code — install as a plugin** (recommended: use it from *your own* working -folder, not from inside this repo): +### Install as a plugin + +Install the plugin once, then use it from the folder that contains your +evaluation material rather than from inside this repository. + +**Codex** (terminal): + +```bash +codex plugin marketplace add amnotyoung/dev-eval-agents --ref main +codex plugin add deveval@deveval-agents +``` + +Start a new Codex task after installation. Codex reviews bundled hooks +separately; inspect and trust the completion hook with `/hooks` if you want it +enabled. + +**Claude Code** (inside an interactive session): + ```bash /plugin marketplace add amnotyoung/dev-eval-agents /plugin install deveval@deveval-agents /reload-plugins ``` -Then, in whatever folder your evaluation material lives: -| Skill | What it does | -|-------|--------------| -| `/deveval:evaluate` | project evaluation — 5–6 criteria in parallel → composite score + draft grade | -| `/deveval:quality-review` | evaluation-report quality inspection — 24 items / 100 pts / A–D | -| `/deveval:impact-review` | impact-evaluation methodology review — 5 axes / 10 questions | -| `/deveval:write-report` | report drafting — write → numeric check → narrative verification → human | +Then invoke the same four workflows with the syntax for your host: + +| Workflow | Codex | Claude Code | What it does | +|----------|-------|-------------|--------------| +| Evaluate | `$deveval:evaluate` | `/deveval:evaluate` | 5–6 independent criteria → composite score + draft grade | +| Quality review | `$deveval:quality-review` | `/deveval:quality-review` | 24-item / 100-point / A–D report-quality inspection | +| Impact review | `$deveval:impact-review` | `/deveval:impact-review` | 5-axis / 10-question methodology review | +| Write report | `$deveval:write-report` | `/deveval:write-report` | draft → numeric check → narrative verification → human | Your working files (`.omo/eval-plan.md`, `.omo/draft-report*.md`) stay in **your** -folder; the plugin directory is read-only knowledge. The bundled -`deveval-consistency-check` command is on `PATH` while the plugin is enabled. +folder; the plugin directory is read-only knowledge. Claude Code exposes the +bundled `deveval-consistency-check` command on `PATH`; the shared skills use the +same checker by absolute path on hosts that do not expose plugin `bin/` entries. + +To try the Claude plugin without installing it (or to develop it): -To try it without installing (or to develop it): ```bash git clone https://github.com/amnotyoung/dev-eval-agents claude --plugin-dir ./dev-eval-agents ``` -**Codex** (single-agent sequential, `AGENTS.md`): +To run the cloned repository directly in Codex without installing the plugin, +use the `AGENTS.md` fallback: + ```bash +cd dev-eval-agents codex exec "samples/sample-evaluation-report.md 이 사업을 DAC 기준으로 평가해줘" ``` diff --git a/agents/quality-verifier.md b/agents/quality-verifier.md index 94945e5..e3ab9fa 100644 --- a/agents/quality-verifier.md +++ b/agents/quality-verifier.md @@ -62,6 +62,6 @@ KOICA 2024는 "보고서 서술내용과 평가등급 배정 간 괴리가 없 - 사람에게 넘겨도 되는가? 사람이 특히 주의할 부분은? ``` -> 참고: 이 검증자는 *개별 평가의 근거·점수*를 본다. **평가보고서 전체의 품질을 채점**하는 일(KOICA 2024: 24문항/100점/A~D, 합격선 70점, 2024 다이제스트 §5)은 다음 슬라이스의 "평가보고서 품질심사관" 에이전트로 구현 예정이다. +> 참고: 이 검증자는 *개별 평가의 근거·점수*를 본다. **평가보고서 전체의 품질을 채점**하는 일은 별도 `report-quality-inspector`가 품질검토 가이드라인 v2(24문항/100점/A~D, 합격선 60점)로 수행한다. 어조: 깐깐하고 단호하게. "대충 맞는 것 같다"는 없다. 확인되거나, 안 되거나 둘 중 하나다. diff --git a/docs/do-no-harm.md b/docs/do-no-harm.md index 84c5a86..db1b4bd 100644 --- a/docs/do-no-harm.md +++ b/docs/do-no-harm.md @@ -49,7 +49,7 @@ governed by the [Code of Conduct](../CODE_OF_CONDUCT.md). ## AI-specific safeguards / AI 특유 안전장치 These are the heart of the do-no-harm design and are enforced in the agents' -instructions (`CLAUDE.md`, `AGENTS.md`, `.claude/agents/*`), grounded in KOICA +instructions (`CLAUDE.md`, `AGENTS.md`, `skills/*/SKILL.md`, `agents/*`), grounded in KOICA evaluation ethics (Regulation No. 536) and the OECD-DAC framework: | Safeguard / 안전장치 | How it works / 작동 방식 | diff --git a/docs/dpg-application.md b/docs/dpg-application.md index 29d8d9c..c8af9e2 100644 --- a/docs/dpg-application.md +++ b/docs/dpg-application.md @@ -83,7 +83,7 @@ Repo: https://github.com/amnotyoung/dev-eval-agents - *Own software* (the `boulder.sh` Stop hook, `scripts/open_runner.py`, the notebook, config) → **MIT**, © 2026 amnotyoung ([`LICENSE`](https://github.com/amnotyoung/dev-eval-agents/blob/main/LICENSE)). - - *Own documentation & content* (agent instructions in `.claude/agents/`, + - *Own documentation & content* (agent instructions in `agents/`, `CLAUDE.md`, `AGENTS.md`, `templates/`, `docs/`) → **CC BY 4.0** ([`LICENSE-CONTENT`](https://github.com/amnotyoung/dev-eval-agents/blob/main/LICENSE-CONTENT)). - *`reference/` digests* → the project's **own original wording** summarizing @@ -284,7 +284,7 @@ Repo: https://github.com/amnotyoung/dev-eval-agents - **Where is PII processed/used, and which components access it? (purpose limitation)** PII exists only **transiently**, inside the single document supplied for one run, held in memory during processing and not persisted. Access - is limited to: the **agent instructions** (`.claude/agents/*`, `CLAUDE.md`, + is limited to: the **agent instructions** (`agents/*`, `CLAUDE.md`, `AGENTS.md`) that read the document **read-only** to draft an evaluation; and the **LLM harness/model** the user chose (hosted → the text is sent to that provider; local `scripts/open_runner.py` → it stays on `localhost:11434`). Nothing else has diff --git a/docs/en/AGENTS.md b/docs/en/AGENTS.md index dd7f80f..d8dbbc4 100644 --- a/docs/en/AGENTS.md +++ b/docs/en/AGENTS.md @@ -3,7 +3,7 @@ # DevEval Agents — Codex Instructions (AGENTS.md) > Codex loads this file automatically (= the Codex counterpart to Claude Code's `CLAUDE.md`). -> **The shared knowledge lives in `reference/` and is shared across all harnesses** (Claude Code, Codex, and the open-weight runner `scripts/open_runner.py`). For the Claude Code workflow, see `CLAUDE.md` + `.claude/agents/`. +> **The shared knowledge lives in `reference/` and is shared across all harnesses** (Claude Code, Codex, and the open-weight runner `scripts/open_runner.py`). Installed Claude/Codex plugin workflows live in `skills/` + `agents/`; this file is the fallback for running a cloned repository directly in Codex. > The evaluation criteria, scales, and regulation are based on actual KOICA materials: `reference/`. --- @@ -12,7 +12,7 @@ You are the **"Evaluation Lead" of the KOICA project-evaluation support system**. You support the evaluation of ODA projects against the OECD DAC criteria. -⚠️ **Codex harness characteristic**: Codex does not natively support Claude Code–style parallel delegation to sub-agents. Therefore, **you yourself rate each criterion one by one, sequentially and independently of one another** — when evaluating one criterion, do not conform to the conclusions or scores of the other criteria; judge **on the evidence of that criterion alone**. (The "independence" that, in the Claude Code version, 5 evaluation officers achieved in parallel and blind, you reproduce through sequential self-discipline.) +⚠️ **Direct-run fallback**: the installed Codex plugin delegates from `skills/`, but this `AGENTS.md` path remains a low-dependency sequential fallback. **Rate each criterion one by one, sequentially and independently** — when evaluating one criterion, do not conform to conclusions or scores from other criteria; judge **only from the evidence for that criterion**. ## Absolute Principles (NON-NEGOTIABLE) diff --git a/docs/en/agents/quality-verifier.md b/docs/en/agents/quality-verifier.md index 1c26f04..12e7ef4 100644 --- a/docs/en/agents/quality-verifier.md +++ b/docs/en/agents/quality-verifier.md @@ -64,6 +64,6 @@ KOICA 2024 mandates that you "carefully check whether there is any gap between t - Is it acceptable to hand off to a human? What should the human pay particular attention to? ``` -> Note: This verifier looks at the *evidence and scores of individual evaluations*. The task of **scoring the quality of the entire evaluation report** (KOICA 2024: 24 questions / 100 points / A–D, passing line 70 points, 2024 Digest §5) is to be implemented in the "Evaluation Report Quality Inspector" agent in a later slice. +> Note: This verifier looks at the *evidence and scores of individual evaluations*. The separate `report-quality-inspector` scores **the quality of the entire evaluation report** under Quality Review Guideline v2 (24 questions / 100 points / A–D, passing line 60 points). Tone: Rigorous and firm. There is no "seems roughly right." Either it is confirmed, or it is not — one of the two. diff --git a/docs/platform-independence.md b/docs/platform-independence.md index f75ff3e..c950054 100644 --- a/docs/platform-independence.md +++ b/docs/platform-independence.md @@ -7,8 +7,8 @@ **DevEval Agents does not depend on any single proprietary platform or model.** Its substance is *portable Markdown*: agent instructions (`CLAUDE.md`, -`AGENTS.md`, `.claude/agents/*.md`) plus a shared knowledge base (`reference/`) -and one small shell hook. The only mandatory dependency is **"a capable LLM +`AGENTS.md`, `agents/*.md`, `skills/*/SKILL.md`) plus a shared knowledge base +(`reference/`) and one small shell hook. The only mandatory dependency is **"a capable LLM agent-harness"** — a *category* of tool, not a specific commercial product. The same agents already run on three independent stacks, including a **fully open-weight** one. @@ -22,8 +22,8 @@ Markdown(에이전트 지침 + 공용 지식 `reference/` + 소형 shell 훅)이 | Stack / 스택 | Harness | Model / 모델 | Openness / 개방성 | Status | |---|---|---|---|---| -| 1 | Claude Code (`.claude/agents/`, parallel sub-agents) | Anthropic Claude | proprietary | validated (README §Validation) | -| 2 | Codex (`AGENTS.md`, single-agent sequential) | GPT-5.5 | proprietary | validated (`codex exec`, Slice 8) | +| 1 | Claude Code (`.claude-plugin/`, `agents/`, parallel subagents) | Anthropic Claude | proprietary | validated (README §Validation) | +| 2 | Codex (`.codex-plugin/` + shared skills/subagents; `AGENTS.md` direct-run fallback) | OpenAI GPT | proprietary | direct-run validated; plugin package schema-validated | | 3 | **Ollama + `scripts/open_runner.py`** | **Qwen2.5-14B (open weights, Apache-2.0)** | **fully open** | **validated locally — see below** | The reference knowledge in `reference/` is shared unchanged across all three. diff --git a/docs/standards.md b/docs/standards.md index 36d4ac4..6d4e983 100644 --- a/docs/standards.md +++ b/docs/standards.md @@ -39,7 +39,8 @@ standards — it does not invent its own criteria: (`LICENSE-CONTENT`), both on the DPGA-approved lists (OSI / Creative Commons), with a clear per-component mapping. - **Model/harness-agnostic interfaces** — the same Markdown agents run on Claude - Code (`.claude/agents/`), Codex (`AGENTS.md`), and open-weight models via the + Code (`.claude-plugin/` + `agents/`), Codex (`.codex-plugin/` + shared skills, + with `AGENTS.md` as a direct-run fallback), and open-weight models via the Ollama HTTP API (`scripts/open_runner.py`). See [`platform-independence.md`](platform-independence.md). diff --git a/skills/evaluate/SKILL.md b/skills/evaluate/SKILL.md index 2de6030..5870b18 100644 --- a/skills/evaluate/SKILL.md +++ b/skills/evaluate/SKILL.md @@ -1,7 +1,6 @@ --- name: evaluate -description: ODA 사업을 OECD DAC/KOICA 기준으로 평가해 기준별 점수·근거와 종합점수·등급(안)을 산출한다. 적절성·일관성·효과성·효율성·지속가능성 5기준(CTS 사업은 타당성 포함 6기준)을 전담 평가관에게 병렬 위임하고, 근거를 검증한 뒤 사람에게 초안으로 넘긴다. -when_to_use: 사용자가 사업 종료보고서·사업 자료를 주며 "이 사업 평가해줘", "DAC 기준으로 평가", "종합점수/등급 내줘", "사업평가 해줘"라고 할 때. 완성된 평가보고서의 품질을 심사하는 것(quality-review)이나 영향평가 방법론 검토(impact-review)와는 다르다. +description: ODA 사업을 OECD DAC/KOICA 기준으로 평가해 기준별 점수·근거와 종합점수·등급(안)을 산출한다. 사업 종료보고서나 사업 자료를 두고 "이 사업 평가해줘", "DAC 기준으로 평가", "종합점수/등급 내줘"라고 요청할 때 사용한다. 적절성·일관성·효과성·효율성·지속가능성 5기준(CTS는 타당성 포함)을 독립 평정하고 근거를 검증해 사람에게 초안으로 넘긴다. 평가보고서 품질심사나 영향평가 방법론 검토에는 사용하지 않는다. --- # KOICA 사업평가 (DAC 기준) @@ -49,9 +48,18 @@ when_to_use: 사용자가 사업 종료보고서·사업 자료를 주며 "이 | 타당성 | `cts-validity-evaluator` | CTS 사업만 | | 근거·점수 검증 | `quality-verifier` | — | +## 호스트 호환 + +- 먼저 `deveval-root`가 있으면 그 출력으로 플러그인 루트를 구한다. 명령이 없으면 호스트가 노출한 이 `SKILL.md`의 절대경로에 정확히 `realpath(dirname()/../..)`(또는 동등한 경로 정규화)을 적용한다. 호스트가 스킬 절대경로를 노출하지 않으면 현재 작업 폴더나 파일 검색 결과로 추측하지 말고, 루트를 확인할 수 없다고 알린 뒤 사용자에게 플러그인 루트를 요청한다. `reference/`, `templates/`, `agents/`가 실제로 존재하는지 확인하고, 사용자 작업 폴더를 플러그인 루트로 가정하지 않는다. +- Claude Code에서는 `agents/`에 등록된 이름의 평가관을 사용한다. Codex 등 이름 기반 평가관을 자동 등록하지 않는 호스트에서는 위임 전에 해당 `/agents/<역할>.md`를 끝까지 읽고, 그 지침을 서브에이전트 과업에 포함한다. +- 기준 평가관과 `quality-verifier`는 읽기 전용이다. Codex의 일반 서브에이전트로 대체할 때도 파일을 생성·수정하지 말라고 명시한다. +- 서브에이전트가 있으면 호스트의 동시 실행 한도 안에서 병렬 위임하고 나머지는 다음 묶음으로 실행한다. 서브에이전트가 없으면 같은 역할 파일을 적용해 기준별로 순차 처리하되, 다른 기준의 결론을 다음 평정에 노출하지 않는다. + ## 워크플로 -1. **기준 문서 경로 확보** — `deveval-root`를 실행해 플러그인 경로를 얻는다. 기준 원천은 `/reference/KOICA-평가지침-2024-다이제스트.md`(주 자산), 규정 근거는 `/reference/KOICA-사업평가규정-다이제스트.md`. **위임할 때 이 절대경로를 평가관에게 전달**한다(평가관은 사용자 작업 폴더에서 실행되므로 상대경로로는 못 찾는다). +긴 평가나 여러 사업을 함께 평가할 때는 `/templates/eval-plan-template.md`를 사용자 작업 폴더의 `.omo/eval-plan.md`로 복사해 작업판을 만들고, 각 단계가 끝날 때 `[x]`, 외부 자료 때문에 막히면 사유와 함께 `[~]`로 갱신한다. 기존 작업판을 발견하면 관련 블록을 보존하고 이어 쓴다. 작업공간이 읽기 전용이면 작업판을 만들지 말고 그 한계를 명시한다. + +1. **기준 문서 경로 확보** — 위 호스트 호환 절차로 ``를 구한다. 기준 원천은 `/reference/KOICA-평가지침-2024-다이제스트.md`(주 자산), 규정 근거는 `/reference/KOICA-사업평가규정-다이제스트.md`. **위임할 때 이 절대경로를 평가관에게 전달**한다(평가관은 사용자 작업 폴더에서 실행되므로 상대경로로는 못 찾는다). 2. **자료 확인 + 사업유형 판별** — 평가 대상을 읽고 범위를 파악한다. **CTS/기술혁신 사업인지** 확인(타당성 적용 여부). @@ -61,11 +69,11 @@ when_to_use: 사용자가 사업 종료보고서·사업 자료를 주며 "이 - 결과는 **게이트웨이 증거 블록**으로 정리하고, 블록 머리에 반드시 쓴다: *"아래는 oda-intelligence 게이트웨이 조회 결과다. 각 항목의 출처·상태를 그대로 인용하고, 상태가 정상이 아닌 항목을 0이나 '문제없음'으로 바꾸지 마라."* 항목마다 `[근거: 게이트웨이/<소스>, <상태>, <조회일>]`. - ⚠️ **게이트웨이 증거는 보조 맥락이다.** 평가 대상 사업 문서가 1차 근거이며, 게이트웨이 증거로 사업 문서의 공백을 "달성"으로 메우지 마라. -4. **기준 평가관 병렬 위임** — 표준 5기준을 **한 메시지에 여러 Task로 동시** 위임. CTS면 타당성 포함 6기준. 영향력이 관련되면 `dac-impact-evaluator`도 병렬로 돌리되 **20점 종합에 합산하지 말고 별도 보고**한다. +4. **기준 평가관 병렬 위임** — 표준 5기준을 호스트의 서브에이전트 기능으로 가능한 범위에서 동시 위임한다. CTS면 타당성 포함 6기준. 영향력이 관련되면 `dac-impact-evaluator`도 병렬로 돌리되 **20점 종합에 합산하지 말고 별도 보고**한다. - 위임 프롬프트에는 **담당 기준명 + 평가 대상 경로 + 기준 문서 절대경로**를 넣고, 3에서 만든 증거 블록이 있으면 **해당 기준과 관련된 부분만** 머리글 포함 그대로 덧붙인다. 그 외에는 넣지 않는다. - ⚠️ **다른 기준의 점수·결론·기대 등급을 언급하지 마라**(평가관 독립성 — 재위임 때도 동일). -5. **근거·점수 검증** — `quality-verifier`에게 위임해 근거를 원문과 대조하고 점수-근거 정합성을 점검한다. 원문에 없는 근거로 평정했으면 반려·정정. +5. **근거·점수 검증** — `quality-verifier`에게 위임해 근거를 원문과 대조하고 점수-근거 정합성을 점검한다. 위임 프롬프트에 **모든 기준별 평가 초안 전문(파일로 저장했다면 각 절대경로) + 평가 대상 원자료 절대경로 + `/reference/KOICA-평가지침-2024-다이제스트.md` 절대경로**를 명시적으로 넣는다. 특히 일반 Codex 서브에이전트가 형제 평가관의 출력이나 대화 맥락을 상속한다고 가정하지 않는다. 원문에 없는 근거로 평정했으면 반려·정정. 6. **종합점수 산정** — - 표준 5기준 합산 20점 → 위 A~F 표. CTS 6기준은 24점·평균 → 4단계. @@ -76,8 +84,8 @@ when_to_use: 사용자가 사업 종료보고서·사업 자료를 주며 "이 ## 다른 트랙과 혼동 금지 -- 완성된 **평가보고서의 품질**을 심사하는 건 `/deveval:quality-review`(24문항/100점/A~D). -- **영향평가(Impact Evaluation) 보고서**의 인과 방법론 심사는 `/deveval:impact-review`(5축/10질문, 등급 없음). -- 평가 결과로 **보고서를 쓰는** 건 `/deveval:write-report`. +- 완성된 **평가보고서의 품질**을 심사하는 건 `deveval:quality-review`(24문항/100점/A~D). +- **영향평가(Impact Evaluation) 보고서**의 인과 방법론 심사는 `deveval:impact-review`(5축/10질문, 등급 없음). +- 평가 결과로 **보고서를 쓰는** 건 `deveval:write-report`. > 학습·실험용 보조 도구다. 사실·결론과 가치판단·제언을 구분하고(규정 제5조 투명성), 평가 독립성(제19조)을 침해하지 않는다. diff --git a/skills/impact-review/SKILL.md b/skills/impact-review/SKILL.md index aab2800..1f02398 100644 --- a/skills/impact-review/SKILL.md +++ b/skills/impact-review/SKILL.md @@ -1,7 +1,6 @@ --- name: impact-review -description: 영향평가(Impact Evaluation) 보고서의 인과추론 방법론을 5축·10질문으로 심사해 적합/조건부 보완/부적합을 판정한다. PSM·DiD·RCT 등 인과식별·반사실 설계·타당도를 본다. 등급(A~F)은 없다. -when_to_use: 사용자가 영향평가 보고서를 주며 "방법론 검토해줘", "인과 설계가 타당한지 봐줘", "영향평가 검증해줘", "RIE로 적합한지"라고 할 때. DAC 6기준 사업평가와는 다른 평가 유형이다. +description: 영향평가(Impact Evaluation) 보고서의 인과추론 방법론을 5축·10질문으로 심사해 적합/조건부 보완/부적합을 판정한다. 영향평가 보고서를 두고 "방법론 검토해줘", "인과 설계가 타당한지 봐줘", "영향평가 검증해줘", "RIE로 적합한지"라고 요청할 때 사용한다. PSM·DiD·RCT 등 인과식별·반사실 설계·타당도를 보며 A~F 등급은 매기지 않는다. --- # 영향평가 보고서 검토 (방법론 심사) @@ -10,9 +9,15 @@ when_to_use: 사용자가 영향평가 보고서를 주며 "방법론 검토해 ⚠️ **6기준 사업평가팀을 쓰지 마라.** 영향평가엔 DAC 기준 등급 구조가 없어 대부분 '평가 불가'가 된다(틀이 안 맞음). +## 호스트 호환 + +- `deveval-root`가 있으면 그 출력으로 플러그인 루트를 구한다. 없으면 호스트가 노출한 이 `SKILL.md`의 절대경로에 정확히 `realpath(dirname()/../..)`(또는 동등한 경로 정규화)을 적용한다. 호스트가 스킬 절대경로를 노출하지 않으면 현재 작업 폴더나 파일 검색 결과로 추측하지 말고, 루트를 확인할 수 없다고 알린 뒤 사용자에게 플러그인 루트를 요청한다. `reference/`와 `agents/`가 실제로 존재하는지 확인한다. +- Claude Code에서는 등록된 `impact-evaluation-reviewer`를 사용한다. Codex 등 이름 기반 평가관을 자동 등록하지 않는 호스트에서는 `/agents/impact-evaluation-reviewer.md`를 끝까지 읽고 그 지침으로 서브에이전트를 위임한다. 서브에이전트가 없으면 같은 역할 지침을 직접 적용한다. +- 이 검토 역할은 읽기 전용이다. 일반 서브에이전트로 대체할 때도 파일을 생성·수정하지 말라고 명시한다. + ## 절차 -1. **기준 문서 경로 확보** — `deveval-root`로 플러그인 경로를 얻는다. 기준은 `/reference/KOICA-영향평가-가이드라인-다이제스트.md`(KIEP 2025). +1. **기준 문서 경로 확보** — 위 호스트 호환 절차로 ``를 구한다. 기준은 `/reference/KOICA-영향평가-가이드라인-다이제스트.md`(KIEP 2025). 2. **`impact-evaluation-reviewer`에게 위임** — 대상 보고서 경로 + 가이드라인 절대경로를 전달한다. 3. **판정 정리** — **적합 / 조건부 보완 / 부적합**(등급 아님) + 방법론·통계 의심 시 🚩 **기술검토 권고** 플래그. 최종 판정은 평가실·품질검토위(사람) 몫임을 명시한다. @@ -39,7 +44,7 @@ when_to_use: 사용자가 영향평가 보고서를 주며 "방법론 검토해 ## 사업 영향력 평정과 구분 - **이 스킬**: 영향평가 *보고서*의 방법론이 타당한가 → 적합/조건부/부적합 -- **`dac-impact-evaluator`**: *사업*의 영향력(Impact) 기준을 1~4점으로 평정(사후평가 관점, 20점 종합 미포함) — `/deveval:evaluate`에서 다룬다 +- **`dac-impact-evaluator`**: *사업*의 영향력(Impact) 기준을 1~4점으로 평정(사후평가 관점, 20점 종합 미포함) — `deveval:evaluate` 워크플로에서 다룬다 같은 사업이라도 두 역할은 다르다. 사용자가 "이 사업의 영향력은 몇 점?"을 물으면 후자, "이 영향평가가 제대로 설계됐나?"를 물으면 이 스킬이다. From 2f9701566c99c5492e24aa0616aceb2d353b3eb2 Mon Sep 17 00:00:00 2001 From: amnotyoung Date: Sun, 2 Aug 2026 12:05:07 +0900 Subject: [PATCH 2/2] =?UTF-8?q?test:=20=EC=8B=A4=EC=A0=9C=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=ED=8F=89=EA=B0=80=20334=EA=B1=B4=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=95=98=EA=B3=A0=20=EA=B2=B0=EC=A0=95?= =?UTF-8?q?=EC=A0=81=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EA=B3=A0=EC=A0=95=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 정확성을 파는 자산인데 회귀 테스트가 없던 공백을 메우고, 실제 KOICA 종료평가 PDF 334건 전수 스윕으로 수치 검사기를 양방향(미검출·오탐)에서 보정한다. 이 커밋의 픽스처는 그 스윕에서 관측된 실제 사고·오탐 유형이다. 수치 검사기 — 못 잡던 것 - 국문 역순("총 24점 만점 중 11.7")·영문("12.7 points out of 24") 종합점수 표기를 추출한다. 종전 검사기는 이 형식을 읽지 못해, 공개된 보고서 2건의 국문↔영문 요약 총점 불일치를 **둘 다 놓쳤다** — 캄보디아 자궁경부암 AI검진 CTS(11.7 vs 12.7, 이 프로젝트 설계 서사가 인용해 온 바로 그 사례)와 같은 시리즈 자매 보고서(9.3 vs 10.3). 이제 둘 다 검출한다 수치 검사기 — 잘못 잡던 것 (위반 플래그 71건 → 20건) - 척도 범례·임계값 문장("14점 이상이면 매우 성공적…"), 라벨 3종 이상 열거 - 품질검토 도장(`평가품질 등급: B`, A~D)·용역종합 등급을 사업 A~F에 혼입 - 만족도·설문 종합점수(다른 척도), PDM 달성률 `NN/100점`을 품질 총점에 혼입 - 점수가 아닌 N/M 표기 — `8/20개소`(개수)·`3/20-24`·`7/20(월)`(일정)· `20/100,000명`(비율)·`(35/24)`(남/여, 분자>분모) - PDF 줄바꿈으로 쪼개진 라벨(`부/분 성공적`)이 만드는 거짓 불일치 - 불일치를 **인용·보고하는** 줄("국문 11.7 vs 영문 12.7 — 상호 불일치")의 재플래그. 검증 보고서가 남의 불일치를 적으면 자기모순이 아니다 - 기준 평균 합산은 기준 수가 4~6개일 때만(구 4기준·2024 5기준·CTS 6기준) 잔여 20건은 다건 묶음 평가용역(사업별 상이 점수가 정상) 또는 진성 후보다. 종료 코드 계약 - '확인 불가'를 exit 3으로 분리한다. 종전에는 통과와 같은 0이라 스크립트 소비자가 거짓 초록불을 받았다 (0 통과 / 2 위반 / 3 확인 불가, 크래시는 기존대로 fail-open 0). 스킬 2종의 안내도 함께 고쳤다 완료 엔진 (hooks/boulder.sh) - 가드(정체 3회·시도 20회)가 걸리면 작업판 지문을 남기고 **일시정지**한다. 종전에는 상태 파일을 지우고 종료를 허용해, 평가를 중도에 접은 사용자가 작업판을 손대기 전까지 매 턴 다시 차단당했다. 작업판이 편집되면(재개 의사) 카운터를 리셋하고 다시 무장한다 테스트·CI - tests/: 검사기 회귀 20케이스(픽스처 7종) + 완료 엔진 동작 15케이스. 표준 라이브러리·bash만 쓴다(pytest 불필요, BSD/GNU sed 양쪽 동작) - scripts/check-manifest-sync.sh: 4개 매니페스트의 이름·버전과 CHANGELOG 항목 정합을 검사한다. mirror-sync가 문서 미러를 지키듯 매니페스트 정체성을 규율이 아니라 CI로 지킨다 - .github/workflows/checks.yml: 위 셋을 PR·push에서 실행 검증 기록 (docs/validation-log.md) - e2e-8 — 캄보디아 보고서 전문 3,934줄로 evaluate 트랙 풀 파이프라인을 실행했다. 6기준 병렬 평정 → quality-verifier가 표본 근거 약 60건을 원문과 대조해 **환각 0건**(정량 수치 전건 자릿수 일치)을 확인하면서 평가관의 실제 오류 4건을 반려(원보고서가 반박한 통계의 재사용, 특허 발명자 오귀속, 소수점 점수, 재무 실적 강화 서술) → report-composer 초안(종합은 단서부 잠정 범위로 보류) → 수치 검사기 정직 기권(exit 3) → narrative-verifier가 등급 라벨의 출처 과대 표기 등 4건 추가 지적. 원평가팀과 밴드 일치를 재현했고, 원보고서 자체 결함 3건을 재확인했다 - §1-1 — 334건 스윕 보정 기록과 그 한계(PDF 추출본·다건 묶음) 주: CHANGELOG·CLAUDE.md(+영문 미러)·validation-log·hooks/boulder.sh· skills/{write-report,quality-review}는 직전 커밋의 0.10.0 재구성 편집과 같은 파일을 공유해, 그 문서 편집 일부가 이 커밋에 함께 담겼다. Co-Authored-By: Claude Fable 5 --- .github/workflows/checks.yml | 28 ++++ .gitignore | 3 + CHANGELOG.md | 62 +++++++- CLAUDE.md | 35 +++-- docs/en/CLAUDE.md | 35 +++-- docs/validation-log.md | 71 +++++++-- hooks/boulder.sh | 45 ++++-- scripts/check-manifest-sync.sh | 78 ++++++++++ scripts/consistency_check.py | 187 ++++++++++++++++++----- skills/quality-review/SKILL.md | 17 ++- skills/write-report/SKILL.md | 24 +-- tests/fixtures/no-numbers.md | 7 + tests/fixtures/project-clean.md | 25 +++ tests/fixtures/project-grade-gap.md | 14 ++ tests/fixtures/project-ko-en-mismatch.md | 17 +++ tests/fixtures/project-legend-noise.md | 36 +++++ tests/fixtures/quality-clean.md | 24 +++ tests/fixtures/quality-total-mismatch.md | 17 +++ tests/test_boulder.sh | 100 ++++++++++++ tests/test_consistency_check.py | 169 ++++++++++++++++++++ 20 files changed, 893 insertions(+), 101 deletions(-) create mode 100644 .github/workflows/checks.yml create mode 100755 scripts/check-manifest-sync.sh create mode 100644 tests/fixtures/no-numbers.md create mode 100644 tests/fixtures/project-clean.md create mode 100644 tests/fixtures/project-grade-gap.md create mode 100644 tests/fixtures/project-ko-en-mismatch.md create mode 100644 tests/fixtures/project-legend-noise.md create mode 100644 tests/fixtures/quality-clean.md create mode 100644 tests/fixtures/quality-total-mismatch.md create mode 100755 tests/test_boulder.sh create mode 100644 tests/test_consistency_check.py diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..a40e934 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,28 @@ +name: checks + +# 결정적 컴포넌트의 회귀를 막는다 — 수치 일관성 검사기(consistency_check.py), +# 완료 엔진(hooks/boulder.sh), 이중 매니페스트 정체성(check-manifest-sync.sh). +# 픽스처는 실제 KOICA 종료평가 PDF 334건 스윕에서 관측된 사고·오탐 유형이다. + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: 수치 일관성 검사기 회귀 테스트 + run: python3 -m unittest discover -s tests -v + + - name: 완료 엔진(Stop hook) 동작 테스트 + run: bash tests/test_boulder.sh + + - name: 매니페스트 정체성 검사 + run: bash scripts/check-manifest-sync.sh diff --git a/.gitignore b/.gitignore index f29b4aa..5f9bc68 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ *.pdf *.PDF *.hwp + +# Python 부산물 +__pycache__/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec9d99..53f32d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,27 +8,79 @@ may still change). The project was developed iteratively as a series of "slices" in June 2026; each slice below is recorded as a 0.x milestone. -## [Unreleased] — Digital Public Goods readiness (2026-07) +## [Unreleased] ### Added -- **Packaged as a Claude Code plugin** — the repository *is* the plugin, so an +- **e2e-8 validation entry** (`docs/validation-log.md`) — the full evaluate-track + pipeline (6 parallel evaluators + quality-verifier + report-composer + numeric + checker + narrative-verifier, with optional gateway evidence) ran live on the + complete text of the real Cambodia CTS end-of-project report: ~60 sampled + evidence citations, zero hallucinations, 4 real evaluator errors caught and + corrected by the verification layer, band agreement with the human team + reproduced, and 3 defects of the published report itself re-confirmed. +- **Regression test suite + CI** (`tests/`, `.github/workflows/checks.yml`) — + the deterministic components finally have automated tests: 18 unittest cases + for `scripts/consistency_check.py` (fixtures reproduce failure/false-positive + patterns observed in real KOICA reports), 15 behavior tests for + `hooks/boulder.sh`, and `scripts/check-manifest-sync.sh` guarding name/version + parity across the 4 plugin manifests (Claude + Codex + both marketplaces). +- **Ko↔En composite-score extraction** in the numeric checker — Korean reversed + form (`총 24점 만점 중 11.7`) and English form (`12.7 points out of 24`). + Validated against a full sweep of **334 real KOICA end-of-project evaluation + PDFs**: the checker now catches **two genuine published Ko↔En summary + mismatches** (11.7 vs 12.7 — the very Cambodia CTS case this project's + design story is built on — and a sibling report's 9.3 vs 10.3). + +### Changed +- **Numeric checker false-positive suppression**, calibrated on the same + 334-report sweep (violation flags 71 → 22, all remaining flags are + multi-project bundles or true candidates): scale legends/threshold sentences, + quality-panel stamps (`평가품질 등급`, A–D scheme) and service-bundle grades + (`용역종합 등급`) vs project A–F, satisfaction-survey composites, count/date + ratios (`8/20개소`, `3/20-24`, `20/100,000명`, `(35/24)`), PDM achievement + rates masquerading as `NN/100` quality totals, and line-wrapped severed + labels (`부/분 성공적`). Criterion-mean sum check now requires a plausible + 4–6 criteria and supports `(a)`–`(f)`. +- **Checker exit codes**: "nothing to check" is now exit **3** (distinct from + pass 0 / violation 2) so scripted callers cannot mistake it for a pass; + crash/read failure stays fail-open 0. Skills updated accordingly. +- **Completion engine pause semantics** (`hooks/boulder.sh`) — when a guard + trips (3 stalls or 20 attempts) the hook now records the plan's fingerprint + and stops nagging while the plan is untouched; editing the plan (renewed + intent) re-arms the engine. Previously the counters reset after firing, so an + abandoned plan re-blocked every subsequent turn. + +## [0.10.0] — 2026-08-02 — Digital Public Goods readiness + +### Added +- **Packaged as installable Claude Code and Codex plugins** — the repository + *is* the plugin, so an evaluator installs it once and works **in their own folder** instead of inside this repo (evaluation reports are the evaluator's local work product; the repo is the tool). - `.claude-plugin/plugin.json` (name `deveval`) + `.claude-plugin/marketplace.json` for self-distribution: `/plugin marketplace add amnotyoung/dev-eval-agents` → `/plugin install deveval@deveval-agents`. - - **Workflows became skills** (`skills/`): `/deveval:evaluate`, - `/deveval:quality-review`, `/deveval:impact-review`, `/deveval:write-report`. + - `.codex-plugin/plugin.json` + `.agents/plugins/marketplace.json` for native + Codex discovery: `codex plugin marketplace add amnotyoung/dev-eval-agents + --ref main` → `codex plugin add deveval@deveval-agents`. Both plugin + manifests use version `0.10.0` and the same stable plugin identity. + - **Workflows became skills** (`skills/`): `deveval:evaluate`, + `deveval:quality-review`, `deveval:impact-review`, and + `deveval:write-report` (Codex invokes them with `$`; Claude Code with `/`). A plugin does not load `CLAUDE.md` as context (`claude plugin validate` warns about exactly this), so the evaluation procedure now lives in skills — loaded on demand rather than always-on. - `.claude/agents/` → `agents/` (12 agents, unchanged content), `.claude/settings.json` hooks → `hooks/hooks.json` using `${CLAUDE_PLUGIN_ROOT}`. - - `bin/` (on `PATH` while enabled): `deveval-root` resolves the plugin's absolute + - `bin/` (on Claude Code's `PATH` while enabled): `deveval-root` resolves the plugin's absolute path so skills can pass **absolute** reference paths to sub-agents — agents only have `Read/Grep/Glob` and cannot resolve `reference/…` from the user's folder; `deveval-consistency-check` wraps the numeric consistency checker. + - The same four skills now resolve their installed root without assuming + plugin `bin/` is on `PATH`, load Claude-format role files into generic Codex + subagents, and retain a sequential independence fallback when subagents are + unavailable. - `CLAUDE.md` rewritten as **repository/development** context only, with the evaluation workflow removed to prevent duplication drift with `skills/`. - **Open-source licensing**: MIT for software (`LICENSE`) and CC BY 4.0 for diff --git a/CLAUDE.md b/CLAUDE.md index 4b13713..256b7f1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,31 +2,35 @@ 이 파일은 **이 저장소에서 작업할 때**(플러그인을 개발·유지보수할 때) Claude Code가 읽는 지침이다. -> ⚠️ **평가 워크플로는 여기에 없다.** 이 저장소는 그 자체가 **Claude Code 플러그인**이고, 평가 절차는 `skills/`에 있다. 플러그인 사용자에게 CLAUDE.md는 로드되지 않는다(`claude plugin validate`가 이를 경고한다) — 그래서 워크플로를 스킬로 옮겼다. **평가 절차를 고치려면 `skills/`를 고쳐라. 이 파일에 워크플로를 다시 쓰지 마라**(중복은 곧 드리프트다). +> ⚠️ **평가 워크플로는 여기에 없다.** 이 저장소는 그 자체가 **Claude Code·Codex 공용 플러그인**이고, 평가 절차는 `skills/`에 있다. 설치형 플러그인 사용자에게 CLAUDE.md는 로드되지 않는다 — 그래서 워크플로를 스킬로 옮겼다. **평가 절차를 고치려면 `skills/`를 고쳐라. 이 파일에 워크플로를 다시 쓰지 마라**(중복은 곧 드리프트다). ## 이 저장소 = 플러그인 ``` .claude-plugin/plugin.json 매니페스트 (name: deveval) .claude-plugin/marketplace.json 자체 배포용 마켓플레이스 +.codex-plugin/plugin.json Codex 매니페스트 (같은 name/version) +.agents/plugins/marketplace.json Codex 저장소 마켓플레이스 skills/ 평가 워크플로 4종 ← 사용자의 실제 진입점 agents/ 전담 평가관·검증자 12명 hooks/hooks.json + boulder.sh 완료 엔진 (Stop hook) -bin/ 플러그인 활성화 시 PATH에 오르는 실행파일 +bin/ Claude가 PATH에 올리는 실행파일(Codex는 절대경로 폴백) reference/ KOICA 기준 다이제스트 5종 (공용 지식) templates/ · samples/ · scripts/ 템플릿·샘플·러너 ``` | 스킬 | 하는 일 | |------|---------| -| `/deveval:evaluate` | 사업평가 — 5~6기준 병렬 평정 → 종합점수·등급(안) | -| `/deveval:quality-review` | 평가보고서 품질심사 — 24문항/100점/A~D | -| `/deveval:impact-review` | 영향평가 방법론 검토 — 5축/10질문 | -| `/deveval:write-report` | 보고서 작성 — 작성→수치검사→서술검증→사람 | +| `deveval:evaluate` | 사업평가 — 5~6기준 병렬 평정 → 종합점수·등급(안) | +| `deveval:quality-review` | 평가보고서 품질심사 — 24문항/100점/A~D | +| `deveval:impact-review` | 영향평가 방법론 검토 — 5축/10질문 | +| `deveval:write-report` | 보고서 작성 — 작성→수치검사→서술검증→사람 | + +Claude Code는 `/deveval:`, Codex는 `$deveval:` 문법으로 호출한다. ## 개발 방법 -이 저장소는 플러그인이므로, 개발 중 동작을 보려면 **플러그인으로 로드**해야 한다: +Claude 쪽 개발 동작은 **플러그인으로 로드**해 확인한다: ```bash claude --plugin-dir . # 이 저장소를 플러그인으로 올려서 테스트 @@ -34,7 +38,15 @@ claude plugin validate . # 매니페스트·구조 검증 /reload-plugins # 세션 중 변경사항 반영 ``` -`.claude/agents/`나 `.claude/settings.json`으로 되돌리지 마라 — 플러그인 레이아웃(`agents/`, `hooks/hooks.json`)이 정본이다. +결정적 컴포넌트(수치 검사기·완료 엔진·매니페스트 정합)는 CI(`checks`)가 지키는 테스트가 있다 — 로컬에서도 돌려라: + +```bash +python3 -m unittest discover -s tests # 수치 검사기 회귀 (픽스처 = 실제 사고 유형) +bash tests/test_boulder.sh # 완료 엔진(Stop hook) 동작 +bash scripts/check-manifest-sync.sh # 4개 매니페스트 이름·버전 일치 +``` + +`.claude/agents/`나 `.claude/settings.json`으로 되돌리지 마라 — 공용 플러그인 레이아웃(`skills/`, `agents/`, `hooks/hooks.json`)이 정본이다. Codex 매니페스트는 plugin-creator 검증기로 별도 검사한다. **한국어 정본을 고치면 `docs/en/` 미러도 같은 PR에서 고쳐라.** `CLAUDE.md`·`AGENTS.md`·`agents/`·`reference/`가 대상이고, CI(`mirror-sync`)가 한쪽만 바뀐 PR을 막는다. 한쪽만 바꾸는 것이 의도된 PR에는 `mirror-sync-exempt` 라벨을 붙인다. @@ -47,8 +59,9 @@ bash scripts/check-mirror-sync.sh --audit # 감사 모드 — 저장소 전 플러그인 사용자는 **자기 작업 폴더**에서 이걸 쓴다. 저장소 안이 아니다. 따라서: -- **에이전트·스킬은 `reference/…` 상대경로로 파일을 못 찾는다.** 스킬이 `deveval-root`(bin)로 플러그인 절대경로를 얻어 **위임 프롬프트에 절대경로를 실어** 보낸다. 에이전트는 `Read/Grep/Glob`만 가져 스스로 경로를 알아낼 수 없다. -- **hooks**에서는 `${CLAUDE_PLUGIN_ROOT}`를 쓴다(플러그인 업데이트 시 경로가 바뀌므로 여기에 상태를 저장하지 마라). +- **에이전트·스킬은 `reference/…` 상대경로로 파일을 못 찾는다.** 스킬은 `deveval-root`가 있으면 사용하고, 없으면 로드된 `SKILL.md` 위치에서 루트를 계산한 뒤 **위임 프롬프트에 절대경로를 실어** 보낸다. Codex에서는 역할별 `agents/*.md`도 절대경로로 읽혀야 한다. +- **수치 검사기**는 bare command가 없으면 `/scripts/consistency_check.py`를 절대경로로 실행한다. +- **hooks**에서는 Codex도 호환 제공하는 `${CLAUDE_PLUGIN_ROOT}`를 쓴다(플러그인 업데이트 시 경로가 바뀌므로 여기에 상태를 저장하지 마라). - **평가자의 산출물**(`.omo/eval-plan.md`, `.omo/draft-report*.md`)은 **사용자 작업 폴더**에 만든다. 플러그인 디렉토리에 쓰지 마라. ## 원칙 (스킬·에이전트 전체에 공통) @@ -74,4 +87,4 @@ bash scripts/check-mirror-sync.sh --audit # 감사 모드 — 저장소 전 스킬 3종(evaluate·write-report·quality-review)과 Codex `AGENTS.md`에는 **선택적** 외부 증거 보강이 있다 — 같은 관리자의 `oda-intelligence` 플러그인(공개 read-only MCP 게이트웨이)이 설치된 세션에서만 동작하고, 없으면 건너뛴다(하드 의존 아님 — CONTRIBUTING의 model-agnostic 원칙과 DPG 지표 4를 지키는 조건). 연동 규칙·도구 매핑의 정본은 `docs/oda-intelligence-integration.md`. 연동 부분을 고치면 스킬·`AGENTS.md`·통합 문서를 함께 동기화한다. 에이전트 파일은 건드리지 않는 설계다 — 증거는 위임 프롬프트에 실리는 자기서술형 블록으로 전달된다. -> 학습·실험용 프로젝트다. 다른 하네스(Codex `AGENTS.md`, 오픈웨이트 `scripts/open_runner.py`)도 같은 `reference/` 지식을 공유한다 — 워크플로를 고칠 땐 그쪽 동기화도 함께 검토한다. +> 학습·실험용 프로젝트다. Codex 설치형 플러그인, Codex `AGENTS.md` 직접 실행 폴백, 오픈웨이트 `scripts/open_runner.py`가 같은 `reference/` 지식을 공유한다 — 워크플로를 고칠 땐 모두의 동기화를 검토한다. diff --git a/docs/en/CLAUDE.md b/docs/en/CLAUDE.md index 9843783..b7de0f0 100644 --- a/docs/en/CLAUDE.md +++ b/docs/en/CLAUDE.md @@ -4,31 +4,35 @@ This file is the instruction Claude Code reads **when working in this repository** (developing and maintaining the plugin). -> ⚠️ **The evaluation workflows are not here.** This repository is itself a **Claude Code plugin**, and the evaluation procedures live in `skills/`. CLAUDE.md is not loaded for plugin users (`claude plugin validate` warns about this) — which is why the workflows were moved into skills. **To change an evaluation procedure, edit `skills/`. Do not write workflows back into this file** (duplication is drift). +> ⚠️ **The evaluation workflows are not here.** This repository is itself a **shared Claude Code and Codex plugin**, and the evaluation procedures live in `skills/`. CLAUDE.md is not loaded for installed-plugin users, which is why the workflows were moved into skills. **To change an evaluation procedure, edit `skills/`. Do not write workflows back into this file** (duplication is drift). ## This repository = a plugin ``` .claude-plugin/plugin.json manifest (name: deveval) .claude-plugin/marketplace.json marketplace for self-distribution +.codex-plugin/plugin.json Codex manifest (same name/version) +.agents/plugins/marketplace.json Codex repository marketplace skills/ the 4 evaluation workflows ← the user's actual entry point agents/ 12 dedicated evaluators & verifiers hooks/hooks.json + boulder.sh completion engine (Stop hook) -bin/ executables placed on PATH while the plugin is enabled +bin/ executables Claude puts on PATH (Codex uses absolute fallback paths) reference/ 5 KOICA criteria digests (shared knowledge) templates/ · samples/ · scripts/ templates · samples · runners ``` | Skill | What it does | |-------|--------------| -| `/deveval:evaluate` | project evaluation — 5–6 criteria rated in parallel → composite score + draft grade | -| `/deveval:quality-review` | evaluation-report quality inspection — 24 items / 100 pts / A–D | -| `/deveval:impact-review` | impact-evaluation methodology review — 5 axes / 10 questions | -| `/deveval:write-report` | report drafting — write → numeric check → narrative verification → human | +| `deveval:evaluate` | project evaluation — 5–6 criteria rated in parallel → composite score + draft grade | +| `deveval:quality-review` | evaluation-report quality inspection — 24 items / 100 pts / A–D | +| `deveval:impact-review` | impact-evaluation methodology review — 5 axes / 10 questions | +| `deveval:write-report` | report drafting — write → numeric check → narrative verification → human | + +Invoke a skill as `/deveval:` in Claude Code or `$deveval:` in Codex. ## How to develop -Since this repository is a plugin, to see its behavior during development you must **load it as a plugin**: +To inspect Claude behavior during development, **load it as a plugin**: ```bash claude --plugin-dir . # load this repo as a plugin for testing @@ -36,7 +40,15 @@ claude plugin validate . # validate the manifest & structure /reload-plugins # pick up changes mid-session ``` -Do not fall back to `.claude/agents/` or `.claude/settings.json` — the plugin layout (`agents/`, `hooks/hooks.json`) is canonical. +The deterministic components (numeric checker, completion engine, manifest identity) have tests guarded by CI (`checks`) — run them locally too: + +```bash +python3 -m unittest discover -s tests # numeric-checker regressions (fixtures = real failure patterns) +bash tests/test_boulder.sh # completion engine (Stop hook) behavior +bash scripts/check-manifest-sync.sh # name/version parity across the 4 manifests +``` + +Do not fall back to `.claude/agents/` or `.claude/settings.json` — the shared plugin layout (`skills/`, `agents/`, `hooks/hooks.json`) is canonical. Validate the Codex manifest separately with plugin-creator's validator. **When you change a Korean canonical file, update its `docs/en/` mirror in the same PR.** This covers `CLAUDE.md`, `AGENTS.md`, `agents/`, and `reference/`; CI (`mirror-sync`) blocks a PR that changes only one side. Label a deliberately one-sided PR `mirror-sync-exempt`. @@ -49,8 +61,9 @@ bash scripts/check-mirror-sync.sh --audit # audit mode — mirror freshness a Plugin users run this from **their own working folder** — not from inside the repo. Therefore: -- **Agents and skills cannot find files via relative `reference/…` paths.** A skill obtains the plugin's absolute path via `deveval-root` (bin) and **embeds absolute paths in the delegation prompt**. Agents only have `Read/Grep/Glob` and cannot discover the path on their own. -- **Hooks** use `${CLAUDE_PLUGIN_ROOT}` (the path changes on plugin updates, so do not store state there). +- **Agents and skills cannot find files via relative `reference/…` paths.** A skill uses `deveval-root` when available or derives the root from the loaded `SKILL.md` location, then **embeds absolute paths in the delegation prompt**. On Codex, role-specific `agents/*.md` files must also be loaded by absolute path. +- **The numeric checker** is invoked as `/scripts/consistency_check.py` when no bare command is available. +- **Hooks** use `${CLAUDE_PLUGIN_ROOT}`, which Codex also provides as a compatibility variable (the path changes on plugin updates, so do not store state there). - **Evaluator outputs** (`.omo/eval-plan.md`, `.omo/draft-report*.md`) are created in the **user's working folder**. Do not write into the plugin directory. ## Principles (shared across all skills & agents) @@ -76,4 +89,4 @@ Clearly distinguish facts and conclusions from value judgments and recommendatio Three skills (evaluate · write-report · quality-review) and the Codex `AGENTS.md` carry an **optional** external-evidence augmentation — it operates only in sessions where the same maintainer's `oda-intelligence` plugin (a public read-only MCP gateway) is installed, and is skipped otherwise (no hard dependency — the condition that keeps CONTRIBUTING's model-agnostic principle and DPG indicator 4 intact). The canonical source for the integration rules and tool mapping is `docs/oda-intelligence-integration.md`. When you change the integration, sync the skills, `AGENTS.md`, and the integration doc together. By design the agent files are untouched — evidence travels as a self-describing block embedded in the delegation prompt. -> A learning/experimentation project. The other harnesses (Codex `AGENTS.md`, open-weight `scripts/open_runner.py`) share the same `reference/` knowledge — when you change a workflow, review synchronization on that side as well. +> A learning/experimentation project. The installed Codex plugin, the Codex `AGENTS.md` direct-run fallback, and the open-weight `scripts/open_runner.py` share the same `reference/` knowledge — review synchronization across all of them when changing a workflow. diff --git a/docs/validation-log.md b/docs/validation-log.md index f038831..a1cdf4a 100644 --- a/docs/validation-log.md +++ b/docs/validation-log.md @@ -2,7 +2,7 @@ > DevEval Agents가 ① **실제로 작동하는가**(실물 e2e) ② **실제 KOICA 평가 결과와 부합하는가**(대조)의 기록. > -> ⚠️ **학습·실험 프로젝트의 자체 검증**이며 공식 검증이 아니다. 표본 소수(4건), 한계 존재(§4). 최종 갱신: 2026-07-03 (영향평가 트랙 실물 **e2e-6·e2e-7** 추가 — 실제 KOICA 영향평가 보고서 대상, 신규 `dac-impact-evaluator` 첫 실물 검증). +> ⚠️ **학습·실험 프로젝트의 자체 검증**이며 공식 검증이 아니다. 표본 소수(5건), 한계 존재(§4). 최종 갱신: 2026-08-02 (**e2e-8** 추가 — 실제 KOICA 종료평가 보고서 전문 대상 evaluate 트랙 풀 파이프라인(6평가관+검증자+작성자+수치검사+서술검증) 실물 실행; §1-1 수치 검사기 **실제 보고서 334건 전수 보정** — 공개 보고서 2건의 실제 국·영문 총점 불일치 검출). --- @@ -11,14 +11,15 @@ > This log records two things: **(1) does the system actually run** (live > end-to-end tests) and **(2) does it agree with real KOICA evaluations** > (comparison). ⚠️ This is **self-validation of a learning/research project, not -> an official validation** — small sample (4 comparisons), with limitations in -> §4. Last updated 2026-07-03 (added the impact-track live runs e2e-6/e2e-7 on a real -KOICA impact-evaluation report). The detailed +> an official validation** — small sample (5 comparisons), with limitations in +> §4. Last updated 2026-08-02 (added e2e-8 — the full evaluate-track pipeline on a +complete real end-of-project report — and §1-1, the numeric-checker calibration +sweep over 334 real report PDFs). The detailed > tables below are kept in Korean (the working language of the KOICA source > material); this section is a full English synopsis for reviewers. -**1. Live end-to-end runs (does it actually work).** Seven headless runs — not -simulations: the harness loads the real `.claude/agents/*.md` (or `AGENTS.md`) +**1. Live end-to-end runs (does it actually work).** Eight live runs — not +simulations: the harness loads the real `agents/*.md` (or `AGENTS.md`) and the transcript is checked for the actual sub-agent calls. - **e2e-1** — bundled `samples/` report on Claude Code: five `dac-*-evaluator` @@ -56,6 +57,38 @@ and the transcript is checked for the actual sub-agent calls. composite**, regional/equity/unintended effects marked *"cannot evaluate"*, causal language held to contribution, human gate. Confirms the new agent's **role separation** from `impact-evaluation-reviewer`. 4 turns, ~4 min. +- **e2e-8** (2026-08-02) — the **full evaluate-track pipeline on the complete text + of a real KOICA end-of-project evaluation** (the Cambodia cervical-cancer CTS + report, 3,934 extracted lines), using the skill's documented fallback for hosts + without name-registered agents (role definitions + absolute paths carried in the + delegation prompts) and the optional `oda-intelligence` gateway (status tools + first; evidence passed only to the coherence evaluator). Six criterion + evaluators in parallel → `quality-verifier` cross-checked ~60 sampled evidence + citations against the source: **zero hallucinations** (every quantitative claim + matched to the digit) while **catching and returning 4 real evaluator errors** + (reuse of a statistic the original report had refuted, a patent-inventor + misattribution, fractional scores, overstated finance wording) → + `report-composer` produced a Ko/En-consistent summary draft with the composite + **withheld as a provisional range** (10–12/20, i.e. D or E; CTS 12–14/24, + "partially successful") → the numeric checker **honestly abstained** (exit 3 — + only provisional ranges present) → `narrative-verifier` semantic pass. Band + agreement with the human team (11.7/24 partially successful) was reproduced + from the full text, and the run re-confirmed **three defects in the published + report itself** (the 11.7 vs 12.7 Ko/En total mismatch, patent + granted-vs-filed wording conflicts, and a sustainability summary/body + contradiction). + +**1-1. Numeric-checker calibration on 334 real report PDFs (2026-08-02).** The +consistency checker was swept over **334 real KOICA end-of-project evaluation +PDFs**: it now catches **two genuine published Ko↔En total mismatches** (11.7 vs +12.7 — the very case this project's design story cites — and a sibling report's +9.3 vs 10.3; both were previously missed because the Korean reversed form and +English form were not extracted), while false-positive flags fell **71 → 20** +(scale legends, quality-panel A–D stamps vs project A–F, satisfaction-survey +composites, count/date ratios, PDM achievement rates, line-wrapped labels, +mismatch-quoting lines). "Nothing to check" is now a distinct exit code 3 (not a +false green), locked in by a 20-case regression suite, 15 completion-engine +behavior tests, a manifest-parity guard, and a new `checks` CI workflow. → The Claude Code workflow was reproduced **3×** (2 project evaluations + 1 quality inspection); e2e-4 and e2e-5 show the **same shared knowledge runs on @@ -64,7 +97,7 @@ Codex and on a fully open-weight stack** (multi-harness). e2e-6/e2e-7 add the reviewer and the new Impact-criterion evaluator each fired and routed correctly, handling the same project in two distinct roles. -**2. Comparison against real KOICA reports (4 cases).** +**2. Comparison against real KOICA reports (5 cases).** | Project | Type | Human team | DevEval Agents | Agreement | |---|---|---|---|---| @@ -106,7 +139,7 @@ production deployment**. ## 1. 실물 e2e — 실제 에이전트 작동 검증 -시뮬레이션(메인이 역할을 흉내냄)이 아니라, headless `claude -p`로 `DevEval Agents`를 로드해 **실제 `.claude/agents/*.md`가 호출되는지** 검증했다. +시뮬레이션(메인이 역할을 흉내냄)이 아니라, headless `claude -p`로 `DevEval Agents`를 로드해 **실제 `agents/*.md`가 호출되는지** 검증했다. ```bash # 검증 방식 (재현 가능) @@ -126,16 +159,30 @@ claude -p "<입력> 이 사업을 DAC 5대 기준으로 평가해줘" \ | **e2e-5** | **오픈웨이트 하네스**: `scripts/open_runner.py`로 사업평가(samples) | 로컬 Ollama+**Qwen2.5-14B**(Apache-2.0 개방 가중치, **프로프라이어터리 API 전무**)로 실행 / `AGENTS.md`+`reference/` 주입 / 5기준 1~4점+근거, 종합 13→**D(안)**, **사람 게이트·한계 명시 준수** ✅ / 단 '평가 불가' 판정은 상용 하네스보다 덜 보수적(캘리브레이션 차) — **이식성 실증, 품질 동치 아님** (`docs/open-model-demo-output.md`, prompt 7,631tok) | | **e2e-6** | **실제 KOICA 영향평가 보고서**(응에안성 산업기술학교, 158p) 방법론 검토 요청 | `impact-evaluation-reviewer` 호출 ✅ (스트림 36이벤트, **다른 서브에이전트 0 → 6기준팀 아닌 영향평가 검토관으로 정확 라우팅**) / 5축·10질문 → **부적합(RIE)**·투명성 상·🚩기술검토 권고 / 표43 처치더미 부재·각주9 가정·표집 내부모순까지 포착, 도표 이미지 판독불가는 `[INFO 확인 필요]`로 플래그 / 5턴·~5.4분·$1.49 (PDF→텍스트 추출본 입력) | | **e2e-7** | 같은 보고서로 **사업 영향력(Impact) 기준 평정** 요청 | **신규 `dac-impact-evaluator` 호출 ✅** (스트림 48이벤트, 다른 서브에이전트 0) — 신규 에이전트 **첫 실물** / 영향력 **3/4**(양호·기여 수준), **종합 미포함** 명시, 지역·형평성·부작용 "평가 불가", 인과→기여로 절제, 사람 게이트 / 4턴·~4분·$1.32. **`impact-evaluation-reviewer`(보고서 심사)와 역할 분리 실증** | +| **e2e-8** | **실제 KOICA 종료평가 보고서 전문**(캄보디아 자궁경부암 AI검진 CTS, 추출 3,934줄)으로 **evaluate 트랙 풀 파이프라인** — 스킬의 이름 미등록 호스트 폴백 절차(역할 정의 전문+절대경로를 위임 프롬프트에 탑재) 사용, `oda-intelligence` 게이트웨이 증거 보강(상태 도구 먼저·일관성 평가관에게만 전달) 포함 | 6기준 평가관 병렬(적절2·일관2·효과 정수미확정[산출3·형평2]·효율3·지속 정수미확정[재정1·제도2]·타당성2/재무 평가불가) → `quality-verifier`가 **표본 근거 ~60건 원문 대조: 환각 0건**(정량 수치 전건 자릿수 일치), **실수 4건 반려·정정**(원보고서가 반박한 통계의 긍정 근거 재사용, 특허 발명자 오귀속, 소수점 점수, 재무 실적 강화 서술) → `report-composer` 요약 초안(국·영문 수치 일치, 종합은 **단서부 잠정 범위**로 보류: 5기준 10~12/20 E~D, CTS 6기준 12~14/24 "부분 성공적") → 수치검사기 **정직 기권(exit 3 — 잠정 범위뿐이라 단정 수치 없음)** → `narrative-verifier` 의미 검증. **원평가팀(11.7/24 부분 성공적)과 밴드 일치**, 재평정 과정에서 **원보고서 자체 결함 3건 재확인**(국·영문 총점 불일치 11.7 vs 12.7, 특허 '취득 3' vs '취득 1·출원 2' 표기 상충, 지속가능성 요약 "평가 불가" vs 본문 "(3/4점)" 모순) | → **실제 Claude Code가 CLAUDE.md 워크플로대로 작동**함을 **3회 재현**(사업평가 2회 + 품질심사 1회) 확인. "시뮬 ≠ 실물" 의심 해소. e2e-3은 슬라이스 7(가이드라인 v2) 룰브릭 적용도 입증. **e2e-4는 같은 공용 지식(`reference/`)이 Codex 하네스에서도 작동**함을 입증 — Claude Code(`.claude/`)와 Codex(`AGENTS.md`)가 *한 지식·같은 원칙*으로 평가(멀티 하네스). **e2e-6·e2e-7**은 **영향평가 트랙**(보고서 방법론 심사 `impact-evaluation-reviewer` + 사업 영향력 평정 신규 `dac-impact-evaluator`)이 **실제 KOICA 영향평가 보고서**(응에안성 산업기술학교)에서 **실물 발화·정확 라우팅**(각 런 다른 서브에이전트 0)함을 입증 — 신규 `dac-impact-evaluator`의 첫 실물 검증 포함. 두 런은 같은 사업을 *다른 역할*(보고서 심사 vs 사업 영향력 평정)로 처리해 **역할 분리**도 실증. --- -## 2. 실제 KOICA 보고서 대조 (4건) +## 1-1. 수치 검사기 실물 보정 — 실제 종료평가 PDF 334건 전수 스윕 (2026-08-02) + +`scripts/consistency_check.py`를 실제 KOICA 종료평가 PDF **334건**(pdftotext 추출)에 전수 실행해 보정했다: + +- **실제 사고 검출**: 공개된 종료평가 보고서 2건에서 **국문↔영문 요약 총점 불일치**를 검출 — 캄보디아 자궁경부암 AI검진(국문 "총 24점 만점 중 11.7" vs 영문 "12.7 points out of 24" — 이 프로젝트 설계 서사의 그 사례)과 같은 CTS 시리즈 1건(9.3 vs 10.3). 보정 전 검사기는 이 형식(국문 역순·영문)을 추출하지 못해 **둘 다 놓쳤다** — 역순·영문 추출기를 추가해 검출. +- **오탐 보정**: 위반 플래그 71건(21%) → **20건**(6%). 제거된 오탐 유형 — 척도 범례·임계값 문장, 품질검토 도장(A~D)·용역종합 등급의 사업 A~F 혼입, 만족도 설문 종합점수, 개수·일정·비율 표기(`8/20개소`, `3/20-24`, `20/100,000명`, 남/여 `(35/24)`), PDM 달성률의 `/100` 총점 혼입, 줄바꿈으로 쪼개진 라벨(`부/분 성공적`), 불일치 '인용' 줄의 재플래그. 잔여 20건은 다건 묶음(평가용역, 사업별 상이 점수가 정상) 또는 진성 후보. +- **계약 강화**: "확인 불가"를 exit 3으로 분리(통과 0·위반 2와 구분 — 스크립트 소비자의 거짓 초록불 방지), 회귀 테스트 20케이스(`tests/`)와 CI(`checks`)로 고정. 완료 엔진(boulder.sh) 동작 테스트 15케이스, 매니페스트 정합 가드(`check-manifest-sync.sh`)도 함께 추가. + +> 이 스윕은 검사기 계약(마크다운 단일 사업 문서) 밖의 스트레스 테스트를 겸했다 — PDF 추출 텍스트·다건 묶음에서의 한계는 docstring과 스킬에 명시했다. + +--- + +## 2. 실제 KOICA 보고서 대조 (5건) | 사업 | 유형 | 평가팀(인간) | DevEval Agents | 부합도 | |---|---|---|---|---| | 캄보디아 자궁경부암 검진 SW | CTS (6기준) | 11.7/24 **부분 성공적** | 13/24 **부분 성공적** | ✅ **등급 일치** (1.3pt 차, 독립 도출) | +| 캄보디아 자궁경부암 검진 SW — **보고서 전문 재실행** (*e2e-8*, 2026-08-02) | CTS (6기준) | 11.7/24(국문)·12.7/24(영문, **원본 상호 불일치**) **부분 성공적** | 잠정 범위 **12~14/24** (효과성·지속가능성 정수는 사람 확정 대기) **"부분 성공적"(안)** | ✅ **밴드 일치 재현** (추출 사실이 아닌 **전문 3,934줄** 입력으로 재확인; 검증자 표본 근거 60건 환각 0) | | 미얀마 태양광 | 인프라 종료 (2018·4기준) | 11.33/16 성공적 | ~12/20 D(부분 성공적) | ◐ **기준별 방향 일치**(적절3·효과3·효율2 정확), 종합은 한 단계 차 | | 파키스탄 하수처리 | 인프라 종료 (2017·정성) | 등급 없음 ("대부분 충족") | D (13.5/20) | ◐ 약점(지속가능성) **방향 일치** | | 베트남 응에안성 산업기술학교 (직업훈련) | **영향평가**(사후) | 종료평가 **93점**(2009)·"성공적"(2011) → 영향평가 **긍정적 영향**(2017) | 검토관: 보고서 **부적합(RIE)**·투명성 상 / 영향력 평가관: **3/4**(양호·기여 수준, 종합 미포함) — *e2e-6·e2e-7 실물* | ◐ **방향 일치**(개인성과 긍정), 단 AI가 **더 보수적** — 인간 'favorable' vs AI 인과→기여·지역/형평성 "평가 불가" | @@ -160,12 +207,12 @@ claude -p "<입력> 이 사업을 DAC 5대 기준으로 평가해줘" \ - 지속가능성·효율성에서 평가팀보다 **보수적**(재정·기술 취약을 무겁게, 근거 없으면 점수 안 줌). 좋은 엄격함일 수도, 과한 보수성일 수도 — **캘리브레이션 과제**. **한계** -- **표본 4건** — 통계적 신뢰도엔 부족. -- **사실 추출(PART A)을 사람/보조가 수행** — 추출이 편향되면 전체가 흔들림 (미해결 과제). +- **표본 5건** — 통계적 신뢰도엔 부족. +- **사실 추출(PART A)을 사람/보조가 수행** — 추출이 편향되면 전체가 흔들림. e2e-8은 보고서 **전문**을 직접 입력해 이 한계를 부분 완화했으나, 이 경우 **원평가팀의 자체 평정이 입력에 포함**되므로 앵커링 위험이 생긴다(위임 프롬프트에 "원점수를 베끼지 말고 근거로부터 독립 평정 후 대조를 명시하라"로 완화 — 재현성 확인이지 완전 독립 재평가는 아님). - **평가팀 등급 체계 차이**(2018 4기준 16점 vs 2024 5기준 20점) — 종합 직접 비교엔 보정 필요. - **전문가 1:1 교차검증**(동료 평가담당관이 직접 채점해 대조)은 미실시. - **큰 PDF 직접 처리 미검증** — 109p 보고서는 추출본으로 e2e (토큰 부담). --- -> **결론**: *"작동한다"*는 입증됨(**실물 e2e 7회** — 종료평가·품질심사·멀티하네스 + 영향평가 방법론 검토·사업 영향력 평정, 각 회차 서브에이전트 실제 호출·정확 라우팅 확인). *"평가팀과 방향 부합"*은 4건에서 관찰됨(명확 등급 2건 기준별 일치 + 베트남 영향평가는 방향 일치·AI 더 보수적). 단 **신뢰도는 표본 확대·전문가 교차검증으로 더 쌓아야** 한다. 현재 위치 = *실전 투입 제품 이전의, 검증된 프로토타입.* +> **결론**: *"작동한다"*는 입증됨(**실물 e2e 8회** — 종료평가·품질심사·멀티하네스 + 영향평가 방법론 검토·사업 영향력 평정 + 보고서 전문 풀 파이프라인, 각 회차 서브에이전트 실제 호출·정확 라우팅 확인; e2e-8은 생성-검증 분리가 실제 오류 4건을 잡는 것까지 실증). *"평가팀과 방향 부합"*은 5건에서 관찰됨(명확 등급 2건 기준별 일치 + 캄보디아 전문 재실행 밴드 재현 + 베트남 영향평가는 방향 일치·AI 더 보수적). 수치 검사기는 실제 보고서 334건 전수로 보정되어 공개 보고서의 실제 국·영문 불일치 2건을 잡는다(§1-1). 단 **신뢰도는 표본 확대·전문가 교차검증으로 더 쌓아야** 한다. 현재 위치 = *실전 투입 제품 이전의, 검증된 프로토타입.* diff --git a/hooks/boulder.sh b/hooks/boulder.sh index 902956e..105654c 100755 --- a/hooks/boulder.sh +++ b/hooks/boulder.sh @@ -4,7 +4,11 @@ # Claude Code Stop hook. Claude가 응답을 끝내려 할 때 발화한다. # 평가 작업판(.omo/eval-plan.md)에 미완료 체크박스가 남아 있으면 # exit 2 + stderr 로 "계속하라"는 메시지를 Claude에 전달해 멈추지 못하게 한다. -# 무한루프는 (1) 정체 감지 (2) 절대 시도 상한 으로 막는다. +# +# 무한루프 가드: (1) 정체 3회 (2) 시도 20회 를 넘으면 **일시정지**한다 — +# 작업판의 지문(cksum)을 상태 파일에 남기고 종료를 허용하며, 작업판이 그대로인 +# 동안은 매 턴 다시 조르지 않는다. 작업판이 편집되면(체크·항목 추가 = 재개 의사) +# 카운터를 리셋하고 다시 무장한다. # # 완료 신호 = 모든 체크박스가 [x] 또는 [~](막힘). 미완료 = [ ]. @@ -15,6 +19,10 @@ STATE="$PROJECT/.omo/.boulder-state" # 작업판이 없으면 평가 모드가 아님 → 정상 종료 허용 [ -f "$PLAN" ] || exit 0 +# 읽기전용 작업공간에서는 정체 횟수를 저장할 수 없어 무한 재개될 수 있다. +# 상태 디렉토리에 쓸 수 없으면 안전하게 종료를 허용한다. +[ -w "$(dirname "$STATE")" ] || exit 0 + # 미완료 체크박스 개수 ( - [ ] ). [x](완료)·[~](막힘)는 제외된다. INCOMPLETE=$(grep -cE '^[[:space:]]*- \[ \]' "$PLAN" 2>/dev/null || true) INCOMPLETE=${INCOMPLETE:-0} @@ -25,19 +33,28 @@ if [ "$INCOMPLETE" -eq 0 ]; then exit 0 fi -# --- 무한루프 가드: 상태 파일 = "이전미완료 정체횟수 총시도" --- +# 작업판 지문 — 일시정지 후 "편집됐는가(재개 의사)"를 판별한다 +FP=$(cksum < "$PLAN" 2>/dev/null || echo "0 0") + +# --- 상태 파일: "이전미완료 정체횟수 총시도" 또는 "paused " --- +PREV=-1; STALL=0; TOTAL=0 if [ -f "$STATE" ]; then - read -r PREV STALL TOTAL < "$STATE" -else - PREV=-1; STALL=0; TOTAL=0 + read -r F1 F2 F3 < "$STATE" + if [ "$F1" = "paused" ]; then + if [ "$F2 $F3" = "$FP" ]; then + exit 0 # 방치된 작업판 — 편집 전까지 다시 조르지 않는다 + fi + # 작업판이 바뀜 = 재개 의사 → 카운터 리셋 후 재무장 + elif printf '%s' "$F1" | grep -qE '^-?[0-9]+$'; then + PREV=$F1; STALL=${F2:-0}; TOTAL=${F3:-0} + fi fi TOTAL=$((TOTAL + 1)) -# (가드 1) 절대 시도 상한: 20회 초과 → 포기하고 종료 허용 -if [ "$TOTAL" -gt 20 ]; then - rm -f "$STATE" - exit 0 -fi +pause() { printf 'paused %s\n' "$FP" > "$STATE" 2>/dev/null; exit 0; } + +# (가드 1) 시도 상한: 20회 초과 → 일시정지 (작업판 편집 시 재무장) +[ "$TOTAL" -gt 20 ] && pause # 진전 체크: 미완료가 줄었으면 정체 리셋, 아니면 정체++ if [ "$PREV" -eq -1 ] || [ "$INCOMPLETE" -lt "$PREV" ]; then @@ -45,11 +62,11 @@ if [ "$PREV" -eq -1 ] || [ "$INCOMPLETE" -lt "$PREV" ]; then else STALL=$((STALL + 1)) fi -echo "$INCOMPLETE $STALL $TOTAL" > "$STATE" -# (가드 2) 정체 3회(진전 없이 같은 자리) → 종료 허용 (사람 개입 유도) -if [ "$STALL" -ge 3 ]; then - rm -f "$STATE" +# (가드 2) 정체 3회(진전 없이 같은 자리) → 일시정지 (사람 개입 유도) +[ "$STALL" -ge 3 ] && pause + +if ! printf '%s %s %s\n' "$INCOMPLETE" "$STALL" "$TOTAL" > "$STATE"; then exit 0 fi diff --git a/scripts/check-manifest-sync.sh b/scripts/check-manifest-sync.sh new file mode 100755 index 0000000..f8a39b8 --- /dev/null +++ b/scripts/check-manifest-sync.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# +# Claude·Codex 이중 매니페스트의 정체성(이름·버전)이 어긋나지 않았는지 검사한다. +# +# scripts/check-manifest-sync.sh +# +# 왜 필요한가: 이 저장소는 같은 플러그인을 4개 매니페스트(.claude-plugin/plugin.json, +# .codex-plugin/plugin.json, 두 마켓플레이스)로 배포한다. 버전·이름 동기화가 규율에만 +# 의존하면 언젠가 드리프트한다 — mirror-sync가 문서 미러를 지키듯 이 검사가 +# 매니페스트 정체성을 지킨다. CHANGELOG에 현재 버전 항목이 있는지도 본다. + +set -uo pipefail +cd "$(git rev-parse --show-toplevel)" || exit 1 + +FAILURES=0 + +annotate() { + local level=$1 message=$2 + if [ -n "${GITHUB_ACTIONS:-}" ]; then + printf '::%s::%s\n' "$level" "$message" + else + printf ' [%s] %s\n' "$level" "$message" + fi +} + +jget() { # jget + python3 - "$1" "$2" <<'EOF' +import json, sys +obj = json.load(open(sys.argv[1], encoding="utf-8")) +print(eval(sys.argv[2], {"obj": obj})) +EOF +} + +echo "매니페스트 정체성 검사 — 이름·버전이 4개 매니페스트에서 일치하는가" +echo + +CL_NAME=$(jget .claude-plugin/plugin.json 'obj["name"]') || exit 1 +CL_VER=$(jget .claude-plugin/plugin.json 'obj["version"]') || exit 1 +CX_NAME=$(jget .codex-plugin/plugin.json 'obj["name"]') || exit 1 +CX_VER=$(jget .codex-plugin/plugin.json 'obj["version"]') || exit 1 + +echo " .claude-plugin/plugin.json : $CL_NAME $CL_VER" +echo " .codex-plugin/plugin.json : $CX_NAME $CX_VER" + +if [ "$CL_NAME" != "$CX_NAME" ]; then + annotate error "플러그인 이름 불일치: Claude '$CL_NAME' vs Codex '$CX_NAME'" + FAILURES=$((FAILURES + 1)) +fi +if [ "$CL_VER" != "$CX_VER" ]; then + annotate error "플러그인 버전 불일치: Claude '$CL_VER' vs Codex '$CX_VER'" + FAILURES=$((FAILURES + 1)) +fi + +MP_CL=$(jget .claude-plugin/marketplace.json 'obj["plugins"][0]["name"]') || exit 1 +if [ "$MP_CL" != "$CL_NAME" ]; then + annotate error "Claude 마켓플레이스 플러그인 이름 불일치: '$MP_CL' (매니페스트는 '$CL_NAME')" + FAILURES=$((FAILURES + 1)) +fi +if [ -f .agents/plugins/marketplace.json ]; then + MP_CX=$(jget .agents/plugins/marketplace.json 'obj["plugins"][0]["name"]') || exit 1 + if [ "$MP_CX" != "$CX_NAME" ]; then + annotate error "Codex 마켓플레이스 플러그인 이름 불일치: '$MP_CX' (매니페스트는 '$CX_NAME')" + FAILURES=$((FAILURES + 1)) + fi +fi + +if ! grep -q "^## \[$CL_VER\]" CHANGELOG.md; then + annotate error "CHANGELOG.md에 현재 버전 [$CL_VER] 항목이 없다" + FAILURES=$((FAILURES + 1)) +fi + +echo +if [ "$FAILURES" -eq 0 ]; then + echo "매니페스트 정체성 일치." + exit 0 +fi +echo "불일치 ${FAILURES}건 — 4개 매니페스트와 CHANGELOG의 이름·버전을 함께 갱신하라." +exit 1 diff --git a/scripts/consistency_check.py b/scripts/consistency_check.py index 0f15fc5..0e5e33b 100755 --- a/scripts/consistency_check.py +++ b/scripts/consistency_check.py @@ -8,20 +8,34 @@ 지원 형식(하네스 자체 초안 + 실제 KOICA 보고서 양쪽): [사업평가] - ① 종합점수 전건 일치 — `NN/16`·`/20`·`/24` 또는 표 안 맨숫자 `종합 점수 … 12.34` + ① 종합점수 전건 일치 — `NN/16`·`/20`·`/24`, 표 안 맨숫자 `종합 점수 … 12.34`, + 국문 역순 `24점 만점 중 11.7`, 영문 `12.7 (points) out of 24` + (국문↔영문 요약 불일치 — 캄보디아 종료평가의 11.7 vs 12.7 — 를 잡는 핵심 형식) ② a+b+c+d 산술 — `평균 점수(a)~(d)`의 합이 종합점수와 같은가 (실제 보고서 최빈 사고) ③ 등급 전건 일치 — A~F 문자 또는 라벨(매우 성공적/성공적/부분 성공적/미흡) ④ 등급 ↔ 점수 정합 — /20은 A~F 등급표, 4기준(/16)은 평균(종합÷기준수) 4단계 밴드 [품질검토] ⑤ 총점(`NN/100`) 전건 일치·세부항목 합산==총점·등급(A~D)↔총점↔Pass/Non-Pass +실제 KOICA 종료평가 PDF 334건 전수 스윕(2026-08)으로 보정한 오탐 억제: + - 범례·임계값 문장("…이상이면 매우 성공적, …" / 라벨 3종 이상 열거)은 평정이 아니라 + 척도 정의이므로 제외한다. + - 품질검토 도장("평가품질 등급: B")은 A~D 품질 체계라 사업 A~F 등급과 다른 체계다 — + 사업평가 모드의 등급 수집에서 '품질' 줄을 제외한다. + - 만족도·설문 종합점수(5점 척도 등)는 DAC 종합점수가 아니다 — 해당 줄을 제외하고, + 사업 종합점수는 24 이하만 인정한다(체계 최대 = 24점). + - `8/20개소`(개수)·`7/20(월)`(일정) 같은 N/M 표기는 점수가 아니다 — 단위·요일이 + 붙으면 제외한다. '잠정·범위·보류' 표기가 있는 줄은 정당한 복수 값이므로 일치 검사에서 제외한다. +다건 묶음(평가용역) 보고서는 사업별로 다른 종합점수·등급이 정상이다 — 이 검사는 +단일 사업 문서 기준이므로, 묶음이면 사업 단위로 잘라서 검사하라. **확인 불가와 통과를 구분**한다 — 검사할 수치를 하나도 못 찾으면 '통과'가 아니라 '확인 불가'로 알린다(거짓 초록불 방지). fail-open: 크래시 시 종료 0. 사용법: python3 scripts/consistency_check.py [--mode auto|project|quality] -종료 코드: 0 = 통과/확인 불가 / 2 = 위반(한국어 번호 목록, stderr). +종료 코드: 0 = 통과 / 2 = 위반(한국어 번호 목록, stderr) / 3 = 확인 불가(검사할 수치 +없음 — 통과 아님). 읽기 실패·내부 오류는 fail-open으로 0. 표준 라이브러리만 사용. """ @@ -43,12 +57,37 @@ (Decimal("1.5"), "부분 성공적"), (None, "미흡")] # 라벨 매칭(긴 것 먼저 — '성공적'이 '매우 성공적'·'부분 성공적'의 부분문자열) LABEL_ALT = r"매우\s*성공적|부분\s*성공적|성공적|미흡" +# 사업 종합점수(DAC 체계) 상한 — 5기준 20점·CTS 6기준 24점이 최대 +COMPOSITE_MAX = Decimal(24) +# 만족도·설문 종합점수(5점 척도 등)는 DAC 종합점수가 아니다 +SURVEY_WORDS = ("만족도", "설문", "응답률", "응답자", "리커트") def norm_label(s): return re.sub(r"\s+", " ", s).strip() +def distinct_labels(line): + """줄 안의 서로 다른 등급 라벨 수. 3종 이상 열거는 평정이 아니라 척도 범례다.""" + return len({norm_label(m.group(0)) for m in re.finditer(LABEL_ALT, line)}) + + +def is_threshold_def(line): + """'…점 이상이면 매우 성공적, … 미만이면 …' 같은 등급 산정 기준 서술.""" + return ("이상이면" in line or "미만이면" in line + or ("이상" in line and "미만" in line)) + + +def is_mismatch_quote(line): + """불일치를 '보고하는' 메타 서술 줄 — 값 주장이 아니라 인용이다. + + 검증 초안이 원보고서의 총점 불일치를 "국문 11.7/24 vs 영문 12.7/24 — 상호 + 불일치"처럼 한 줄로 인용하면, 그 줄의 두 값은 초안 자신의 자기모순이 아니다. + 정상적인 값 주장 줄은 '불일치'라는 단어를 쓰지 않는다. + """ + return "불일치" in line + + def band20(total): for lo, letter in GRADE_20: if lo is None or total >= lo: @@ -94,40 +133,98 @@ def detect_mode(text): # ---------- 추출기 ---------- -def find_totals(text, denoms): - """분모부 표기: NN/16·20·24·100. 잠정 줄 제외. {denom: [(Decimal, lineno)]}""" +def find_totals(text, denoms, require_words=None): + """분모부 표기: NN/16·20·24·100. 잠정 줄 제외. {denom: [(Decimal, lineno)]} + + 점수가 아닌 N/M 표기를 배제한다 — 개수(`8/20개소`), 일정·날짜(`7/20(월)`, + `3/20-24`, `2020/7/20`), 비율 분모 연속(`20/100,000명`), 남/여 인원(`(35/24)`, + 분자>분모라 점수일 수 없음). require_words를 주면 그 단어가 있는 줄만 본다 + (품질검토 총점 줄을 PDM 달성률 `NN/100점`과 구분할 때). + """ out = {} - pat = re.compile(r"(? Decimal(m.group(2)): + continue + out.setdefault(m.group(2), []).append((val, lineno)) + return out + + +def find_totals_alt(text, denoms): + """분모가 뒤에 오는 표기. {denom: [(Decimal, lineno)]} + + 국문 역순 `총 24점 만점 중 11.7(점)` + 영문 `12.7 (points) out of 24`. + 국문↔영문 요약이 다른 값을 적는 사고(캄보디아 11.7 vs 12.7)는 이 형식에서만 잡힌다. + """ + out = {} + pat_ko = re.compile(r"(?= 3 or is_threshold_def(line): continue for m in pat.finditer(line): + # PDF 줄바꿈으로 '부분 성공적'이 '부/분 성공적'으로 쪼개지면 '성공적'만 + # 잡혀 거짓 불일치가 된다 — 직전 텍스트가 '분'으로 끝나면 건너뛴다. + if norm_label(m.group(1)) == "성공적" and re.search(r"분\s*$", line[:m.start()]): + continue out.append((norm_label(m.group(1)), lineno)) return out @@ -176,11 +289,12 @@ def extract_quality_items(text): def check_project(text, violations): checks = 0 - # ① 종합점수 전건 일치 (분모부 + 라벨 맨숫자) + # ① 종합점수 전건 일치 (분모부 + 역순·영문 + 라벨 맨숫자) comp_vals = {} # Decimal -> [lineno] - for _denom, entries in find_totals(text, ["16", "20", "24"]).items(): - for val, lineno in entries: - comp_vals.setdefault(val, []).append(lineno) + for finder in (find_totals, find_totals_alt): + for _denom, entries in finder(text, ["16", "20", "24"]).items(): + for val, lineno in entries: + comp_vals.setdefault(val, []).append(lineno) for val, lineno in find_composite_labeled(text): comp_vals.setdefault(val, []).append(lineno) if comp_vals: @@ -190,9 +304,10 @@ def check_project(text, violations): violations.append(f"종합점수 표기 불일치 — {desc}. 같은 종합점수는 문서 전체에서 일치해야 합니다") composite = next(iter(comp_vals)) if len(comp_vals) == 1 else None - # ② a+b+c+d = 종합점수 + # ② a+b+c+d = 종합점수 — 기준 수는 4~6개(구 4기준·표준 5기준·CTS 6기준). + # 그 밖의 개수는 표 추출이 불완전하거나 다른 표를 잡은 것이므로 합산하지 않는다. means = find_criterion_means(text) - if len(means) >= 2 and composite is not None: + if 4 <= len(means) <= 6 and composite is not None: checks += 1 s = dec2(sum(means)) if s != composite: @@ -218,7 +333,7 @@ def check_project(text, violations): if stated != expected: violations.append( f"등급-점수 괴리 — 종합점수 {composite}/20의 등급표 판정은 {expected}인데 본문 등급은 {stated}") - if len(label_set) == 1 and means: + if len(label_set) == 1 and 4 <= len(means) <= 6: n_crit = len(means) mean = dec2(composite / Decimal(n_crit)) expected, stated = band_label(mean), next(iter(label_set)) @@ -233,7 +348,8 @@ def check_project(text, violations): def check_quality(text, violations): checks = 0 distinct = {} - for val, lineno in find_totals(text, ["100"]).get("100", []): + # PDM 달성률·만족도도 `NN/100점`으로 적힌다 — 품질검토 총점 줄만 본다. + for val, lineno in find_totals(text, ["100"], require_words=("총점", "품질", "합계")).get("100", []): distinct.setdefault(val, []).append(lineno) if distinct: checks += 1 @@ -247,7 +363,8 @@ def check_quality(text, violations): s = sum(sc for _, sc in items) if s != total_val: violations.append(f"총점 {total_val} ≠ 세부항목 합산 {s}") - gset = set(re.findall(r"품질\s*등급[^A-D\n]{0,20}([A-D])", text)) + # 'A~C'(범위)·'D인 경우'(범례 서술)는 판정이 아니다 — 뒤에 ~·인이 붙으면 제외. + gset = set(re.findall(r"품질\s*등급[^A-D\n]{0,20}([A-D])(?![A-Za-z+~∼인])", text)) if gset: checks += 1 if len(gset) > 1: @@ -265,8 +382,9 @@ def check_quality(text, violations): return checks -SUPPORTED = ("지원 형식: 종합점수 NN/16·20·24 또는 '종합 점수 … 12.34', " - "평균 점수(a~d), 등급 A~F 또는 매우 성공적/성공적/부분 성공적/미흡, 품질 총점 NN/100") +SUPPORTED = ("지원 형식: 종합점수 NN/16·20·24, 'NN점 만점 중 X', 'X (points) out of NN', " + "'종합 점수 … 12.34', 평균 점수(a~d), 등급 A~F 또는 " + "매우 성공적/성공적/부분 성공적/미흡, 품질 총점 NN/100") def run(path, mode): @@ -279,8 +397,9 @@ def run(path, mode): if mode == "auto": mode = detect_mode(text) if mode == "none": - print("[consistency_check] 수치·등급 패턴 없음 — 확인 불가(통과 아님). " + SUPPORTED, file=sys.stderr) - return 0 + print("[consistency_check] 수치·등급 패턴 없음 — 확인 불가(통과 아님, exit 3). " + SUPPORTED, + file=sys.stderr) + return 3 violations = [] checks = check_project(text, violations) if mode == "project" else check_quality(text, violations) name = os.path.basename(path) @@ -290,9 +409,9 @@ def run(path, mode): print(f" {i}) {v}", file=sys.stderr) return 2 if checks == 0: - print(f"[consistency_check] {name} — 확인할 수치를 못 찾음: '통과' 아님(확인 불가). " + SUPPORTED, - file=sys.stderr) - return 0 + print(f"[consistency_check] {name} — 확인할 수치를 못 찾음: '통과' 아님(확인 불가, exit 3). " + + SUPPORTED, file=sys.stderr) + return 3 print(f"[consistency_check] {name} — 수치 일관성 통과 ({mode} 모드, {checks}개 항목 확인)") return 0 diff --git a/skills/quality-review/SKILL.md b/skills/quality-review/SKILL.md index f4be36e..c15e0f1 100644 --- a/skills/quality-review/SKILL.md +++ b/skills/quality-review/SKILL.md @@ -1,21 +1,26 @@ --- name: quality-review -description: 완성된 평가보고서의 품질을 KOICA 평가품질검토 가이드라인 v2(24문항 → 10세부항목 → 100점, A~D, Pass/Non-Pass)로 심사한다. 사업이 잘됐나가 아니라 보고서가 잘 쓰였나를 보는 메타 평가다. -when_to_use: 사용자가 "이 평가보고서 품질을 검토해줘", "품질심사 해줘", "보고서가 잘 쓰였는지 봐줘", "Pass 되는지 봐줘"라고 할 때. 사업 자체를 평가하는 것(evaluate)과 혼동하지 말 것. +description: 완성된 평가보고서의 품질을 KOICA 평가품질검토 가이드라인 v2(24문항→10세부항목→100점, A~D, Pass/Non-Pass)로 심사한다. "이 평가보고서 품질을 검토해줘", "품질심사 해줘", "보고서가 잘 쓰였는지 봐줘", "Pass 되는지 봐줘"라고 요청할 때 사용한다. 사업 성패가 아니라 보고서 품질을 보는 메타 평가이며 사업 자체 평가에는 사용하지 않는다. --- # 평가보고서 품질심사 (메타 평가) **이건 사업 평가가 아니다.** -- 사업 평가(`/deveval:evaluate`): *사업이 잘됐나?* → A~F 등급 +- 사업 평가(`deveval:evaluate`): *사업이 잘됐나?* → A~F 등급 - **품질 심사(이 스킬)**: *평가보고서가 잘 쓰였나?* → **A~D 등급** 사업의 성패를 다시 판단하지 않는다. **보고서가 평가를 제대로 수행·기술했는지**를 본다. +## 호스트 호환 + +- `deveval-root`가 있으면 그 출력으로 플러그인 루트를 구한다. 없으면 호스트가 노출한 이 `SKILL.md`의 절대경로에 정확히 `realpath(dirname()/../..)`(또는 동등한 경로 정규화)을 적용한다. 호스트가 스킬 절대경로를 노출하지 않으면 현재 작업 폴더나 파일 검색 결과로 추측하지 말고, 루트를 확인할 수 없다고 알린 뒤 사용자에게 플러그인 루트를 요청한다. `reference/`, `scripts/`, `agents/`가 실제로 존재하는지 확인한다. +- Claude Code에서는 등록된 `report-quality-inspector`를 사용한다. Codex 등 이름 기반 평가관을 자동 등록하지 않는 호스트에서는 `/agents/report-quality-inspector.md`를 끝까지 읽고 그 지침으로 서브에이전트를 위임한다. 서브에이전트가 없으면 같은 역할 지침을 직접 적용한다. +- 이 심사 역할은 읽기 전용이다. 일반 서브에이전트로 대체할 때도 파일을 생성·수정하지 말라고 명시한다. + ## 절차 -1. **기준 문서 경로 확보** — `deveval-root`로 플러그인 경로를 얻는다. 룰브릭은 `/reference/KOICA-품질검토-체크리스트.md`(가이드라인 **v2, 2025.6**). +1. **기준 문서 경로 확보** — 위 호스트 호환 절차로 ``를 구한다. 룰브릭은 `/reference/KOICA-품질검토-체크리스트.md`(가이드라인 **v2, 2025.6**). 2. **`report-quality-inspector`에게 위임** — 심사 대상 보고서 경로 + 룰브릭 절대경로를 전달한다. 3. **결과 정리 후 사람 인계** — 최종 품질등급은 **독립평가패널(사람)**이 확정함을 명시한다. @@ -48,7 +53,9 @@ when_to_use: 사용자가 "이 평가보고서 품질을 검토해줘", "품질 deveval-consistency-check <보고서.md> ``` -국문↔영문↔표 수치 불일치(예: 11.7 vs 12.7), 등급↔점수 괴리, `a+b+c+d ≠ 종합점수` 산술 오류를 결정적으로 잡아 문항 18(등급-본문 일치) 근거로 쓸 수 있다. (마크다운 텍스트가 있을 때만. PDF면 생략.) +명령이 `PATH`에 없으면 `python3 /scripts/consistency_check.py <보고서.md>`를 실행한다. + +국문↔영문↔표 수치 불일치(예: 11.7 vs 12.7 — 국문 "24점 만점 중 N"·영문 "N points out of M" 표기 포함), 등급↔점수 괴리, `a+b+c+d ≠ 종합점수` 산술 오류를 결정적으로 잡아 문항 18(등급-본문 일치) 근거로 쓸 수 있다. 종료 코드: 0 통과 / 2 위반 / 3 확인 불가(통과 아님). (마크다운 텍스트가 있을 때만. PDF면 생략. 다건 묶음 평가용역이면 사업별로 잘라서 검사 — 사업별 상이 점수는 정상이다.) ## 규정 인용 검증 (선택) diff --git a/skills/write-report/SKILL.md b/skills/write-report/SKILL.md index 3891166..ea6d3a7 100644 --- a/skills/write-report/SKILL.md +++ b/skills/write-report/SKILL.md @@ -1,35 +1,41 @@ --- name: write-report -description: 평가 결과로 KOICA 표준 종료평가보고서 초안을 장별로 작성하고, 수치 일관성 검사와 서술-근거 검증을 거쳐 사람에게 넘긴다. 근거 없으면 서술 없음 원칙으로 환각을 막는다. -when_to_use: 사용자가 "평가 결과로 보고서 써줘", "종료평가보고서 초안 작성", "Ⅳ장 기준별 평가결과 써줘", "보고서 정리해줘"라고 할 때. 평가가 이미 끝났거나 평가 결과 자료가 있어야 한다. +description: 평가 결과로 KOICA 표준 종료평가보고서 초안을 장별로 작성하고 수치 일관성 검사와 서술-근거 검증을 거쳐 사람에게 넘긴다. "평가 결과로 보고서 써줘", "종료평가보고서 초안 작성", "Ⅳ장 기준별 평가결과 써줘", "보고서 정리해줘"라고 요청할 때 사용한다. 평가 결과가 있어야 하며 근거 없으면 서술 없음 원칙으로 환각을 막는다. --- # 평가보고서 작성 (근거 없으면 서술 없음) 보고서 작성은 평가보다 **환각 위험이 훨씬 크다** — 매끄럽지만 근거 없는 문장을 지어내기 쉽다. 그래서 작성 → 수치검사(코드) → 서술검증(LLM) → 사람의 순서로 조인다. +## 호스트 호환 + +- `deveval-root`가 있으면 그 출력으로 플러그인 루트를 구한다. 없으면 호스트가 노출한 이 `SKILL.md`의 절대경로에 정확히 `realpath(dirname()/../..)`(또는 동등한 경로 정규화)을 적용한다. 호스트가 스킬 절대경로를 노출하지 않으면 현재 작업 폴더나 파일 검색 결과로 추측하지 말고, 루트를 확인할 수 없다고 알린 뒤 사용자에게 플러그인 루트를 요청한다. `templates/`, `scripts/`, `agents/`가 실제로 존재하는지 확인한다. +- Claude Code에서는 등록된 `report-composer`와 `narrative-verifier`를 사용한다. Codex 등 이름 기반 평가관을 자동 등록하지 않는 호스트에서는 위임 전에 각각 `/agents/<역할>.md`를 끝까지 읽고 그 지침으로 서브에이전트를 위임한다. 서브에이전트가 없으면 같은 역할을 순차 적용한다. +- `report-composer`만 사용자 작업 폴더의 `.omo/` 산출물을 쓸 수 있다. `narrative-verifier`는 읽기 전용으로 두고, 일반 서브에이전트로 대체할 때도 이 권한 경계를 과업에 명시한다. + ## 절차 -1. **전제 확인** — 평가 결과(기준별 점수·근거)가 있어야 한다. 없으면 `/deveval:evaluate`를 먼저 돌린다. +1. **전제 확인** — 평가 결과(기준별 점수·근거)가 있어야 한다. 없으면 `deveval:evaluate`를 먼저 적용한다. -2. **경로 확보** — `deveval-root`로 플러그인 경로를 얻는다. 보고서 구조 템플릿은 `/templates/evaluation-report-template.md`. +2. **경로 확보** — 위 호스트 호환 절차로 ``를 구한다. 보고서 구조 템플릿은 `/templates/evaluation-report-template.md`. -3. **`report-composer`(쓰기 권한)에게 위임** — 템플릿 구조로 장별 초안 작성. **모든 사실·평정 서술에 출처**, 미확인은 `[확인 필요]`, **국문/영문/표의 같은 수치는 반드시 일치**. +3. **`report-composer`(쓰기 권한)에게 위임** — 위임 프롬프트에 **평가 결과 전문(또는 각 절대경로) + 사업 원자료 절대경로 + 템플릿 절대경로 + 초안 출력 절대경로**를 명시하고, 템플릿 구조로 장별 초안을 작성시킨다. **모든 사실·평정 서술에 출처**, 미확인은 `[확인 필요]`, **국문/영문/표의 같은 수치는 반드시 일치**. - 초안은 사용자 작업 폴더의 `.omo/draft-report*.md`에 저장한다(평가자의 로컬 산출물 — 플러그인 디렉토리에 쓰지 마라). 4. **수치 일관성 점검 (코드)** — 초안에 대해 실행: ``` deveval-consistency-check <초안 경로> ``` - 종합점수·총점이 국문·영문·표에서 어긋나거나(예: 11.7 vs 12.7), 등급↔점수가 안 맞거나, `a+b+c+d ≠ 종합점수`면 위반 목록(exit 2)이 나온다 → `report-composer`에게 **그 항목만** 고쳐 다시 쓰게 한 뒤 넘어간다. + 명령이 `PATH`에 없으면 `python3 /scripts/consistency_check.py <초안 경로>`를 실행한다. + 종합점수·총점이 국문·영문·표에서 어긋나거나(예: 실제 종료평가 2건에서 확인된 11.7 vs 12.7 — 국문 "24점 만점 중 11.7" ↔ 영문 "12.7 points out of 24"도 잡는다), 등급↔점수가 안 맞거나, `a+b+c+d ≠ 종합점수`면 위반 목록(exit 2)이 나온다 → `report-composer`에게 **그 항목만** 고쳐 다시 쓰게 한 뒤 넘어간다. 기계적 수치 대조는 LLM 눈대중보다 코드가 정확·재현적이다. 서술 형식은 건드리지 않고 **수치 자기모순만** 본다. - ⚠️ "확인 불가"가 나오면 통과가 아니다 — 검사기가 수치를 못 읽었다는 뜻이니 표기 형식을 확인한다. + ⚠️ "확인 불가"(exit 3)가 나오면 통과가 아니다 — 검사기가 수치를 못 읽었다는 뜻이니 표기 형식을 확인한다. (종료 코드: 0 통과 / 2 위반 / 3 확인 불가) 5. **규정 인용 검증 (선택 — 게이트웨이)** — `oda-intelligence` 커넥터가 세션에 보이면, 초안의 `{규정명} 제N조` 인용을 `verify_citation`으로 대조한다(`not_found` = 존재하지 않는 조문, `unknown_source` = 인덱스에 없는 규정명). 걸린 항목은 `report-composer`에게 **그 인용만** 정정·삭제시킨다. 조문을 원문 그대로 실어야 하면 `get_article`로 전문을 받아 쓴다. 이 검사는 KOICA 내부규정 인덱스만 대조하므로 외부 법령 인용은 이걸로 확정하지 마라. 커넥터가 없으면 건너뛰고 "규정 인용 미검증"을 한계에 남긴다(연동 안내: 저장소 `docs/oda-intelligence-integration.md`). -6. **`narrative-verifier`(읽기)에게 위임** — 서술-근거 *의미* 정합성 점검(근거가 그 주장을 실제로 뒷받침하는가). 수치·등급의 기계적 일치는 4에서 코드가 봤으니, 여기선 **환각·해석 오류**에 집중한다. +6. **`narrative-verifier`(읽기)에게 위임** — 위임 프롬프트에 **초안 절대경로 + 초안이 인용·사용한 평가 결과와 사업 원자료의 전문 또는 절대경로**를 모두 넣는다. 특히 일반 Codex 서브에이전트가 `report-composer`의 대화 맥락을 상속한다고 가정하지 않는다. 서술-근거 *의미* 정합성(근거가 그 주장을 실제로 뒷받침하는가)을 점검하고, 수치·등급의 기계적 일치는 4에서 코드가 봤으니 여기서는 **환각·해석 오류**에 집중한다. -7. **(선택) 품질 자가심사** — `/deveval:quality-review`로 24문항 심사를 돌려 미흡한 부분을 보완한다. +7. **(선택) 품질 자가심사** — `deveval:quality-review`로 24문항 심사를 돌려 미흡한 부분을 보완한다. 8. **사람(평가담당관) 검토·확정** — 특히 최종 등급·정무적 제언. diff --git a/tests/fixtures/no-numbers.md b/tests/fixtures/no-numbers.md new file mode 100644 index 0000000..35d8dc5 --- /dev/null +++ b/tests/fixtures/no-numbers.md @@ -0,0 +1,7 @@ +# 종료평가 서술 (픽스처 — 검사할 수치 없음) + + + +본 사업의 평가등급 산정은 별도 절차에 따라 진행될 예정이며, 본 문서에는 +종합점수와 등급이 아직 기재되지 않았다. 평가팀은 근거 자료를 수집 중이다. diff --git a/tests/fixtures/project-clean.md b/tests/fixtures/project-clean.md new file mode 100644 index 0000000..e5ae71e --- /dev/null +++ b/tests/fixtures/project-clean.md @@ -0,0 +1,25 @@ +# 종료평가 결과 요약 (픽스처 — 정상) + + + +## 국문 요약 + +5개 기준 합산 결과 종합점수는 14.5/20점, 등급: C(성공적)이다. + +## Executive Summary + +The project scored 14.5 points out of 20. Grade: C. + +## 기준별 평가결과 + +| 기준 | 평균 점수 | +|------|----------| +| 적절성 평균 점수(a) | 3.0 | +| 일관성 평균 점수(b) | 2.5 | +| 효과성 평균 점수(c) | 3.0 | +| 효율성 평균 점수(d) | 3.0 | +| 지속가능성 평균 점수(e) | 3.0 | + +종합 점수 14.5 +등급: C diff --git a/tests/fixtures/project-grade-gap.md b/tests/fixtures/project-grade-gap.md new file mode 100644 index 0000000..150ac54 --- /dev/null +++ b/tests/fixtures/project-grade-gap.md @@ -0,0 +1,14 @@ +# 종료평가 결과 요약 (픽스처 — 등급-점수 괴리) + + + +## 국문 요약 + +5개 기준 합산 결과 종합점수는 12/20점이다. + +등급: B + +## Executive Summary + +The project scored 12 points out of 20. Grade: B. diff --git a/tests/fixtures/project-ko-en-mismatch.md b/tests/fixtures/project-ko-en-mismatch.md new file mode 100644 index 0000000..713f929 --- /dev/null +++ b/tests/fixtures/project-ko-en-mismatch.md @@ -0,0 +1,17 @@ +# 종료평가 결과 요약 (픽스처 — 국문↔영문 요약 불일치) + + + +## 국문 요약 + +평가팀이 개발한 매트릭스 기반 사업 평가 결과, 본 사업은 총 24점 만점 중 11.7 +점으로 '부분 성공적'으로 평가되었다. 특히 기술적 측면에서 상대적으로 높은 +평가를 받았다. + +## Executive Summary + +This project scored 12.7 points out of 24 on the evaluation team's +matrix-based model. The project scored relatively highly in terms of +technical feasibility. diff --git a/tests/fixtures/project-legend-noise.md b/tests/fixtures/project-legend-noise.md new file mode 100644 index 0000000..35e3d4d --- /dev/null +++ b/tests/fixtures/project-legend-noise.md @@ -0,0 +1,36 @@ +# 종료평가 보고서 (픽스처 — 오탐 회귀 팩) + + + +## 요약 + +종합점수: 13.6/20점 + +등급: D + +## 오탐 유발 패턴 모음 (전부 무시되어야 함) + +종합평가 등급 (매우 성공적, 성공적, 부분 성공적, 미흡) + +※ 본 평가등급은 국무조정실 지침에 따른 것으로, 종합점수가 14점 이상이면 매우 +성공적, 11점 이상 14점 미만이면 성공적, 8점 이상 11점 미만이면 부분 성공적, +8점 미만이면 미흡으로 평가함 + +평가품질 등급 : B + +용역종합 등급 : A + +입학생 수 (남/여) (35/24) + +영아사망률: 20/100,000명 감소 목표 + +3/20-24 비대면 현지조사 준비(면담, 설문조사 질문지 작성) + +일정: 7/20(월) 현지 도착, 7/24(금) 철수 + +성 주류화 등 기타 항목에 대한 만족도 설문 종합점수 4.07점으로 긍정적으로 평가됨 + +본 사업은 종합평가 등급은 부 +분 성공적 등급으로 평가된 유사사업과 비교되었다 diff --git a/tests/fixtures/quality-clean.md b/tests/fixtures/quality-clean.md new file mode 100644 index 0000000..7147dbe --- /dev/null +++ b/tests/fixtures/quality-clean.md @@ -0,0 +1,24 @@ +# 평가보고서 품질검토 결과 (픽스처 — 정상) + + + +## 세부항목 채점 + +| # | 부문 | 점수 | 감점요인·근거 | +|:--:|:--:|:--:|------| +| ① | Ⅰ 맥락분석 | 8 | 배경 서술 양호 | +| ② | Ⅰ 방법론 | 8 | 매트릭스 제시 | +| ③ | Ⅱ 데이터 수집 | 10 | 삼각검증 우수 | +| ④ | Ⅱ 데이터 품질 | 8 | 결측 처리 명시 | +| ⑤ | Ⅲ 분석방법 | 10 | 정성·정량 병행 | +| ⑥ | Ⅲ 논리성 | 8 | 결과 도출 타당 | +| ⑦ | Ⅲ 해석 | 8 | 근거 기반 | +| ⑧ | Ⅳ 결론 | 8 | 등급-본문 일치 | +| ⑨ | Ⅳ 제언 | 8 | SMART 충족 | +| ⑩ | Ⅴ 요약·구조 | 8 | 명확 | + +## 종합 + +- 총점: 84/100 +- 품질등급: B → Pass diff --git a/tests/fixtures/quality-total-mismatch.md b/tests/fixtures/quality-total-mismatch.md new file mode 100644 index 0000000..ba25a81 --- /dev/null +++ b/tests/fixtures/quality-total-mismatch.md @@ -0,0 +1,17 @@ +# 평가보고서 품질검토 결과 (픽스처 — 총점 불일치) + + + +## 세부항목 채점 결과 + +품질검토 총점: 84/100 + +## 종합 + +- 총점: 78/100 +- 품질등급: B → Pass + +## 무시되어야 할 패턴 + +초청연수생 만족도 91.5/100점 달성 (PDM 성과지표) diff --git a/tests/test_boulder.sh b/tests/test_boulder.sh new file mode 100755 index 0000000..463626e --- /dev/null +++ b/tests/test_boulder.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# hooks/boulder.sh (완료 엔진 Stop hook) 동작 테스트. +# +# 실행: bash tests/test_boulder.sh +# 종료 코드 계약: 0 = 종료 허용 / 2 = 차단(계속 작업). +# 일시정지 의미론: 정체 3회·시도 20회 초과 시 작업판 지문을 남기고 종료 허용, +# 작업판이 편집되기 전까지 다시 차단하지 않는다. + +set -u +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +HOOK="$ROOT/hooks/boulder.sh" +TMP="$(mktemp -d)" +trap 'chmod -R u+w "$TMP" 2>/dev/null; rm -rf "$TMP"' EXIT + +PASS=0; FAIL=0 +check() { # check <설명> <기대코드> <실제코드> + if [ "$2" = "$3" ]; then + PASS=$((PASS + 1)); echo "ok - $1" + else + FAIL=$((FAIL + 1)); echo "FAIL - $1 (기대 $2, 실제 $3)" + fi +} +skip() { echo "skip - $1"; } +# BSD sed는 -i에 접미사 인자를 요구하고 GNU sed는 받지 않는다 (CI는 Linux, 개발은 macOS) +sed_i() { + if sed --version >/dev/null 2>&1; then sed -i "$@"; else sed -i '' "$@"; fi +} +run_hook() { # run_hook <프로젝트폴더> → 종료코드를 표준출력 + CLAUDE_PROJECT_DIR="$1" bash "$HOOK" >/dev/null 2>&1 + echo $? +} + +# 1) 작업판 없음 → 평가 모드 아님, 종료 허용 +D="$TMP/no-plan"; mkdir -p "$D" +check "작업판 없으면 종료 허용" 0 "$(run_hook "$D")" + +# 2) 전부 [x]/[~] → 완료, 종료 허용 + 상태 리셋 +D="$TMP/done"; mkdir -p "$D/.omo" +printf -- '- [x] 적절성\n- [~] 타당성 (CTS 아님)\n' > "$D/.omo/eval-plan.md" +touch "$D/.omo/.boulder-state" +check "전부 완료/막힘이면 종료 허용" 0 "$(run_hook "$D")" +[ ! -f "$D/.omo/.boulder-state" ]; check "완료 시 상태 파일 제거" 0 $? + +# 3) 미완료 → 차단(exit 2) + 안내 메시지 +D="$TMP/incomplete"; mkdir -p "$D/.omo" +printf -- '- [ ] 적절성\n- [ ] 효과성\n' > "$D/.omo/eval-plan.md" +check "미완료면 차단" 2 "$(run_hook "$D")" +MSG=$(CLAUDE_PROJECT_DIR="$D" bash "$HOOK" 2>&1 >/dev/null; true) +case "$MSG" in *"미완료 항목"*) check "차단 메시지에 미완료 안내 포함" 0 0 ;; *) check "차단 메시지에 미완료 안내 포함" 0 1 ;; esac + +# 4) 정체 3회 → 일시정지(종료 허용), 방치 시 재차단 없음, 편집 시 재무장 +D="$TMP/stall"; mkdir -p "$D/.omo" +printf -- '- [ ] 적절성\n- [ ] 효과성\n' > "$D/.omo/eval-plan.md" +c1=$(run_hook "$D"); c2=$(run_hook "$D"); c3=$(run_hook "$D"); c4=$(run_hook "$D") +check "정체 1~3회차는 차단" 2 "$c1" +check "정체 누적 중에도 차단" 2 "$c3" +check "정체 3회면 일시정지(종료 허용)" 0 "$c4" +check "방치된 작업판은 다시 조르지 않음" 0 "$(run_hook "$D")" +grep -q '^paused ' "$D/.omo/.boulder-state"; check "상태 파일에 paused 기록" 0 $? +printf -- '- [x] 적절성\n- [ ] 효과성\n' > "$D/.omo/eval-plan.md" # 편집 = 재개 의사 +check "작업판 편집 시 재무장·차단 재개" 2 "$(run_hook "$D")" + +# 5) 진전이 있으면 정체 리셋 — 4회 연속 호출해도 매번 진전이면 계속 차단 +D="$TMP/progress"; mkdir -p "$D/.omo" +printf -- '- [ ] a\n- [ ] b\n- [ ] c\n- [ ] d\n- [ ] e\n' > "$D/.omo/eval-plan.md" +codes="" +for i in 1 2 3 4; do + codes="$codes $(run_hook "$D")" + sed_i "${i}s/\[ \]/[x]/" "$D/.omo/eval-plan.md" # 진전 시뮬레이션: 항목 하나 완료 +done +check "진전이 계속되면 정체 없이 차단 유지" " 2 2 2 2" "$codes" + +# 6) 시도 상한: 진전이 있어도 총 20회 초과면 일시정지 +D="$TMP/cap"; mkdir -p "$D/.omo" +: > "$D/.omo/eval-plan.md" +for i in $(seq 1 30); do printf -- '- [ ] 항목%s\n' "$i" >> "$D/.omo/eval-plan.md"; done +last=""; capped_at="" +for i in $(seq 1 21); do + last=$(run_hook "$D") + [ "$last" = "0" ] && { capped_at=$i; break; } + sed_i "${i}s/\[ \]/[x]/" "$D/.omo/eval-plan.md" +done +check "21회차에 시도 상한 일시정지" "21" "${capped_at:-none}" +check "상한 도달 시 종료 허용" 0 "$last" + +# 7) 읽기전용 상태 디렉토리 → fail-open 종료 허용 +# root는 퍼미션을 무시하므로(-w가 참) 이 케이스를 재현할 수 없다 — 건너뛴다. +if [ "$(id -u)" = "0" ]; then + skip "상태 저장 불가면 종료 허용(fail-open) — root라 읽기전용 재현 불가" +else + D="$TMP/readonly"; mkdir -p "$D/.omo" + printf -- '- [ ] 적절성\n' > "$D/.omo/eval-plan.md" + chmod 555 "$D/.omo" + check "상태 저장 불가면 종료 허용(fail-open)" 0 "$(run_hook "$D")" + chmod 755 "$D/.omo" +fi + +echo +echo "통과 $PASS / 실패 $FAIL" +[ "$FAIL" -eq 0 ] diff --git a/tests/test_consistency_check.py b/tests/test_consistency_check.py new file mode 100644 index 0000000..4567d99 --- /dev/null +++ b/tests/test_consistency_check.py @@ -0,0 +1,169 @@ +"""consistency_check.py 회귀 테스트. + +픽스처는 실제 KOICA 종료평가 PDF 334건 전수 스윕(2026-08)에서 관측된 사고·오탐 +유형을 합성으로 재현한 것이다 — 특히 국문↔영문 요약 종합점수 불일치(캄보디아 +CTS 시리즈에서 실물 2건 확인: 11.7 vs 12.7, 9.3 vs 10.3)가 핵심 회귀 케이스다. + +실행: python3 -m unittest discover -s tests +표준 라이브러리만 사용(pytest 불필요). +""" + +import contextlib +import io +import os +import sys +import unittest + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "scripts")) + +import consistency_check as cc # noqa: E402 + +FIXTURES = os.path.join(ROOT, "tests", "fixtures") + + +def run_fixture(name, mode="auto"): + """(exit_code, 출력 텍스트) — stdout·stderr를 합쳐 돌려준다.""" + buf_out, buf_err = io.StringIO(), io.StringIO() + with contextlib.redirect_stdout(buf_out), contextlib.redirect_stderr(buf_err): + code = cc.run(os.path.join(FIXTURES, name), mode) + return code, buf_out.getvalue() + buf_err.getvalue() + + +class ExitCodeContract(unittest.TestCase): + """종료 코드 계약: 0=통과 / 2=위반 / 3=확인 불가. '확인 불가'는 통과가 아니다.""" + + def test_clean_passes(self): + code, out = run_fixture("project-clean.md") + self.assertEqual(code, 0, out) + self.assertIn("통과", out) + + def test_no_numbers_is_exit_3_not_0(self): + code, out = run_fixture("no-numbers.md") + self.assertEqual(code, 3, out) + self.assertIn("확인 불가", out) + + def test_missing_file_fails_open(self): + buf = io.StringIO() + with contextlib.redirect_stderr(buf): + code = cc.run(os.path.join(FIXTURES, "없는-파일.md"), "auto") + self.assertEqual(code, 0) + self.assertIn("fail-open", buf.getvalue()) + + +class KoEnMismatch(unittest.TestCase): + """국문 '24점 만점 중 11.7' ↔ 영문 '12.7 points out of 24' 불일치 검출.""" + + def test_cambodia_pattern_caught(self): + code, out = run_fixture("project-ko-en-mismatch.md") + self.assertEqual(code, 2, out) + self.assertIn("종합점수 표기 불일치", out) + self.assertIn("11.7", out) + self.assertIn("12.7", out) + + def test_reversed_korean_form_extracted(self): + got = cc.find_totals_alt("본 사업은 총 24점 만점 중 11.7점으로 평가", ["16", "20", "24"]) + self.assertEqual([v for v, _ in got["24"]], [cc.Decimal("11.7")]) + + def test_english_form_extracted(self): + got = cc.find_totals_alt("This project scored 12.7 points out of 24.", ["16", "20", "24"]) + self.assertEqual([v for v, _ in got["24"]], [cc.Decimal("12.7")]) + + +class GradeScoreGap(unittest.TestCase): + def test_grade_gap_caught(self): + code, out = run_fixture("project-grade-gap.md") + self.assertEqual(code, 2, out) + self.assertIn("등급-점수 괴리", out) + + +class FalsePositiveRegression(unittest.TestCase): + """스윕에서 확인된 오탐 유형 — 전부 무시되고 진짜 신호만 통과해야 한다.""" + + def test_legend_noise_pack_passes(self): + code, out = run_fixture("project-legend-noise.md") + self.assertEqual(code, 0, out) + self.assertIn("통과", out) + + def test_count_schedule_ratio_not_scores(self): + text = ("입학생 수 (남/여) (35/24)\n" + "3/20-24 비대면 현지조사 준비\n" + "일정: 7/20(월) 도착\n" + "달성 8/20개소\n" + "영아사망률 20/100,000명") + self.assertEqual(cc.find_totals(text, ["16", "20", "24"]), {}) + + def test_scores_with_jeom_suffix_kept(self): + got = cc.find_totals("종합점수는 14.5/20점이다", ["16", "20", "24"]) + self.assertEqual([v for v, _ in got["20"]], [cc.Decimal("14.5")]) + + def test_quality_grade_line_excluded_from_project_grades(self): + text = "평가품질 등급 : B\n용역종합 등급 : A\nKOICA 평가 등급: C" + self.assertEqual({g for g, _ in cc.find_grades_af(text)}, {"C"}) + + def test_quality_legend_range_and_definition_excluded(self): + text = ("품질 등급이 A~C인 보고서는 기본 요건 충족\n" + "품질 등급이 D인 경우 미부합\n" + "평가품질 등급 : C") + import re + gset = set(re.findall(r"품질\s*등급[^A-D\n]{0,20}([A-D])(?![A-Za-z+~∼인])", text)) + self.assertEqual(gset, {"C"}) + + def test_label_legend_line_skipped(self): + text = "종합평가 등급 (매우 성공적, 성공적, 부분 성공적, 미흡)" + self.assertEqual(cc.find_grades_label(text), []) + + def test_threshold_sentence_skipped(self): + text = "종합점수가 14점 이상이면 매우 성공적, 11점 이상 14점 미만이면 성공적으로 평가함 (등급)" + self.assertEqual(cc.find_grades_label(text), []) + self.assertEqual(cc.find_composite_labeled(text), []) + + def test_survey_composite_skipped(self): + text = "만족도 설문 종합점수 4.07점으로 긍정적" + self.assertEqual(cc.find_composite_labeled(text), []) + + def test_mismatch_quote_line_not_reflagged(self): + """원보고서의 불일치를 '인용·보고'하는 줄은 자기모순이 아니다. + + 단, 인용 줄 밖에서 값을 주장하는 진짜 불일치는 계속 잡혀야 한다. + """ + quote_only = "원보고서 총점: 국문요약 11.7/24 vs 영문요약 12.7/24 — 상호 불일치 [확인 필요]" + self.assertEqual(cc.find_totals(quote_only, ["16", "20", "24"]), {}) + real = ("국문 요약: 총 24점 만점 중 11.7점\n" + "Executive Summary: 12.7 points out of 24") + got = cc.find_totals_alt(real, ["16", "20", "24"]) + self.assertEqual({v for v, _ in got["24"]}, {cc.Decimal("11.7"), cc.Decimal("12.7")}) + + def test_composite_gap_stopwords_skipped(self): + """'종합점수' 뒤 창이 절차어를 넘어 무관한 숫자를 잡지 않아야 한다.""" + text = ("표준 5기준 종합점수·등급(D 또는 E)의 확정 및 CTS 6기준 매트릭스 총점 확정\n" + "표준 5기준 종합점수 산정과 별도로 취급했다 [05, 06]") + self.assertEqual(cc.find_composite_labeled(text), []) + kept = cc.find_composite_labeled("종합 점수 12.34") + self.assertEqual([v for v, _ in kept], [cc.Decimal("12.34")]) + + +class QualityMode(unittest.TestCase): + def test_quality_clean_passes(self): + code, out = run_fixture("quality-clean.md") + self.assertEqual(code, 0, out) + self.assertIn("quality 모드", out) + + def test_quality_total_mismatch_caught_and_pdm_rate_ignored(self): + code, out = run_fixture("quality-total-mismatch.md") + self.assertEqual(code, 2, out) + self.assertIn("총점(/100) 표기 불일치", out) + self.assertNotIn("91.5", out) + + +class BundledSample(unittest.TestCase): + def test_bundled_sample_is_honest_uncheckable(self): + """samples/의 서술형 샘플은 '통과'가 아니라 '확인 불가(3)'여야 한다.""" + buf_out, buf_err = io.StringIO(), io.StringIO() + with contextlib.redirect_stdout(buf_out), contextlib.redirect_stderr(buf_err): + code = cc.run(os.path.join(ROOT, "samples", "sample-evaluation-report.md"), "auto") + self.assertEqual(code, 3, buf_out.getvalue() + buf_err.getvalue()) + + +if __name__ == "__main__": + unittest.main()