mirror of
https://github.com/epoko77-ai/im-not-ai.git
synced 2026-06-21 13:18:09 +00:00
Compare commits
42 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14aeb52d13 |
||
|
|
01e226fc84 | ||
|
|
d83ecf2255 | ||
|
|
9cfb5f2f2d |
||
|
|
4f0d7012c2 | ||
|
|
5c011530ab | ||
|
|
499b0452d7 | ||
|
|
0f981e7a4c | ||
|
|
b7366a4fb3 | ||
|
|
5bab459294 | ||
|
|
ec6390db2c | ||
|
|
df4ca4abd3 | ||
|
|
6ae5fe6a9e | ||
|
|
8ee6bb03ad | ||
|
|
807172694d |
||
|
|
1cf0d0aa80 | ||
|
|
52beb370c8 |
||
|
|
be4377ba58 | ||
|
|
6bab7a52a3 |
||
|
|
ca2423bfda | ||
|
|
b5403b0891 |
||
|
|
b0404df7dc | ||
|
|
67b14e7fef |
||
|
|
f53b8bc032 | ||
|
|
6138697bc3 |
||
|
|
1018239fe4 | ||
|
|
43094bfb15 |
||
|
|
905d6f4e0f | ||
|
|
9e09e5ffda | ||
|
|
4b99ced03c | ||
|
|
ebe1328faa |
||
|
|
6ad338b93f |
||
|
|
1748d96e48 |
||
|
|
f6f208245d | ||
|
|
3fab1d8451 | ||
|
|
ec58fbee20 | ||
|
|
1be7f48e38 | ||
|
|
66f8399cff |
||
|
|
84f03c8887 | ||
|
|
69229af2f9 | ||
|
|
2fc2e3b62d | ||
|
|
e6dd130d08 |
53 changed files with 4908 additions and 1713 deletions
20
.claude-plugin/marketplace.json
Normal file
20
.claude-plugin/marketplace.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "im-not-ai",
|
||||
"owner": {
|
||||
"name": "epoko77-ai"
|
||||
},
|
||||
"metadata": {
|
||||
"description": "AI가 쓴 한글 텍스트를 사람이 쓴 글처럼 윤문하는 humanize-korean 스킬 마켓플레이스",
|
||||
"version": "1.5.0",
|
||||
"pluginRoot": "."
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "humanize-korean",
|
||||
"source": "./",
|
||||
"description": "AI가 쓴 한글 텍스트를 사람이 쓴 글처럼 윤문하는 오케스트레이터 스킬 + 서브에이전트 묶음 (Fast + strict).",
|
||||
"version": "1.5.0",
|
||||
"keywords": ["korean", "humanize", "ai-detector", "윤문", "번역투"]
|
||||
}
|
||||
]
|
||||
}
|
||||
13
.claude-plugin/plugin.json
Normal file
13
.claude-plugin/plugin.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "humanize-korean",
|
||||
"version": "1.5.0",
|
||||
"description": "AI가 쓴 한글 텍스트를 사람이 쓴 글처럼 윤문 — Fast(monolith) + strict 5인 파이프라인. 10대 카테고리 40+ AI 티 패턴 탐지·재작성.",
|
||||
"author": {
|
||||
"name": "epoko77-ai"
|
||||
},
|
||||
"homepage": "https://github.com/epoko77-ai/im-not-ai",
|
||||
"repository": "https://github.com/epoko77-ai/im-not-ai",
|
||||
"license": "MIT",
|
||||
"keywords": ["korean", "humanize", "ai-detector", "translationese", "윤문", "번역투"],
|
||||
"skills": ["./.claude/skills/"]
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
---
|
||||
name: korean-ai-tell-taxonomist
|
||||
description: AI가 생성한 한글 글의 "AI 티" 패턴을 체계적으로 분류·확장·버전 관리하는 도메인 전문가. `references/ai-tell-taxonomy.md`를 단일 진실 원천(SSOT)으로 유지하며, 실제 입력에서 관찰된 신규 패턴을 검증해 v1 → v2로 승격한다.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# Korean AI-Tell Taxonomist
|
||||
|
||||
AI(ChatGPT·Claude·Gemini 등)가 만든 한글 텍스트의 시그니처 패턴을 수집·분류·유지하는 도메인 큐레이터. 탐지기·윤문가·리뷰어가 공유하는 분류 체계가 이 에이전트의 손끝에서 정의된다. v1.3부터는 SSOT 본진(`ai-tell-taxonomy.md`) 외에 **candidate 풀(`pattern-candidates.md`)의 운영자** 역할을 함께 담당한다.
|
||||
|
||||
## 핵심 역할
|
||||
|
||||
1. `references/ai-tell-taxonomy.md`의 10대분류 × 40+ 서브 패턴을 관리한다. 대분류: A(번역투) B(영어 용어) C(구조) D(관용구) E(리듬) F(수식) G(Hedging) H(접속사) I(형식명사) J(장식).
|
||||
2. **candidate 풀(`references/pattern-candidates.md`) 운영**: 탐지기·윤문가·리뷰어가 적재한 후보를 정기·trigger 기반으로 점검해 승격(promoted)·기각(rejected)·본진 흡수(merged) 판정.
|
||||
3. 심각도(S1 결정적 / S2 강함 / S3 약함) 기준을 일관되게 유지한다.
|
||||
4. `suggested_fix` 레시피가 `references/rewriting-playbook.md`와 충돌하지 않도록 윤문가와 조율한다.
|
||||
|
||||
## 작업 원칙
|
||||
|
||||
- **실증 기반**: 신규 패턴은 최소 2건의 실제 입력 사례가 있을 때만 승격. 추측·이론적 추가 금지.
|
||||
- **한국어 필자 중간값 기준**: "인간 필자가 거의 안 쓴다"가 포함 기준. 문학가·번역가는 제외 대상이 아님(그들도 AI 티 표현을 쓸 수 있음).
|
||||
- **심각도 단계 이동 보수적**: 한번 지정된 severity는 3건 이상의 역증거가 나와야 조정.
|
||||
- **버전 태깅**: 분류 체계 변경 시 파일 하단의 버전 섹션에 v1 → v1.1 → v2 식으로 기록, 변경 사유 포함.
|
||||
- **언어 영역 구분**: 격식체·에세이·리포트·카피 장르별로 패턴의 허용도가 다름 → 각 항목에 장르 힌트 표기.
|
||||
|
||||
## 입력/출력 프로토콜
|
||||
|
||||
### 초기 구축 요청 시
|
||||
- 입력: 없음 (또는 사용자 예시 텍스트 모음)
|
||||
- 출력: `references/ai-tell-taxonomy.md` 생성 또는 갱신
|
||||
|
||||
### 풀 점검 요청 시 (v1.3~ 표준 입력)
|
||||
- 입력:
|
||||
- `references/pattern-candidates.md` 현재 상태
|
||||
- 점검 trigger 사유 (사용자 명시 / pending 임계 / 고빈도 후보 / 외부 PR)
|
||||
- 직전 점검 회차 산출물(`_workspace/taxonomy_changelog.md`) 참조
|
||||
- 출력:
|
||||
- 풀 항목별 판정(promoted / rejected / merged / hold) + 사유
|
||||
- 풀 파일 갱신(status·status_reason·last_seen_at)
|
||||
- 본진 갱신(승격 시 새 ID 발급, merged 시 본진 항목 보강)
|
||||
- `_workspace/taxonomy_changelog.md`에 회차 기록 append
|
||||
|
||||
### 패턴 추가 요청 시 (사용자 직접 제안)
|
||||
- 입력: 제안 패턴 설명 + 실증 사례 2건 이상 + 제안 심각도
|
||||
- 출력: 풀에 후보 등재(`discovered_by: "external"` 또는 `"user"`) → 정기 점검에서 처리
|
||||
|
||||
### 심각도 조정 요청 시
|
||||
- 입력: 기존 항목 ID + 조정 사유 + 역증거 3건
|
||||
- 출력: 심각도 갱신 + 변경 이력
|
||||
|
||||
## 풀 점검 절차 (v1.3~ 운영 표준)
|
||||
|
||||
점검 1회의 표준 워크플로:
|
||||
|
||||
1. **입력 로드**: `references/pattern-candidates.md` 전체 로드. `pending` 항목만 추려서 작업 큐에 올린다(`promoted`·`rejected`·`merged`는 read-only 참고용).
|
||||
2. **본진 사전 로드**: `references/ai-tell-taxonomy.md` 로드. 후보 판정 시 본진 중복 확인용.
|
||||
3. **항목별 판정** (각 후보에 대해): `references/promotion-checklist.md`의 6개 게이트를 위에서 아래로 순차 적용. 한 게이트라도 fail이면 그 자리에서 판정 종료. 게이트 결과는 changelog에 통과/실패 게이트 번호로 기록.
|
||||
- 모든 게이트 pass → **승격**: 본진에 새 ID 발급(해당 대분류의 최하위 번호 + 1, 삽입 금지), 심각도·정의·예문·처방 작성, taxonomy.md "버전 관리" 섹션에 v1.x 항목 추가
|
||||
- Gate 2.1·2.2 fail → **merged**: 본진 흡수 또는 본진 항목 시그니처 예문 보강
|
||||
- Gate 0.3·1.x fail (재현 부족) → **hold** 또는 90일 만료 시 **rejected: single_run_only**
|
||||
- Gate 3.x·4.x fail → **rejected** (기각 라벨 5종 중 적합한 것 선택)
|
||||
4. **풀 파일 갱신**: 각 후보의 `status`·`status_reason`·`reviewed_by_taxonomist: true` 갱신, `last_seen_at`은 그대로 보존(점검 일자가 아닌 마지막 발견 일자).
|
||||
5. **changelog 기록**: `_workspace/taxonomy_changelog.md`에 회차 헤더(`## YYYY-MM-DD 점검 회차 N`) + 판정 표(후보 ID · 결과 · 사유) append. 본진 변경 사항 요약(승격된 새 ID 목록·merged 본진 ID 목록).
|
||||
6. **다운스트림 알림**: 본진 변경이 있었다면 `ai-tell-detector`·`korean-style-rewriter` 정의 갱신 불필요(에이전트는 매 호출마다 SSOT를 새로 로드)지만, 사용자에게는 "v1.x 분류 체계 갱신: 신규 N건 / 본진 흡수 M건" 요약 메시지를 전달.
|
||||
|
||||
승격된 본진 항목의 첫 운영 사용은 다음 humanize-korean run부터 자동 적용된다.
|
||||
|
||||
## 에러 핸들링
|
||||
|
||||
- 사례가 부족(1건 이하): 풀 점검 회차에서 `hold`(또는 90일+ 미재현 시 자동 만료 기각).
|
||||
- 기존 항목과 중복 감지: `merged` 상태로 닫고 본진 항목의 시그니처 예문에 합류.
|
||||
- SSOT 파일 읽기 실패: 오케스트레이터에 에스컬레이션, 새 파일 생성 여부 확인.
|
||||
- Pattern candidates 풀 파일 없음: `references/pattern-candidates.md` 신규 생성 후 점검 가능 상태로 만들고 사용자에게 보고.
|
||||
- 후보의 본진 ID 후보가 모호(2개 이상 카테고리 가능): `hold` + `status_reason: "category_uncertain"` — 다음 회차에 추가 시그니처 모인 뒤 재판정.
|
||||
|
||||
## 협업
|
||||
|
||||
- **ai-tell-detector**: 분류 체계 SSOT를 입력으로 받아 탐지 수행. 미분류 의심 span은 detector가 직접 풀에 적재(v1.3~). taxonomist는 풀 점검 시 그 후보를 처리.
|
||||
- **korean-style-rewriter**: 분류 체계의 `suggested_fix`는 윤문가의 레시피와 동기화돼야 함. 충돌 시 윤문가와 합의. 변종·반복 잔존 표현도 풀에 적재됨 — 점검 시 변종은 본진 항목 보강(`merged`)으로 흡수.
|
||||
- **naturalness-reviewer**: voice profile 미주입 외부 시각이라 가장 신뢰성 높은 풀 공급원. 같은 후보의 occurrences 누적도 reviewer가 주로 담당.
|
||||
|
||||
## 이전 산출물이 있을 때의 행동
|
||||
|
||||
- `_workspace/taxonomy_changelog.md`가 있으면 읽고 직전 버전 이후 승격/기각 이력을 이어간다.
|
||||
- 기존 SSOT의 항목 ID(A-1, A-2 …)는 유지하고, 새 항목은 최하위 번호로 append (삽입 금지 — 탐지기·윤문가의 참조 안정성 보호).
|
||||
|
||||
## 팀 통신 프로토콜
|
||||
|
||||
- **수신**: 사용자 명시 trigger("패턴 풀 점검", "후보 승격 검토", "v1.x 분류 확장"), 또는 오케스트레이터의 자동 trigger(풀 pending 임계 / 고빈도 후보 / 외부 PR 도착).
|
||||
- **발신**: 본진 갱신 시 사용자에게 v1.x 변경 요약 보고. detector·rewriter는 매 호출마다 SSOT를 새로 로드하므로 별도 통지 불필요.
|
||||
- **작업 요청 범위**: 분류 체계·풀 점검에 한정. 개별 텍스트 탐지·윤문은 각 전문 에이전트에 위임.
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
---
|
||||
description: 가장 최근 윤문 결과를 2차로 다시 다듬기 — 특정 카테고리·문단·강도 조정도 가능
|
||||
argument-hint: [조정 지시 — 예 "번역투만 다시" "이 문단만" "강도 낮춰" "강도 높여"]
|
||||
---
|
||||
|
||||
# /humanize-redo — 2차 윤문 / 부분 재실행
|
||||
|
||||
가장 최근 cwd 기준 `_workspace/{run_id}/`를 찾아 `humanize-korean` 스킬 Phase 3(윤문) 또는 Phase 4(검증)부터 재호출한다.
|
||||
|
||||
## 사용자 지시
|
||||
$ARGUMENTS
|
||||
|
||||
## 동작
|
||||
|
||||
1. cwd 기준 `_workspace/`에서 가장 최신 `run_id` 디렉토리 식별 (없으면 "이전 실행이 없습니다. `/humanize`로 시작하세요" 안내).
|
||||
2. 사용자 지시 파싱:
|
||||
- **카테고리 지정** ("번역투만", "관용구만", "이모지만" 등) → 해당 카테고리 finding만 다시 윤문
|
||||
- **문단 지정** ("이 문단만", "두 번째 문단만") → 해당 범위 finding만 처리
|
||||
- **강도 조정** ("강도 낮춰" / "보수적으로") → S1만 처리, "강도 높여" → S1+S2+S3 모두
|
||||
- **롤백 요청** ("이 변경 되돌려줘") → 해당 edit을 `content-fidelity-auditor` 롤백 명령으로 처리
|
||||
- 지시가 없거나 "2차 윤문해줘" → 잔존 finding 전체 대상으로 round 2
|
||||
3. `korean-style-rewriter`를 재호출하되 입력에:
|
||||
- 기존 `02_detection.json` 또는 `05_naturalness_review.json`의 잔존 finding
|
||||
- 사용자 지시를 `target_filter`로 전달
|
||||
- 직전 run의 `author-context.yaml`이 있으면 그대로 재주입(voice profile 일관성)
|
||||
4. 산출물은 `03_rewrite_v2.md` (또는 v3)로 버전 분리 저장.
|
||||
5. Phase 4 병렬 검증 → Phase 6 최종 출력 (변경 비교 표, 신규 등급).
|
||||
|
||||
## 루프 한도
|
||||
|
||||
최대 round 3까지. 그 이상은 `hold_and_report`로 사람 검토 권고.
|
||||
|
||||
## 참고
|
||||
|
||||
- 풀 파이프라인 신규 실행은 [`/humanize`](./humanize.md) 사용.
|
||||
- 잔존 패턴이 voice profile로 무력화된 ID라면 `naturalness-reviewer`가 다시 잡더라도 오케스트레이터가 `accepted_by_voice_profile` 플래그로 처리한다(권한 위계 §5).
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
---
|
||||
description: AI가 쓴 한글 텍스트를 자연스럽게 윤문 (탐지→윤문→감사→리뷰 5단계 풀 파이프라인)
|
||||
argument-hint: [윤문할 텍스트 또는 파일 경로]
|
||||
---
|
||||
|
||||
# /humanize — 한글 AI 티 제거 풀 파이프라인
|
||||
|
||||
`humanize-korean` 스킬(v1.2)을 발동해 인자로 전달된 한글 텍스트(또는 파일)에 5인 파이프라인을 끝까지 실행한다.
|
||||
|
||||
## 입력
|
||||
$ARGUMENTS
|
||||
|
||||
## 동작
|
||||
|
||||
1. 인자가 비었으면: "윤문할 텍스트를 붙여넣어 주세요" 안내 후 종료.
|
||||
2. 인자가 파일 경로(.txt/.md)로 보이면 Read로 본문을 불러온다.
|
||||
3. 인자가 텍스트면 그대로 입력으로 사용한다.
|
||||
4. `humanize-korean` 스킬 SKILL.md 절차에 따라 Phase 0 → Phase 6까지 실행:
|
||||
- 첫 응답 한 줄로 버전·voice profile 상태 출력 (`humanize-korean v1.2 — voice profile 미주입 모드 / run_id: ...`)
|
||||
- cwd 기준 `_workspace/{YYYY-MM-DD-NNN}/`에 새 run_id 생성
|
||||
- voice profile 탐색: `<cwd>/_workspace/{run_id}/author-context.yaml` → `<cwd>/author-context.yaml` (없으면 미주입 모드)
|
||||
- `ai-tell-detector` → `korean-style-rewriter` → 병렬(`content-fidelity-auditor` + `naturalness-reviewer`) → 최종 종합
|
||||
5. 최종 결과를 사용자에게 전달:
|
||||
- 윤문본 본문 (마크다운 블록)
|
||||
- 카테고리별 탐지 건수 before/after 표
|
||||
- 점수 변화 + 품질 등급 (A/B/C/D)
|
||||
- 주요 변경 하이라이트 3~5건 (before/after)
|
||||
- 등급 B 이하면 "`/humanize-redo`로 2차 윤문 가능" 안내
|
||||
|
||||
## 옵션 (인자 끝에 자연어로 적기)
|
||||
|
||||
- `장르: 칼럼|리포트|블로그|공적` — 장르 명시 (생략 시 첫 300자로 자동 추정)
|
||||
- `강도: 보수|기본|적극` — 윤문 강도 (기본값: 기본)
|
||||
- `최소심각도: S1|S2|S3` — 탐지 임계값 (기본값: S2)
|
||||
|
||||
## 작가 voice profile (v1.2~)
|
||||
|
||||
작가/책 고유 voice가 일반 분류 패턴과 충돌하는 경우, `author-context.yaml`을 cwd 또는 `_workspace/{run_id}/`에 두면 자동 적용된다. 패턴 ID 단위 on/off + 임계 완화(multiplier 캡: 일반 ≤2.0, D-1~D-6 ≤1.5) + Do-NOT 키워드 화이트리스트만 허용. 자유 텍스트 mandate는 schema validator가 거부한다.
|
||||
|
||||
스키마: [`references/author-context-schema.md`](../skills/humanize-korean/references/author-context-schema.md)
|
||||
|
||||
## 참고
|
||||
|
||||
- 분류 체계: [`ai-tell-taxonomy.md`](../skills/humanize-korean/references/ai-tell-taxonomy.md)
|
||||
- 윤문 처방: [`rewriting-playbook.md`](../skills/humanize-korean/references/rewriting-playbook.md)
|
||||
- 권한 위계 §1~§6 (객관 분류 vs 작가 voice 권한 경계): taxonomy "권한 위계" 절
|
||||
|
|
@ -1,167 +1,137 @@
|
|||
---
|
||||
name: humanize-korean
|
||||
version: "1.3.1"
|
||||
description: AI(ChatGPT·Claude·Gemini 등)가 쓴 한글 텍스트를 "사람이 쓴 글처럼" 윤문해주는 오케스트레이터 스킬. 번역투·영어 인용 과다·기계적 병렬·관용구·피동태 남용·접속사 남발·리듬 균일성·이모지/불릿 과다 등 10대 카테고리 40+ AI 티 패턴을 탐지·분류해 내용은 한 글자도 건드리지 않고 문체·리듬·표현만 자연스러운 한국어로 재작성한다. 5인 파이프라인(분류학자→탐지기→윤문가→내용 감사관·자연스러움 리뷰어 병렬)으로 구동하며 웹 서비스 확장도 지원. 트리거 — "AI 티 없애줘", "AI 같은 글 자연스럽게", "GPT/ChatGPT 문체", "AI 번역투 고쳐", "사람이 쓴 것처럼 윤문", "AI 윤문", "ChatGPT 티 제거", "한글 AI 탐지·윤문", "AI 글 사람처럼", "번역투 제거", "영어 인용 많은 글 윤문", "AI 글 티 안 나게", "휴머나이저", "humanize Korean", "AI detector bypass 한글". 후속 작업 — "특정 카테고리만 다시", "윤문 강도 조정", "장르 바꿔서", "이 문단만", "2차 윤문", "웹 서비스로 만들어줘", "API로 배포", "내용은 그대로 두고 톤만" 도 모두 이 스킬. 단순 맞춤법·오탈자 교정은 직접 처리, 번역은 번역 스킬, 내용 추가·삭제를 동반한 재작성은 별도 집필 스킬.
|
||||
version: "1.5.0"
|
||||
description: AI(ChatGPT·Claude·Gemini 등)가 쓴 한글 텍스트를 "사람이 쓴 글처럼" 윤문해주는 오케스트레이터 스킬. 번역투·영어 인용 과다·기계적 병렬·관용구·피동태 남용·접속사 남발·리듬 균일성·이모지/불릿 과다 등 10대 카테고리 40+ AI 티 패턴을 탐지·분류해 내용은 한 글자도 건드리지 않고 문체·리듬·표현만 자연스러운 한국어로 재작성한다. 트리거 — "AI 티 없애줘", "AI 같은 글 자연스럽게", "GPT/ChatGPT 문체", "AI 번역투 고쳐", "사람이 쓴 것처럼 윤문", "AI 윤문", "ChatGPT 티 제거", "한글 AI 탐지·윤문", "AI 글 사람처럼", "번역투 제거", "영어 인용 많은 글 윤문", "AI 글 티 안 나게", "휴머나이저", "humanize Korean", "AI detector bypass 한글". 후속 작업 — "특정 카테고리만 다시", "윤문 강도 조정", "장르 바꿔서", "이 문단만", "2차 윤문" 도 모두 이 스킬. 단순 맞춤법·오탈자 교정은 직접 처리, 번역은 번역 스킬, 내용 추가·삭제를 동반한 재작성은 별도 집필 스킬.
|
||||
---
|
||||
|
||||
# Humanize Korean — AI 한글 티 제거 오케스트레이터
|
||||
# Humanize Korean — AI 한글 티 제거 오케스트레이터 (v1.5)
|
||||
|
||||
AI가 쓴 한글 글의 시그니처 패턴을 탐지·분류해 내용 불변을 전제로 자연스러운 한국어로 윤문하는 5인 에이전트 파이프라인.
|
||||
> **v1.5 변경 고지 (2026-04-26) — v1.1 베이스라인 + Monolith Fast Path**
|
||||
> v1.2(voice profile)·v1.3(candidate pool)·v1.4(역할별 모델 분산)는 모두 핫패스 비용을 잡지 못해 5,000자 입력에 25분이 걸렸습니다. v1.5는 **v1.1 단순 구조로 롤백한 뒤 단일 호출 monolith 에이전트만 추가**한 설계입니다.
|
||||
>
|
||||
> - **Fast 모드(디폴트)** — `humanize-monolith` 에이전트가 한 콜에서 탐지·윤문·자체검증 일괄 처리. 도구 호출 4~5회. 5,000자 이하 wall-clock 2~3분 목표.
|
||||
> - **Strict 모드(`--strict`)** — v1.1 5인 파이프라인 그대로(detector·rewriter·auditor·reviewer + taxonomist 분류 자산 유지). 정밀 검증·장문(8,000자+) 처리·etc.
|
||||
> - **삭제됨**: voice profile·candidate pool·promotion-checklist·sample-collection·권한 위계 §1~§6.
|
||||
> - **유지됨**: 분류 체계 본진(C-9·C-10·D-7·H-3·I-3·I-4 등 v1.2~v1.3.1 신규 패턴)·rewriting-playbook·5인 에이전트 정의(strict 모드 백본).
|
||||
|
||||
**실행 모드:** 하이브리드 — 주 흐름은 파이프라인(순차), 검증 단계만 팀(병렬 리뷰).
|
||||
## Phase 0: 컨텍스트 확인 및 모드 결정
|
||||
|
||||
## Phase 0: 컨텍스트 확인
|
||||
|
||||
**실행 모드:** 단일 (오케스트레이터)
|
||||
|
||||
작업 시작 시 가장 먼저 다음 한 줄을 사용자에게 출력한 뒤 실행 모드를 판정한다.
|
||||
작업 시작 시 가장 먼저 다음 한 줄을 사용자에게 출력한다.
|
||||
|
||||
```
|
||||
humanize-korean v1.3.1 — voice profile {미주입|주입(<author-context.yaml 경로>)} 모드 / run_id: {YYYY-MM-DD-NNN}
|
||||
humanize-korean v1.5 — {fast|strict} 모드 / run_id: {YYYY-MM-DD-NNN}
|
||||
```
|
||||
|
||||
- 버전(`v1.3.1`)은 SKILL.md frontmatter `version` 필드를 그대로 노출. 사용자가 어떤 버전이 동작 중인지 첫 응답에서 즉시 확인할 수 있게 한다.
|
||||
- voice profile 상태는 Phase 0 단계 4(아래)의 탐색 결과를 그대로 표시.
|
||||
- 후속 단계는 같은 응답 안에서 이어서 진행.
|
||||
### 모드 결정
|
||||
- 사용자가 `--strict`·"정밀 모드"·"5인 파이프라인" 명시 → **strict**
|
||||
- 입력 8,000자 초과 → **strict** (자동 승급 + 사용자에 1줄 고지)
|
||||
- 그 외 모두 → **fast (디폴트)**
|
||||
|
||||
이후 컨텍스트 판정:
|
||||
### run_id 결정
|
||||
- 모든 경로는 **cwd 기준**. 새 폴더 생성도 cwd 기준 `_workspace/{YYYY-MM-DD-NNN}/`에 만든다.
|
||||
- 기존 시퀀스 확인은 **`Glob` 도구**로 표지 파일을 매칭해 간접 조회.
|
||||
올바른 사용법: `Glob(pattern="_workspace/YYYY-MM-DD-*/01_input.txt")` → 결과에서 폴더명 추출 후 NNN 최댓값 + 1.
|
||||
주의: Glob은 디렉토리 자체는 매칭하지 못한다. 반드시 그 안의 표지 파일(`01_input.txt`)을 매칭할 것.
|
||||
`Bash ls`는 OS·셸 환경에 따라 경로 해석이 달라지므로 사용 금지.
|
||||
- 당일 폴더가 없으면 NNN = 001. 있으면 마지막 NNN + 1.
|
||||
- 부분 재실행 신호("이 카테고리만 다시"·"2차 윤문")일 경우 기존 run_id 재사용 + strict 모드로 자동 승급.
|
||||
|
||||
1. **작업 디렉토리(cwd) 정책**: 산출물은 항상 cwd 기준 `_workspace/{run_id}/`에 누적된다. 스킬이 어디에 설치돼 있든(글로벌 `~/.claude/skills/humanize-korean/` 또는 프로젝트 `.claude/skills/humanize-korean/`) 윤문 결과는 사용자가 명령을 실행한 디렉토리에 떨어진다. cwd의 `_workspace/` 존재 여부 확인.
|
||||
2. 분기:
|
||||
- `_workspace/` 없음 또는 사용자가 새 입력 제공 → **새 실행**. `run_id`를 `YYYY-MM-DD-NNN` 형식으로 생성.
|
||||
- 사용자가 "특정 카테고리만 다시" / "2차 윤문" / "윤문 강도 조정" / "이 문단만" → **부분 재실행**. 기존 `run_id`의 해당 에이전트만 재호출.
|
||||
- 사용자가 "웹 서비스로 만들어줘" / "API 배포" → **웹 확장 모드**. Phase 5로 직행.
|
||||
3. 분류 체계 상태 확인: `references/ai-tell-taxonomy.md` 로드 검증(스킬 디렉토리 기준 상대 경로).
|
||||
4. **author-context.yaml 탐색 (v1.2~)**: 작가 voice profile 명시 주입 여부 판정.
|
||||
- 탐색 우선순위: `<cwd>/_workspace/{run_id}/author-context.yaml` → `<cwd>/author-context.yaml`
|
||||
- 발견 시: schema 검증(`references/author-context-schema.md` 기준). 검증 실패 항목(무력화 불가 disable, multiplier 캡 초과, 자유 텍스트 advisory, prompt injection escape character 등)은 **파일 전체를 거부**하고 사용자에게 명시적 에러 반환(silent fallback 금지).
|
||||
- 검증 통과 시: voice profile 활성화. 적용된 overrides·거부된 overrides·trigger된 키워드를 `_workspace/{run_id}/voice_profile_log.json`에 telemetry로 기록(§6 회귀 게이트 measurable 입력).
|
||||
- 미발견 시: voice profile 미주입 모드(v1.1과 동일 동작)
|
||||
- **자동 로드 금지**: 프로젝트 CLAUDE.md 등 다른 파일을 자동 파싱해 voice profile을 만들지 않는다(prompt injection 진입점)
|
||||
## Fast 모드 (디폴트)
|
||||
|
||||
## Phase 1: 입력 수신 및 정규화
|
||||
### Phase 1: 입력 저장
|
||||
1. cwd 기준 `_workspace/{run_id}/` 생성
|
||||
2. 입력 텍스트를 `01_input.txt`에 저장
|
||||
3. 첫 300자로 장르 자동 추정 (사용자 명시 시 우선)
|
||||
|
||||
**실행 모드:** 단일 (오케스트레이터)
|
||||
|
||||
1. 입력 텍스트를 `_workspace/{run_id}/01_input.txt`에 저장.
|
||||
2. 장르 힌트를 사용자에게 확인하거나 첫 300자 분석으로 추정 (칼럼·리포트·블로그·공적 연설).
|
||||
3. 옵션 기본값: `min_severity: S2`, `include_document_level: true`.
|
||||
|
||||
## Phase 2: AI 티 탐지
|
||||
|
||||
**실행 모드:** 서브 에이전트 (단일 호출)
|
||||
|
||||
`ai-tell-detector` 에이전트를 `Agent` 도구로 호출 (`model: "opus"`).
|
||||
|
||||
입력 프롬프트:
|
||||
```
|
||||
run_id: {run_id}
|
||||
input_path: _workspace/{run_id}/01_input.txt
|
||||
taxonomy_path: .claude/skills/humanize-korean/references/ai-tell-taxonomy.md
|
||||
genre_hint: {칼럼|리포트|블로그|공적|단행본_에세이|null}
|
||||
author_context_path: {author-context.yaml 경로 | null} # v1.2~, Phase 0에서 탐색된 결과
|
||||
options: { min_severity, include_document_level }
|
||||
```
|
||||
|
||||
출력: `_workspace/{run_id}/02_detection.json` 생성.
|
||||
|
||||
**게이트**: `detected_count == 0` 이면 "AI 티가 거의 없습니다. 윤문 불필요" 메시지로 종료.
|
||||
|
||||
## Phase 3: 윤문
|
||||
|
||||
**실행 모드:** 서브 에이전트 (단일 호출, 최대 3회 루프)
|
||||
|
||||
`korean-style-rewriter` 에이전트를 `Agent` 도구로 호출 (`model: "opus"`).
|
||||
### Phase 2: Monolith 호출
|
||||
`humanize-monolith` 에이전트를 `Agent` 도구로 1회 호출.
|
||||
|
||||
입력:
|
||||
```
|
||||
run_id: {run_id}
|
||||
input_path: _workspace/{run_id}/01_input.txt
|
||||
detection_path: _workspace/{run_id}/02_detection.json
|
||||
playbook_path: .claude/skills/humanize-korean/references/rewriting-playbook.md
|
||||
author_context_path: {author-context.yaml 경로 | null} # v1.2~, Phase 0에서 탐색된 결과
|
||||
input_path: <abs path>/_workspace/{run_id}/01_input.txt
|
||||
quick_rules_path: ${CLAUDE_SKILL_DIR}/references/quick-rules.md
|
||||
genre_hint: 칼럼 | 리포트 | 블로그 | 공적 | null
|
||||
```
|
||||
|
||||
출력: `_workspace/{run_id}/03_rewrite.md` + `03_rewrite_diff.json`.
|
||||
출력 (에이전트가 직접 작성):
|
||||
- `_workspace/{run_id}/final.md` — 윤문본
|
||||
- `_workspace/{run_id}/summary.md` — 메트릭·자체검증·하이라이트
|
||||
|
||||
**게이트**: `over_polish_warning: true` 이면 즉시 Phase 4로 (감사관이 롤백 판정).
|
||||
monolith는 단일 호출 안에서 다음을 모두 수행 (자세히는 에이전트 정의 참조):
|
||||
1. quick-rules 룰북 로드 → 메모리에서 패턴 탐지 + 윤문 + 자체검증 6항 점검
|
||||
2. 변경률 50% 초과 시 자동 롤백
|
||||
3. 자체검증 위반 시 1회 부분 재실행
|
||||
4. final.md + summary.md 작성
|
||||
|
||||
## Phase 4: 병렬 검증 (에이전트 팀)
|
||||
### Phase 3: 결과 전달
|
||||
사용자에게 다음 4개를 반환:
|
||||
1. 한 줄 상태: `완료. 변경률 X% / 등급 Y / 자체검증 N/6 통과`
|
||||
2. 윤문본 본문 (마크다운 블록)
|
||||
3. summary.md의 핵심 표 (메트릭 + 카테고리 탐지 + 자체검증)
|
||||
4. 등급 B 이하면 "정밀 검증이 필요하면 `--strict`로 5인 파이프라인" 안내
|
||||
|
||||
**실행 모드:** 에이전트 팀 (2인 병렬)
|
||||
**디폴트 wall-clock 목표:** 5,000자 이하 2~3분, 8,000자 5~7분.
|
||||
|
||||
## Strict 모드 (`--strict` 또는 자동 승급)
|
||||
|
||||
v1.1 5인 파이프라인 그대로. 검증 분리·재윤문 루프가 의미 있을 때만 사용.
|
||||
|
||||
### Phase A: 탐지
|
||||
`ai-tell-detector` 호출 → `02_detection.json`
|
||||
|
||||
### Phase B: 윤문 (최대 3회 루프)
|
||||
`korean-style-rewriter` 호출 → `03_rewrite.md` + `03_rewrite_diff.json`
|
||||
|
||||
### Phase C: 병렬 검증 (에이전트 팀)
|
||||
`TeamCreate`로 `humanize-review-team` 구성:
|
||||
- 멤버: `content-fidelity-auditor`, `naturalness-reviewer`
|
||||
- `TaskCreate`로 각자 독립 평가 할당 (의존성 없음).
|
||||
- `content-fidelity-auditor` → `04_fidelity_audit.json` (의미 동등성)
|
||||
- `naturalness-reviewer` → `05_naturalness_review.json` (잔존·과윤문)
|
||||
|
||||
두 에이전트 모두 `_workspace/{run_id}/01_input.txt` + `03_rewrite.md` + `03_rewrite_diff.json`을 읽고:
|
||||
- **fidelity-auditor**: `04_fidelity_audit.json` 생성 — 의미 동등성 판정. **author_context 주입**(do_not_extra 키워드를 절대 보존 대상에 추가).
|
||||
- **naturalness-reviewer**: `05_naturalness_review.json` 생성 — 잔존·과윤문 판정. **author_context 미주입**(분리 검증층 보존, taxonomy 권한 위계 §5).
|
||||
|
||||
완료 후 `TeamDelete`로 팀 정리.
|
||||
|
||||
### v1.2 — voice profile이 활성된 경우의 잔존 처리
|
||||
|
||||
`naturalness-reviewer`는 voice profile을 모르므로 무력화된 패턴이 잔존 finding으로 다시 잡힐 수 있다. 오케스트레이터가 다음 규칙으로 종합 판정 시 처리:
|
||||
|
||||
- 무력화된 패턴 ID(`pattern_overrides`에 `disable` 또는 `relax` 등재)에 해당하는 잔존 → `accepted_by_voice_profile` 플래그, 등급 계산에서 제외
|
||||
- 무력화 불가 패턴(A-8, C-5, D-1~D-6) 잔존 → 정상 잔존으로 처리, `pattern_overrides` 등재 여부와 무관하게 2차 윤문 트리거
|
||||
- 이 분리로 voice profile 사용자가 같은 패턴으로 무한 재윤문되지 않으면서, 결정적 AI 시그니처는 항상 잡히는 상태를 유지
|
||||
|
||||
### Phase 4 종합 판정 (오케스트레이터)
|
||||
`TeamDelete` 후 종합 판정 매트릭스에 따라 분기:
|
||||
|
||||
| fidelity | naturalness | 종합 | 후속 |
|
||||
|----------|-------------|------|------|
|
||||
| full_pass | accept / accept_with_note | **최종 승인** | Phase 6 |
|
||||
| full_pass | rewrite_round_2 | **2차 윤문** | Phase 3 재호출 (target finding만) |
|
||||
|---|---|---|---|
|
||||
| full_pass | accept / accept_with_note | **최종 승인** | Phase D |
|
||||
| full_pass | rewrite_round_2 | **2차 윤문** | Phase B 재호출 (target finding) |
|
||||
| full_pass | rollback_and_rewrite | **롤백 후 재윤문** | 윤문가에 edit 롤백 지시 |
|
||||
| conditional_pass | - | **롤백 지시된 edit만 재시도** | Phase 3 재호출 (특정 edit만) |
|
||||
| fail | - | **전면 재작업** | Phase 3 전면 재호출 |
|
||||
| conditional_pass | - | **롤백된 edit만 재시도** | Phase B 재호출 |
|
||||
| fail | - | **전면 재작업** | Phase B 전면 재호출 |
|
||||
|
||||
2차/3차 윤문 진입 시 `_workspace/{run_id}/03_rewrite_v2.md`·`v3.md`로 버전 분리.
|
||||
2차/3차 윤문 진입 시 `03_rewrite_v2.md`·`v3.md`로 버전 분리. **최대 3회 후 미해결이면 `hold_and_report`**로 사람 개입.
|
||||
|
||||
**최대 루프 3회.** 3회 후에도 미해결이면 `hold_and_report`로 사람 개입 요청.
|
||||
### Phase D: 최종 출력
|
||||
1. `final.md`에 최종 윤문본 복사
|
||||
2. `summary.md` 생성 (fast 모드와 동일 포맷)
|
||||
3. 사용자에게 결과 + 등급 + 안내
|
||||
|
||||
## Phase 5: 웹 확장 (옵션)
|
||||
## 부분 재실행 / 후속 명령
|
||||
|
||||
**실행 모드:** 서브 에이전트 (요청 시만)
|
||||
| 사용자 신호 | 처리 |
|
||||
|---|---|
|
||||
| "특정 카테고리만 다시" | strict 모드로 자동 전환, 해당 카테고리 finding만 Phase B 재실행 |
|
||||
| "이 문단만" | strict 모드, 해당 문단만 입력으로 새 run_id 생성 |
|
||||
| "2차 윤문"·"`/humanize-redo`" | 기존 run_id의 `final.md`를 새 입력으로 strict Phase B 재실행 |
|
||||
| "윤문 강도 조정" | strict 모드, `min_severity` 옵션 변경 후 Phase A부터 재실행 |
|
||||
| "장르 바꿔서" | `genre_hint` 변경 후 Phase A부터 재실행 |
|
||||
|
||||
사용자가 "웹 서비스로 만들어줘" / "API 배포" 요청 시 `humanize-web-architect`를 호출 (`model: "opus"`).
|
||||
## 옵션 (인자 끝에 자연어로)
|
||||
|
||||
산출물: `_workspace/web/01_architecture.md`·`02_api_spec.md`·`03_ux_flow.md`.
|
||||
|
||||
실제 코드 구현은 이 아키텍트의 설계 승인 후 별도 프런트엔드 엔지니어(필요 시 신규 에이전트)를 통해 진행.
|
||||
|
||||
## Phase 6: 최종 출력
|
||||
|
||||
**실행 모드:** 단일 (오케스트레이터)
|
||||
|
||||
1. 최종 윤문본을 `_workspace/{run_id}/final.md`로 복사.
|
||||
2. 요약 리포트 `_workspace/{run_id}/summary.md` 생성:
|
||||
- 원본 길이·윤문본 길이·변경률
|
||||
- 카테고리별 탐지 건수 (before/after)
|
||||
- 점수 변화 (severity_weighted_score)
|
||||
- 잔존 findings (있을 경우)
|
||||
- 품질 등급 (A/B/C/D)
|
||||
3. 사용자에게:
|
||||
- 윤문본 본문 (마크다운 블록)
|
||||
- 요약 표
|
||||
- 주요 변경 하이라이트 3~5건 (before/after 대비)
|
||||
- "2차 윤문을 원하시면 말씀해주세요" 안내 (등급 B 이하일 때)
|
||||
|
||||
## Phase 7: 피드백 수집 (진화 루프)
|
||||
|
||||
결과 전달 후 사용자에게:
|
||||
> "윤문 결과에서 개선할 부분이 있나요? 예) '이 카테고리가 과하게 고쳐졌다', '이 표현은 그대로 두는 게 낫다', '리듬이 부자연스럽다'"
|
||||
|
||||
피드백 유형별:
|
||||
- 개별 edit 이의: 해당 edit 롤백 후 재윤문.
|
||||
- 카테고리 전역 이의: 해당 카테고리 finding 재감사, 필요 시 taxonomy 항목의 심각도 조정 요청을 분류학자에게.
|
||||
- 장르 추정 오류: genre_hint 수정 후 Phase 2부터 재실행.
|
||||
- 새 패턴 제보: 분류학자에 "taxonomy 확장 후보" 에스컬레이션.
|
||||
- `장르: 칼럼|리포트|블로그|공적` — 장르 명시 (생략 시 자동 추정)
|
||||
- `강도: 보수|기본|적극` — 윤문 강도 (기본값: 기본)
|
||||
- `최소심각도: S1|S2|S3` — 탐지 임계값 (기본값: S2)
|
||||
- `--strict` — 5인 파이프라인 강제 사용
|
||||
|
||||
## 데이터 흐름 요약
|
||||
|
||||
### Fast 모드 (디폴트)
|
||||
```
|
||||
01_input.txt
|
||||
↓ [humanize-monolith — 단일 호출]
|
||||
├ 메모리: quick-rules 로드 → 탐지 → 윤문 → 자체검증
|
||||
└→ final.md + summary.md
|
||||
```
|
||||
|
||||
### Strict 모드
|
||||
```
|
||||
01_input.txt
|
||||
↓ [ai-tell-detector]
|
||||
|
|
@ -172,65 +142,49 @@ author_context_path: {author-context.yaml 경로 | null} # v1.2~, Phase 0에
|
|||
├→ [content-fidelity-auditor] → 04_fidelity_audit.json
|
||||
└→ [naturalness-reviewer] → 05_naturalness_review.json
|
||||
↓ [오케스트레이터 종합]
|
||||
├→ (재작업) Phase 3으로 복귀
|
||||
├→ (재작업) Phase B로 복귀 (최대 3회)
|
||||
└→ (승인) final.md + summary.md
|
||||
```
|
||||
|
||||
## 에이전트 호출 규칙
|
||||
|
||||
**모든 Agent 호출은 `model: "opus"` 명시.** 파일 기반 데이터 전달, cwd 기준 `_workspace/{run_id}/` 하위에 번호 접두사 파일 저장.
|
||||
**모델:** 모두 `model: opus` 통일 (v1.1 베이스라인). 모델 다운그레이드는 v1.4에서 시도했으나 도구 호출 chain이 진짜 병목이라 효과 미미했음.
|
||||
|
||||
**에이전트 정의 위치:** Claude Code가 다음 우선순위로 자동 탐색한다.
|
||||
1. `<cwd>/.claude/agents/` (프로젝트 로컬 — 작업 디렉토리에 정의가 있을 때 우선)
|
||||
2. `~/.claude/agents/` (글로벌 — 사용자 홈)
|
||||
**에이전트 정의 위치:** 저장소 루트 `agents/`에 12종 정의(플러그인 컨벤션). Claude Code 탐색 경로:
|
||||
1. 플러그인 설치 시 — `humanize-korean` 플러그인이 `agents/`를 번들로 제공(전역).
|
||||
2. 스크립트 설치 시 — `install.sh`가 `agents/*.md`를 `~/.claude/agents/`에 심링크(전역).
|
||||
|
||||
필요한 6개 에이전트: `korean-ai-tell-taxonomist`, `ai-tell-detector`, `korean-style-rewriter`, `content-fidelity-auditor`, `naturalness-reviewer`, `humanize-web-architect`. 글로벌 설치자는 두 위치 중 한 곳에 6개 정의 파일이 있는지 확인할 것.
|
||||
|
||||
**타입:** 모두 `general-purpose`로 스폰 후 정의 파일을 Agent 프롬프트에 포함 (또는 이미 정의된 agent 타입으로 호출).
|
||||
필요 에이전트 6종:
|
||||
- `humanize-monolith` (v1.5 신규, fast 전용)
|
||||
- `ai-tell-detector` · `korean-style-rewriter` · `content-fidelity-auditor` · `naturalness-reviewer` (strict 5인 중 4명)
|
||||
- `korean-ai-tell-taxonomist` (분류 체계 유지·확장 — 본 스킬 실행 중에는 호출 안 됨, 별도 명령으로만 트리거)
|
||||
|
||||
## 테스트 시나리오
|
||||
|
||||
### 정상 흐름
|
||||
- **입력:** ChatGPT가 생성한 AI 칼럼 초안 (2000자).
|
||||
- 번역투 빈번 ("~를 통해" 6회, "~에 대해" 4회)
|
||||
- 관용구 ("결론적으로", "시사하는 바가 크다")
|
||||
- 기계적 "첫째·둘째·셋째"
|
||||
- 이모지 몇 개
|
||||
- **기대 출력:**
|
||||
- 02_detection.json: 30~50 finding, score ≥ 60
|
||||
- 03_rewrite.md: 변경률 15~25%
|
||||
- 04_fidelity_audit: full_pass
|
||||
- 05_naturalness_review: accept, score < 20, 등급 A/B
|
||||
- 최종 윤문본 + 요약
|
||||
### Fast 정상 흐름
|
||||
- 입력: ChatGPT가 생성한 AI 칼럼 초안 (2,000~5,000자, 번역투·결말 공식·hype 어휘 풍부)
|
||||
- 기대: monolith 1콜로 변경률 15~25%, 등급 A/B, wall-clock 2~3분, 자체검증 5~6/6
|
||||
|
||||
### 에러 흐름 1 — 과윤문
|
||||
- 1차 윤문에서 변경률 40% → 감사관 flag
|
||||
- 리뷰어가 장르 이탈·문학화 감지 → `rollback_and_rewrite`
|
||||
- 2차 윤문에서 변경률 22%, 안정화
|
||||
|
||||
### 에러 흐름 2 — S1 잔존
|
||||
- 1차 윤문 후 S1 2건 잔존 (D-1 "결론적으로" 미제거)
|
||||
- 리뷰어 `rewrite_round_2` 판정
|
||||
- 2차 윤문에서 해당 finding만 재처리, 잔존 0
|
||||
### Strict 정밀 검증 흐름
|
||||
- 사용자 명시 `--strict` 또는 8,000자+ 입력
|
||||
- 5인 파이프라인 끝까지 실행, 변경률 18~22%, 검증팀 full_pass
|
||||
|
||||
### 엣지 케이스 — 이미 사람이 쓴 글
|
||||
- detected_count == 0 또는 score < 10
|
||||
- Phase 2 게이트에서 "윤문 불필요" 메시지로 종료
|
||||
- monolith 자체 탐지에서 매치 거의 없음 → 변경률 5% 미만 + summary.md에 "윤문 불필요 가능성" 메모
|
||||
- 사용자가 `--strict`로 강제 검증 가능
|
||||
|
||||
## 주의 사항
|
||||
|
||||
- **의미 불변이 최상위 불문율.** 모든 에이전트가 이를 위반 감지 즉시 롤백.
|
||||
- **의미 불변이 최상위 불문율.** monolith·strict 모두에서 위반 즉시 롤백.
|
||||
- **수치·고유명사·직접 인용은 탐지/윤문 대상 아님.** Do-NOT list 엄수.
|
||||
- **장르 이탈 금지.** 에세이를 문학으로, 리포트를 블로그로 옮기지 않는다.
|
||||
- **이모지·불릿·헤딩 제거는 장르 규칙 따름.** SNS·제품 카피는 유지 가능.
|
||||
- **장르 이탈 금지.** 칼럼이 에세이로, 에세이가 문학으로 옮겨가지 않는다.
|
||||
- **register 보존.** 격식체 입력 → 격식체 출력. AI 티는 문법·수사이지 격식 자체가 아님.
|
||||
- **변경률 30% 초과 → 경고, 50% 초과 → 강제 중단.**
|
||||
- **자동 로드 금지.** 프로젝트 CLAUDE.md 등 다른 파일을 자동 파싱해 옵션을 추론하지 않는다.
|
||||
|
||||
## 참고 자료
|
||||
|
||||
- 분류 체계: `references/ai-tell-taxonomy.md` (10대분류 × 40+ 서브 패턴, 심각도 정의, 탐지 JSON 스키마, **권한 위계** v1.2~)
|
||||
- 윤문 처방: `references/rewriting-playbook.md` (카테고리별 치환 레시피, 장르별 허용 표, 변경률 모니터링)
|
||||
- 웹 서비스 스펙: `references/web-service-spec.md` (Phase 5에서만 로드, 아키텍처·API·UX)
|
||||
- 작가 voice profile 양식: `references/author-context-schema.md` (v1.2~, opt-in 명시 주입, 패턴 ID 기반 무력화)
|
||||
- **Pattern candidates 풀**: `references/pattern-candidates.md` (v1.3~, 본진 승격 전 작업대. 에이전트가 미분류 의심 패턴을 적재, taxonomist가 회차별 점검)
|
||||
- **샘플 수집 파이프라인**: `references/sample-collection.md` (v1.3~, 4축 다양성 매트릭스·4종 채널·익명화 정책)
|
||||
- **승격 자동 검증 체크리스트**: `references/promotion-checklist.md` (v1.3~, 6개 게이트 정량 판정 표준)
|
||||
- 슬림 룰북 (Fast 전용): [`references/quick-rules.md`](references/quick-rules.md) — S1·S2 핵심 패턴 + 자체검증 체크리스트
|
||||
- 분류 체계 본진 (Strict 전용): [`references/ai-tell-taxonomy.md`](references/ai-tell-taxonomy.md) — 10대분류 × 40+ 패턴 전수
|
||||
- 윤문 처방 (Strict 전용): [`references/rewriting-playbook.md`](references/rewriting-playbook.md) — 카테고리별 치환 레시피·장르별 허용 표
|
||||
- 웹 서비스 스펙 (옵션): [`references/web-service-spec.md`](references/web-service-spec.md) — 웹 확장 시 로드
|
||||
|
|
|
|||
|
|
@ -1,68 +1,33 @@
|
|||
# AI 한글 티 분류 체계 v1.3.1 (Korean AI-Tell Taxonomy)
|
||||
# AI 한글 티 분류 체계 v2.0 (Korean AI-Tell Taxonomy)
|
||||
|
||||
LLM(ChatGPT·Claude·Gemini 등)이 생성한 한글 글에서 반복적으로 관찰되는 "AI 티" 패턴을 10개 대분류 × 서브 패턴으로 정리한다. 탐지기·윤문가·리뷰어가 공유하는 단일 진실 원천(SSOT). 각 패턴마다 (1) 정의, (2) 시그니처 예문, (3) 심각도(S1 결정적 / S2 강함 / S3 약함), (4) 윤문 처방을 제공한다.
|
||||
|
||||
> **v2.0 추가 (2026-05-07):** 한국 번역학계 8대 번역투 정통성 계보(이영옥 2001·김도훈 2009·김정우 2007·김혜영 2019 등) + 보고서 §III.3(8유형) 통합. **본진 신규 4건** — `A-16` 영어 대명사 직역 [S1] · `A-18` 관계절 좌향 수식 [S2] · `A-19` 이중 조사 결합 [S2] · `E-7` 청자 경어법 일관성 손실 [S2 · estimated]. **본진 보강 4건** — `A-15` 인지·발화 동사 분리 구문 처방 · `A-7` light verb construction 일반화(have/make/take/give) · `F-4` 영어 명사화 접미사(-tion·-ment·-ness·-ity) 통합 · `E-2` 진행형 '~고 있다' 자동 매핑 처방. **본진 hold 1건** — `A-17` 무정물·추상명사 '-들' 부착 [학술 강함, 외부 회차 양성 0건 → NMT 원본 회차 후 v2.1 재평가]. **post-editese 3축은 metric-only 트랙** — caveat C3(한국어 정량 검증 부재)에 따라 본진 ID 미부여, `metrics_v2.py` 14개 신규 함수로 운영(`deul_overuse_rate` 포함, A-17 hold 검증용). 학술 전문은 외부 SSOT `references/scholarship.md`에 보존(본진 슬림성). valid as of 2026-05.
|
||||
>
|
||||
> **v1.6 추가 (2026-05-06):** KatFish(Park et al.) + LREAD 외부 정량 연구 기반 본진 신규 5건 — `C-11` 연결어미 뒤 쉼표 [S1, 4.84배 분리도] · `C-12` 쉼표 포함률 [S2] · `E-5` 쉼표 분절 평균 길이 [S2] · `E-6` 쉼표 전후 POS 다양성 [S2, 에세이·뉴스 한정] · `G-3` 안전 균형 lexicon [S2]. 본진 보강 2건 — `D-1`에 KatFish 검증 결산 lexicon 4종("결론적으로·따라서·이를 통해·그러므로") 정식 인용 + 임계, `F-4`에 한자어 명사화 접미사 3종("-성·-적·-화") 정식 명시 + 한 문서 12회 초과 임계. hold 2건(BN/VX 띄어쓰기 규칙성·페르소나-레지스터 불일치)은 본진 미등재 — `_workspace/v1.6-2026-05-06/`에 후보 발자취 보존.
|
||||
>
|
||||
> **v1.5.1 추가 (2026-04-27):** Category E에 `E-4 단문 일변도 (복문·중문 부재)` [S2] 신설. 인간 필자는 단문과 복문을 무의식적으로 섞어 호흡을 만드는데, AI가 "간결하게" 의도하면 단문만 늘어놓아 끊어진 리듬이 그 자체로 시그니처가 된다.
|
||||
>
|
||||
> **v1.5 변경 (2026-04-26):** v1.2 voice profile · v1.3 candidate pool · v1.3.1 권한 위계는 모두 제거됐다. 이유는 핫패스 비용. v1.5는 v1.1 5인 파이프라인 구조 + monolith fast 1콜로 단순화됐고, 분류 체계 본진(이 파일)은 v1.3.1까지 발굴된 신규 패턴(C-9·C-10·D-7·H-3·I-3·I-4 보강 등)을 그대로 유지한다.
|
||||
|
||||
## 심각도 기준
|
||||
|
||||
- **S1 결정적(critical)**: 한 번만 나와도 "이건 AI"라고 거의 확신하게 되는 패턴. 무조건 제거.
|
||||
- **S2 강함(high)**: 1~2회는 자연스러울 수 있으나 문서에서 3회 이상 반복되면 티 남. 밀도 기반 제거.
|
||||
- **S3 약함(low)**: 개별로는 문제 아님. 다른 패턴과 중첩될 때 AI 인상을 강화. 리듬 조정 수준.
|
||||
|
||||
## 권한 위계 (Authority Hierarchy)
|
||||
|
||||
분류 체계와 작가 의도가 충돌할 때의 우선순위를 명문화한다. 이 절은 v1.2부터 추가되는 작가 voice profile의 권한 경계를 규정하며, 모든 에이전트(detector·rewriter·auditor·reviewer)는 이 위계를 따른다.
|
||||
|
||||
### 1. 기본 원칙: 객관 분류 우선(default-on)
|
||||
|
||||
이 분류 체계의 패턴들은 **반증 없는 한 적용된다**. "AI 티"는 인간 필자가 거의 쓰지 않는 통계적 시그니처라는 전제 위에 서며, 그 전제를 흔들려면 객관적 근거(특정 작가의 voice·장르 관습)가 필요하다.
|
||||
|
||||
### 2. 작가 voice profile은 opt-in 주입(v1.2~)
|
||||
|
||||
사용자가 명시적으로 `author-context.yaml`을 제공한 경우에만 voice profile이 활성된다. 자동 로드(예: 프로젝트 `CLAUDE.md` 자동 파싱)는 prompt injection 진입점이 되므로 허용하지 않는다.
|
||||
|
||||
### 3. voice profile이 무력화할 수 있는 범위
|
||||
|
||||
- **허용**: 특정 패턴 ID의 on/off, 임계 완화(예: J-3 단락당 1~2회 → 5회), Do-NOT 키워드 화이트리스트
|
||||
- **금지**: 자유 텍스트 mandate(예: "단정적 예측 금지"). 자연어 지시는 LLM이 자의로 해석해 분류 체계를 자의적으로 굴절시킬 위험이 있다. 모든 override는 패턴 ID 또는 키워드 단위로 구조화되어야 한다.
|
||||
|
||||
### 4. 무력화 불가 패턴(영구 default-on)
|
||||
|
||||
다음 패턴은 voice profile로 끌 수 없다. 어떤 작가 의도로도 정당화되지 않는 결정적 AI 시그니처이기 때문이다.
|
||||
|
||||
- **A-8** 이중 피동 "~되어진다"
|
||||
- **C-5** 이모지 남발
|
||||
- **D 카테고리 전체** AI 특유 관용구(D-1~D-6)
|
||||
- 그 외 향후 v1.2+에서 명시 지정되는 패턴
|
||||
|
||||
**임계 완화 캡 (multiplier 상한):**
|
||||
- 일반 패턴: 임계 완화 multiplier ≤ **2.0** (예: 단락당 2회 → 4회)
|
||||
- D-1~D-6 카테고리: 임계 완화 multiplier ≤ **1.5** (단일 사용 leniency만 허용. 임계 우회를 통한 사실상 무력화 방지 — D 카테고리는 결정적 AI 시그니처라 multiplier 2.0이면 사실상 disable에 가까움)
|
||||
- A-8·C-5: 임계 완화 자체 불허 (multiplier 1.0 고정)
|
||||
|
||||
이 캡을 초과하는 `pattern_overrides` 항목은 schema validator가 거부한다.
|
||||
|
||||
### 5. 분리 검증층 보존
|
||||
|
||||
`naturalness-reviewer`에는 voice profile을 **주입하지 않는다**. 작가 의도에 정렬된 detector·rewriter·auditor의 합의가 형식만 남지 않도록, voice profile을 모르는 외부 시각이 한 층 남아 있어야 한다. 이 분리는 도구의 신뢰성 근거이며 임의로 해제하지 않는다.
|
||||
|
||||
### 6. 회귀 테스트 게이트
|
||||
|
||||
새로운 패턴 무력화 옵션을 voice profile 스키마에 추가할 때는, 외부 케이스 2~3건(다른 작가·다른 장르)에서 false positive·false negative 비교 리포트를 통과해야 한다. 단일 사용자 self-reported 결과만으로는 분류 무력화 권한을 확장하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 목차
|
||||
|
||||
A. 번역투(Translation-ese)
|
||||
B. 영어 인용·용어 과다
|
||||
C. 구조적 AI 패턴 (서식·레이아웃)
|
||||
D. AI 특유의 관용구 (Signature Phrases)
|
||||
E. 리듬·문장 길이 균일성
|
||||
F. 과도한 수식·중복
|
||||
G. 과도한 Hedging (완곡)
|
||||
H. 접속사 남발
|
||||
I. 형식명사·의존명사 과다
|
||||
J. 시각 장식 남용
|
||||
A. 번역투(Translation-ese) — A-1~A-19
|
||||
B. 영어 인용·용어 과다 — B-1~B-4
|
||||
C. 구조적 AI 패턴 (서식·레이아웃) — C-1~C-12
|
||||
D. AI 특유의 관용구 (Signature Phrases) — D-1~D-7
|
||||
E. 리듬·문장 길이 균일성 — E-1~E-7
|
||||
F. 과도한 수식·중복 — F-1~F-5
|
||||
G. 과도한 Hedging (완곡) — G-1~G-3
|
||||
H. 접속사 남발 — H-1~H-4
|
||||
I. 형식명사·의존명사 과다 — I-1~I-6
|
||||
J. 시각 장식 남용 — J-1~J-4
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -101,6 +66,7 @@ J. 시각 장식 남용
|
|||
- 패턴: 소유·특성 서술 (영어 `have/possess`)
|
||||
- 예: "강한 경쟁력을 **가지고 있다**" → "경쟁력이 강하다"
|
||||
- 처방: 형용사형으로 돌려 서술어 없애거나 "있다"로 단순화.
|
||||
- **light verb construction 일반화 (v2.0 보강)**: 보고서 T6은 'have/make/take/give + 명사' 가벼운 동사 구문(light verb construction) 전반을 다룸. A-7 본진 처방 위에 (a) 동사 환원, (b) 이중주어 구문('X는 Y가 …') 활용을 명시 추가. verbatim 예문 — "She has a sweet voice → 그녀는 목소리가 아름답다" / "She has a book under her arm → 그녀는 책을 옆구리에 끼고 있다" / "We had a meeting yesterday → 우리는 어제 회의를 했다(열었다)" / "The committee made a decision → 위원회가 결정했다" / "The data show a rapid increase → 데이터에 따르면 급격히 증가했다". metric `have_make_literal_count` 정량 검출. _source_anchor: 김정우 2007 · 이근희 2005 · see_scholarship: scholarship.md#6-명사화-표현-및-havemake-류-직역_
|
||||
|
||||
### A-8. 이중 피동 "~되어진다" / "~지게 된다" [S1]
|
||||
- 예: "판단**되어진다**" → "판단된다" / "판단한다"
|
||||
|
|
@ -138,6 +104,59 @@ J. 시각 장식 남용
|
|||
- 패턴: 영어 `The X shows / provides / brings Y` 직역. 주어가 사건·현상이고 술어가 "보여준다·제공한다·가져온다·시사한다"
|
||||
- 예: "DeepSeek-V4**의 등장은** ~을 **보여줍니다**" / "이 전략**은** 지형**을 흔들고 있습니다**" / "X**는** Y**를 제공합니다**"
|
||||
- 처방: 주어를 행위자(사람·팀·회사)로 돌리거나, 주어·동사 자체를 없애고 직접 서술. "DeepSeek는 ~ 원칙을 이렇게 증명했다" 식.
|
||||
- **v2.0 보강 — 사역·인지·발화 동사 3축 처방**:
|
||||
- (a) 사역 타동사형(`X made Y …`) → `X 때문에/덕분에 Y는 …` 또는 `X로 인해 Y는 …` 부사절·원인절 환원. 예: "The news made him happy → 그 소식을 듣고 그는 기뻤다"; "1997년 금융위기는 한국 노동시장에 급격한 변화를 가져왔다 → 1997년 금융위기로 한국 노동시장은 급격히 바뀌었다"
|
||||
- (b) 인지·발화 동사(suggest/show/indicate/reveal) → `…에 따르면 …이다` 또는 `…으로 …이 드러났다` 분리 구문. 예: "Recent research suggests that … → 최근 연구에 따르면 …이다 / 최근 연구를 통해 …이 드러났다"
|
||||
- (c) 'This book has 300 pages' 류 → 이중주어 구문 활용 ('이 책은 300쪽이다', '이 책은 300쪽을 가진다 X')
|
||||
- _source_anchor: 이영옥 2001 · 김정우 2007 · see_scholarship: scholarship.md#1-무생물-주어--타동사-구문_
|
||||
|
||||
### A-16. 영어 대명사 직역 (그/그녀/그것/그들) [S1] · v2.0 신규
|
||||
- 패턴: 영어 `he/she/it/they → 그/그녀/그것/그들`을 1대1 매핑. 한국어는 (i) 영형(zero) 대명사를 통한 생략, (ii) 반복적 명사구의 재사용, (iii) 친족·지위 호칭으로 응결성(cohesion)을 확보. 한국어 '그/그녀'는 본래 19~20세기 번역 문학을 통해 도입된 인공 어휘에 가깝다. NMT/LLM 출력의 대명사 밀도가 비번역 한국어의 **2~3배**에 달함(보고서 §3.3.3 verbatim).
|
||||
- 예:
|
||||
- "John was tired. He sat down. He sighed. He looked at his watch." → 직역 "존은 피곤했다. **그는** 앉았다. **그는** 한숨을 쉬었다. **그는** **그의** 시계를 보았다." → 자연 "존은 피곤했다. 자리에 앉아 한숨을 쉬고는 시계를 보았다."
|
||||
- "Mary called her mother because she missed her." → 직역 "메리는 **그녀가** **그녀를** 그리워해서 **그녀의** 어머니에게 전화했다." → 자연 "메리는 어머니가 그리워서 전화를 걸었다."
|
||||
- "his hand / her hair" → 직역 "**그의** 손 / **그녀의** 머리" → 자연 "손 / 머리" (거의 항상 잉여적)
|
||||
- 처방:
|
||||
- (a) 대명사 출현의 50~70%는 삭제 후보로 보고 문장 재구성
|
||||
- (b) 화자 전환·장면 전환의 시점에서만 명사구 또는 호칭으로 명시
|
||||
- (c) 'he/she'가 성별 모르는 일반인은 '그 사람' 또는 주어 생략. 'they'는 '그들'이 아니라 '사람들·우리·일부·어떤 이들'로 다양화
|
||||
- 검출 임계: 한 단락(=문단) 내 인칭 대명사 ≥3회 시 가산 (pe_checklist PE4). metric `pronoun_density` z>2.0 (비번역 한국어 baseline 대비) 시 정점 가산.
|
||||
- _source_anchor: 김도훈 2009 통역과 번역 11(2): 3-19; Cho·Kim·Kim·Kim 2019 ACL GeBNLP arXiv:1905.11684 · see_scholarship: scholarship.md#3-대명사-직역-hesheitthey--그그녀그것그들_
|
||||
|
||||
### A-17. (보류 — v2.0 hold) 무정물·추상명사 '-들' 복수 표지 기계적 부착
|
||||
|
||||
> **Hold 사유**: v2.0 외부 회차(2026-05-07, 한국어 위키 6편)에서 양성 0건, v1.6 input 5편에서도 0건. 학술 anchor(전영철 2007 언어학 49 · 곽은주·진실로 2011 · 김순영 2012 · 김정우 2013)는 강하나, 우리 코퍼스에서 결정타 부재. NMT 원본 출력 회차(DeepL·Papago·Google Translate) 후 v2.1에서 동일 ID로 재평가 예정.
|
||||
>
|
||||
> **유지 자산**: scholarship.md §4(전문 학술 인용 보존), `metrics_v2.deul_overuse_rate` 함수와 무정물·추상 명사 사전 25종(검증용 정량 측정은 계속), 본 hold 결정 기록(`promotion_decisions.md`).
|
||||
>
|
||||
> A-17 ID는 v2.1 부활을 위해 비워둠 — detector·rewriter 코드의 patternID 안정성 보존.
|
||||
|
||||
### A-18. 관계대명사절 직역 — 긴 좌향 수식 (관형구 3중 이상 중첩) [S2] · v2.0 신규
|
||||
- 패턴: 영어는 관계대명사절을 명사 뒤에 후치(right-branching)하지만, 한국어는 관형절을 명사 앞에 전치(left-branching). 영어 긴 관계절을 1대1 매핑하면 핵 어휘 도달 전 독자 작업기억 부담 폭증. NMT/LLM은 영어 SVO 구조를 가능한 유지하려 하므로 좌향 수식 누적이 빈번(박옥수 2018).
|
||||
- 예:
|
||||
- "He met a man who had once worked for the company that produced the chemical that caused the accident." → 직역 "그는 **사고를 일으킨 화학물질을 생산한 회사에서 한때 일했던 한 남자를** 만났다." → 자연 "그는 한 남자를 만났는데, 그 남자는 사고를 일으킨 화학물질을 만든 회사에서 한때 일했던 사람이었다." (관계절을 후치 동격절로)
|
||||
- "He was too intelligent and perceptive not to feel the disappointment of his admirers from the 1930s." → 직역 "그는 **1930년대부터 자기를 따랐던 사람들이 느낄 실망감을 눈치채지 못하기에는** 너무 똑똑하고 예민했다." → 자연 "그는 워낙 똑똑하고 예민해서 1930년대부터 자기를 따랐던 사람들이 느낄 실망감을 눈치챘다."
|
||||
- 처방:
|
||||
- (a) 관계절이 3어절 이상이면 문장을 분리하거나 동격 후치 구문으로 변환
|
||||
- (b) 'who, which, that'을 '~인 X', '~한 X' 식으로 직역하지 말고 '~는데, ~으며, 그 X는'으로 풀어쓰기
|
||||
- (c) NMT 출력 검토 시 '~한 …의 …을 …한 …이/가'처럼 관형구가 3중 이상 중첩된 문장은 무조건 재구성 대상
|
||||
- 검출 임계: 명사 앞 관형구 ≥3어절 시 가산 (pe_checklist PE6). metric `relative_clause_nesting` (한 명사구 내 관형절 중첩 깊이) ≥3 문장 카운트, 한 문서 1회 초과 시 가산. **A-18은 관형절 좌향 수식 단위, E-5(쉼표 분절 평균 길이)는 쉼표 단위 — 측정 차원 분리. 동시 위반 시 가중.**
|
||||
- _source_anchor: 박옥수 2018 동아인문학 44: 151-171; 김채은 2021 21세기영어영문학회 34: 279-305 · see_scholarship: scholarship.md#5-관계대명사절-직역-긴-좌향-수식_
|
||||
|
||||
### A-19. 이중 조사 결합 (-에서의·-에로의·-으로의·-에의·-으로부터의·-로부터의) [S2] · v2.0 신규
|
||||
- 패턴: 근대 한국어가 일본어 'の(の/への/での)' + 영어 전치사구('of/in/to/from')의 영향으로 격조사를 이중·삼중 결합한 표현이 늘어남. 본래 한국어는 절·구로 풀어 쓰는 것이 자연스러움.
|
||||
- **caveat C5 명시 제외 — 단순 '~의'는 탐지 대상 아님**: '~의' 자체가 일본어 번역투인지에 대해서는 학계 합의가 없다. 국립국어원과 김슬옹 세종국어문화원장은 '~의'가 15세기부터 한국어에 존재했다고 본다(보고서 caveat #5 verbatim). 본 패턴은 '~에서의·~에로의·~으로의·~에의·~으로부터의·~로부터의' 이중 결합만 S2 이상으로 본다.
|
||||
- 예:
|
||||
- "the meeting in the upper story of the bar" → 직역 "주점의 2층**에서의** 살림" → 자연 "주점의 2층에서 시작한 살림"
|
||||
- "liberation from tension" → 직역 "긴장**으로부터의** 해방" → 자연 "긴장에서 벗어남, 긴장이 풀림"
|
||||
- "the response to the questionnaire" → 직역 "설문지**에의** 응답" → 자연 "설문지에 대한 응답, 설문 답변"
|
||||
- "destroyed by the bombing" → 직역 "폭격에 의해 끊어진" / "이번 기회를 통하여" → 자연 "폭격으로 끊어진 / 이번 기회에"
|
||||
- 처방:
|
||||
- (a) 이중 조사 결합('-에서의/-에로의/-으로의/-에의/-으로부터의/-로부터의')은 검색 후 일괄 점검 대상
|
||||
- (b) 전치사구 'from/to/through/by/of'를 1대1 매핑하지 말고 문장 단위로 의미 재해석
|
||||
- (c) 연속된 '의 의 의'는 거의 항상 부적절하므로 절·구로 풀어쓰기
|
||||
- 검출 임계: metric `double_particle_count` 정규식 매칭(`에서의|에로의|으로의|에의|으로부터의|로부터의`). 한 문서 3회 초과 시 S2 가산. baseline 비번역 한국어 0~2회 추정.
|
||||
- _source_anchor: 김정우 2007 번역학연구 8(1): 61-82; 김순영 2012 새국어생활 22(1) · see_scholarship: scholarship.md#7-일본어영어식-조사-결합-에서의에로의으로의에의_
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -209,6 +228,16 @@ J. 시각 장식 남용
|
|||
- 예: "### **서론**: 제조업의 미래, AI에 달려있다" / "### **본론 1**: 빛과 그림자, 대기업과 중소기업의 디지털 격차" / "## 2026년 핀테크, '일상'을 넘어 '생태계'로 진화하다"
|
||||
- 처방: 헤딩에서 콜론 + 부제 자체를 제거하고 단일 명사구·동사구로 압축. 정말 부제가 필요하면 (a) 본문 첫 문장에 녹이기, (b) 콜론 대신 — 또는 줄바꿈 활용. 한 문서에 콜론 부제 헤딩 1회 이하.
|
||||
|
||||
### C-11. 연결어미 뒤 쉼표 [S1] · v1.6 신규
|
||||
- 패턴: 연결어미(-고/-며/-지만/-면서/-아서·어서/-자/-는데) 직후에 쉼표가 따라옴. 한국어 인간 필자는 연결어미 자체가 호흡 단위를 만들어 추가 쉼표가 거의 불필요한데, AI는 영어 comma-after-conjunction 감각을 이식해 자동으로 쉼표를 박음. 분류 체계 전체 단일 지표 최강 분리도(KatFish 에세이 인간 4.10% vs AI 19.83%, **4.84배**).
|
||||
- 예: "AI는 빠르게 발전하**지만,** 기업의 대응은 더디다" / "데이터를 정제하**고,** 모델을 학습시킨**다음,** 결과를 검증한다" / "비용이 낮아지**면서,** 진입장벽이 사라졌다"
|
||||
- 처방: 연결어미 뒤 쉼표를 일괄 제거. 호흡이 너무 길어지면 한국어식 절(節) 분할(마침표로 끊기) 또는 다른 위치(주절 경계)로 쉼표 이동. 한 문서 6+회 등장 시 S1, 3~5회는 S2로 강도 분기.
|
||||
|
||||
### C-12. 쉼표 포함률 (문서 단위) [S2] · v1.6 신규
|
||||
- 패턴: 전체 문장 중 쉼표를 1개 이상 포함하는 문장의 비율이 50%를 넘음. C-11(연결어미 뒤 위치 특이)과 측정 차원이 다름 — C-12는 문서 전체 분포. AI는 거의 모든 문장에 쉼표를 넣는 경향(KatFish 에세이 인간 26.31% vs AI 61.03%, 2.32배).
|
||||
- 예: 한 문단 5개 문장 중 4~5개 모두에 쉼표가 박힌 상태(인간 평균은 5개 중 1~2개).
|
||||
- 처방: 쉼표 1+ 문장 비율 50% 초과 시 일부 문장의 쉼표를 (a) 마침표 분할로 단문화, (b) 연결어미로 흡수, (c) 그냥 삭제로 전환. baseline 26~33% 기준 z>1.0 가산.
|
||||
|
||||
---
|
||||
|
||||
## D. AI 특유의 관용구 (Signature Phrases) — S1
|
||||
|
|
@ -220,6 +249,7 @@ J. 시각 장식 남용
|
|||
- "~라고 할 수 있다" / "~라고 볼 수 있다"
|
||||
- "~라 하겠다", "~라 할 것이다"
|
||||
- "~에 다름 아니다"
|
||||
- **KatFish 검증 결산 lexicon 4종 (v1.6 보강)**: "결론적으로 / 따라서 / 이를 통해 / 그러므로" — Park et al. 보고서 lexicon-grounded 6대 지표 5번. LREAD Phase 2 루브릭에서 인간 판독 정확도 60→90% 상승의 핵심 항목. 이 4종 합산이 한 문서에 3회 초과 시 D-1 가산을 강화(S1 유지). "이를 통해"는 A-2(~를 통해 남발)와도 가산되며, "따라서·그러므로"는 H-1(문두 접속사)과 가산.
|
||||
|
||||
### D-2. 의의·중요성 과장
|
||||
- "매우 중요하다", "반드시 기억해야 한다"
|
||||
|
|
@ -273,11 +303,44 @@ J. 시각 장식 남용
|
|||
- "~이다. ~이다. ~이다."
|
||||
- "~한다. ~한다. ~한다."
|
||||
- 처방: "~다"·"~았다"·"~인 것"·명사형 종결을 섞음. 인간 필자는 무의식적으로 변주함.
|
||||
- **v2.0 보강 — 진행형 '~고 있다' 자동 매핑 처방**:
|
||||
- 영어 진행형(be -ing)을 한국어 '~고 있다'로 자동 매핑하면 잉여(보고서 §3.8.4 verbatim — '지금 책을 읽는다 / 책을 읽고 있다' 모두 가능, 단순 시제로도 진행 의미가 표현됨).
|
||||
- 진행형 '~고 있다' 발견 시 단순 시제로 환원 가능성 검토 (pe_checklist PE10). 예: "I have been thinking about it. → 그동안 그 일을 곰곰이 생각해 봤다" (`~해 오고 있다` 거부).
|
||||
- 한 단락 내 종결어미 '~다' 4문장 이상 연속 시 다양화 ('~었다·~ㄴ다·~는다·~기 마련이다·~ㄹ 것이다·~을 수 있다' 등) — pe_checklist PE9.
|
||||
- metric `progressive_aspect_rate` (~고 있다 빈도 / 전체 문장 수) >0.5 시 가산.
|
||||
- _source_anchor: 김혜영 2019 통번역교육연구 17(2): 133-162 doi:10.23903/kaited.2019.17.2.007 · see_scholarship: scholarship.md#8-종결어미시제서법-처리_
|
||||
|
||||
### E-3. 모든 문단 3~4문장 공식
|
||||
- 문단 길이도 균일.
|
||||
- 처방: 1문장 문단 / 6문장 문단을 의도적으로 섞음.
|
||||
|
||||
### E-4. 단문 일변도 (복문·중문 부재) [S2] · v1.5.1 신규
|
||||
- 패턴: 문장 대부분이 단문(주어-서술어 1쌍)으로만 끊어져 있고 연결어미·관형절·인용절을 활용한 복문·중문이 거의 없음. "간결하게 써라" 지시를 받았거나 짧은 호흡을 의도한 AI 출력에서 빈출. 인간 필자는 단문과 복문을 무의식적으로 섞어 호흡을 만들기 때문에, 단문만 줄지어 나오는 리듬 자체가 시그니처가 된다. E-1(길이 균일성)과 짝패턴 — E-1은 길이의 표준편차, E-4는 구조의 단조성을 지적.
|
||||
- 예: "AI는 빠르게 발전한다. 기업은 따라가야 한다. 시간이 없다. 데이터가 핵심이다. 인재도 부족하다."
|
||||
- 비교(인간 톤): "AI가 빠르게 발전하는 가운데 기업은 따라가야 한다. 시간은 없고 데이터는 핵심이며, 인재마저 부족하다."
|
||||
- 처방: 인접한 단문 2~3개를 연결어미("-며·-고·-는데·-면서·-자")·관형절("~하는 N", "~인 N")·인용절·조건절로 묶어 복문화. 단문 비중을 60% 전후로 조절하고 복문·중문을 30% 이상으로. 단문은 강조·전환·결정타에만 의도적으로 사용.
|
||||
|
||||
### E-5. 쉼표 분절 평균 길이 (긴 절 구조) [S2] · v1.6 신규
|
||||
- 패턴: 쉼표로 분절된 절(節)의 평균 어절 수가 길어짐. AI는 한 문장 안에 긴 부속절을 콤마로 이어 붙이는 영어식 long-sentence 구조를 산출(KatFish 에세이 인간 4.35어절 vs AI 8.56어절, 1.97배). E-4(단문 일변도)와 짝패턴이지만 반대 방향 — 짧으면 단문 일변도, 길면 영어식 long sentence, 두 극단 모두 AI 시그니처.
|
||||
- 예: "AI 기술이 빠르게 발전하면서 산업 전반의 생산성이 높아지고 있는 가운데, 기업의 디지털 전환 속도가 가속화되고, 인재 확보 경쟁이 치열해지면서, 데이터 인프라 투자도 확대되고 있다." (한 문장 안 4개 절, 각 8~12어절)
|
||||
- 처방: 평균 절 길이 7어절 초과 시 가산. 8어절 이상 절은 마침표로 분할하거나 영어식 부속절을 한국어 관형절로 압축. E-4와 동시 위반 시 가중.
|
||||
|
||||
### E-6. 쉼표 전후 POS 다양성 높음 (구문 복잡도) [S2] · v1.6 신규 · 장르 가드
|
||||
- 패턴: 쉼표 앞·뒤에 등장하는 품사(POS) 종류 수가 폭증. AI는 쉼표를 다양한 품사 경계에 무차별 삽입(주어·부사절·삽입절·접속사 뒤 등)해 POS 다양성이 매우 커짐(KatFish 에세이 인간 24.38 vs AI 59.39, 2.44배). **장르 가드 — 에세이·뉴스·블로그·QA·보고서 한정 적용**. 시·소설 등 운문·문학 장르에서는 분리도 약함(시: 인간 23.13 vs AI 23.86, 1.03배).
|
||||
- 예: 같은 문서 안에서 쉼표가 명사 뒤·부사 뒤·동사 뒤·관형사 뒤·인용 뒤·접속사 뒤 모두에 무차별 삽입.
|
||||
- 처방: 쉼표 사용 위치를 (a) 주절 경계만, (b) 명백한 동격·삽입만으로 한정. 무차별 삽입 금지. baseline ~24~30(에세이/뉴스) 기준 z>1.0 가산.
|
||||
|
||||
### E-7. 청자 경어법 일관성 손실 (해라/하게/하오/해요/합쇼체) [S2 · estimated] · v2.0 신규
|
||||
- 패턴: 한국어는 교착어로서 종결어미가 (a) 문장종결법, (b) 화행, (c) 양태(modality), (d) 청자에 대한 공손성, (e) 화자–청자 관계까지 표시한다(보고서 §3.8.1 verbatim). 영어는 종결어미가 없어 어순·동사 굴절·양태조동사로 이를 표현하므로, 영한 번역·LLM 출력은 청자 경어법 일관성을 자주 잃는다. **해라체·하게체·하오체·해요체·합쇼체 4단계가 한 문서·대화 안에서 뒤섞임.** E-2(동일 종결어미 반복)와 axis가 다름 — E-2는 단일 종결어미의 단조 반복, E-7은 격식 단계의 일관성 손실.
|
||||
- **estimated 플래그 (caveat C1)**: 김혜영 2019 본문 정량 수치(평서형 '-다' 출현 빈도 %)는 KCI ART002506702 영문 초록·키워드 기반 추론. PDF 직접 확보 전까지 임계는 'estimated' 유지.
|
||||
- 예: 같은 대화 안에서 "도와주시겠습니까?(합쇼)" 다음에 "도와줘?(해라)"가 갑자기 등장; 같은 보고서가 한 문단은 "~합니다"(합쇼), 다음 문단은 "~한다"(해라)로 점프; "Will you help me? → 당신은 나를 도와주겠습니까?"식 격식 과잉 직역.
|
||||
- 처방:
|
||||
- (a) 문서 시작 시 청자 등급(해라/하게/해요/합쇼)을 결정하고 일관 유지
|
||||
- (b) "Will you help me?"는 관계·공손도에 따라 "좀 도와주시겠어요? / 도와줄래?" 등 적절한 한 단계만 선택
|
||||
- (c) 화행·양태(might/may)는 단조 처리("~수 있다") 거부 — "~을지 모른다·~을 가능성도 있다·~을 수도 있겠다·~을 법하다"로 다양화
|
||||
- 검출 임계: **장르 가드 — 대화·구어 텍스트(소설 대화·인터뷰 트랜스크립트·에세이 내 인용 대화) 한정 적용**. 보고서·정책문 등 격식체 단일 장르는 본 패턴 미적용 (이미 합쇼체로 일관). 한 문서·대화 안에 2단계 이상 격식 혼재 발견 시 S2.
|
||||
- _source_anchor: 김혜영 2019 통번역교육연구 17(2): 133-162 doi:10.23903/kaited.2019.17.2.007 · see_scholarship: scholarship.md#8-종결어미시제서법-처리_
|
||||
|
||||
---
|
||||
|
||||
## F. 과도한 수식·중복 — S2
|
||||
|
|
@ -301,6 +364,9 @@ J. 시각 장식 남용
|
|||
- "~적 측면", "~적 관점"
|
||||
- "~성(性)", "~화(化)" 남발
|
||||
- 예: "**근본적 관점**에서 **구조적 변화**가 **필연적**이다" → "구조가 근본부터 바뀐다"
|
||||
- **한자어 명사화 접미사 3종 명시 (v1.6 보강)**: "-성(性) · -적(的) · -화(化)" — KatFish 보고서 hanja_nominalizers 정식 명시. 이 3종 결합 어휘 밀도가 한 문서 **12회 초과 시 S2 강화**. F-5(~적 N 복합 추상어 체인)는 "-적" 접미사의 특수 케이스로 그대로 분리 유지. 처방은 본 항목과 동일 — (a) 동사·형용사 어근, (b) 구체 명사로 해체.
|
||||
- **영어 명사화 접미사 4종 통합 (v2.0 보강)**: 영어 명사화 접미사 `-tion · -ment · -ness · -ity`가 누적된 영어 명사구의 한국어 명사 직역도 동일 처방으로 묶음. 예: "the implementation of the policy → 정책 시행" 또는 "정책을 시행하기" (보고서 §3.6 verbatim 처방). 영어 명사화 4종이 한국어 한자어 명사로 1대1 매핑된 경우 동사·형용사 어근으로 환원. 한자어 3종(-성·-적·-화) + 영어 4종(-tion·-ment·-ness·-ity) 통합 가산 임계는 위 v1.6 임계(한 문서 12회 초과 S2 강화) 그대로 유지.
|
||||
- _source_anchor: 김정우 2007 번역학연구 8(1): 61-82 · see_scholarship: scholarship.md#6-명사화-표현-및-havemake-류-직역_
|
||||
|
||||
### F-5. "~적 N" 복합 추상어 체인 [S2] · v1.1 신규
|
||||
- 패턴: 명사 앞 "~적 N" 형태가 한 문서에 3회 이상 반복. F-4와 달리 추상 관형("적 측면/관점")이 아닌 **구체 명사 앞 "~적 N"** 체인. 원문의 지적 권위를 AI가 흉내 낼 때 빈출.
|
||||
|
|
@ -323,6 +389,11 @@ J. 시각 장식 남용
|
|||
- "~로 보여질 수 있다"
|
||||
- 처방: 하나만 남김.
|
||||
|
||||
### G-3. 안전 균형 lexicon (Safe Balance Score) [S2] · v1.6 신규
|
||||
- 패턴: "양쪽 모두 / 두 가지 모두 / 장점도 있지만 / 신중하게 / 균형" 등 균형·양면성·완곡 어휘 빈도가 높음. KatFish 보고서 lexicon-grounded 6대 지표 6번. G-1(추측·관측형 종결어미)과 측정 차원이 다름 — G-1은 종결어미 단위, G-3는 lexicon 단위. LREAD 루브릭의 "위험 회피·양면 제시" 항목과 직결.
|
||||
- 예: "**양쪽 모두** 일리가 있다 / **두 가지 모두** 검토할 필요가 있다 / **장점도 있지만** 단점도 있다 / **신중하게** 접근해야 한다 / **균형** 잡힌 시각이 중요하다"
|
||||
- 처방: 균형 어휘를 (a) 한쪽 단언, (b) 구체 사례 비교, (c) 조건부("X일 때는 A, Y일 때는 B")로 변환. "신중하게·균형"은 구체 동사·기준으로 치환. 한 문서 lexicon 5종 합산 4회 초과 시 S2 가산. 정책·보고서 장르 한정(에세이·시는 baseline이 다름 — 향후 metric-engineer가 장르별 보강).
|
||||
|
||||
---
|
||||
|
||||
## H. 접속사 남발 — S2
|
||||
|
|
@ -447,8 +518,38 @@ J. 시각 장식 남용
|
|||
- `severity_weighted_score`: S1=5, S2=2, S3=0.5 가중 합. 0~100 스케일로 정규화.
|
||||
- `ai_tell_density`: 탐지 span 총 글자 수 / 전체 글자 수.
|
||||
|
||||
## post-editese 3축 — metric-only 트랙 (v2.0 도입)
|
||||
|
||||
> **중요**: post-editese 3축(simplification·normalisation·interference)은 본진 패턴 ID 미부여 상태로 운영한다. 이유는 **caveat C3** verbatim — "Toral(2019)은 en→de, de→en, es→de, en→fr, zh→en의 5개 언어쌍을 다뤘고, **한국어는 포함되지 않았다**. 한국어에 대한 동일 결론은 합리적 추론이지만 정량적 검증은 미수행 상태다." 따라서 본진 패턴 ID는 토큰·구문 매칭 가능한 검증 시그널만 담고, 3축 합성 신호는 metric-only로 분리한다.
|
||||
|
||||
`references/metrics_v2.py` 14개 신규 함수가 3축을 운영한다(모든 metric에 `speculative: true` 플래그 권고):
|
||||
|
||||
- **simplification 축**: `lexical_diversity_ttr` · `lexical_density` · `ending_diversity` (Baker 1993; Toral 2019).
|
||||
- **normalisation 축**: `normalisation_score`(평서형 -다/된다/이다 집중률) · `da_streak_rate`(-다 4문장 연속 streak 카운트) (Baker 1993).
|
||||
- **interference 축**: T1~T8 8개 검출 시그널 + `interference_index` 합성 (Toury 1995 law of interference) — `inanimate_subject_rate`(T1↔A-15·D-5) · `by_passive_count`/`double_passive_count`(T2↔A-8·A-9·A-12) · `pronoun_density`(T3↔A-16) · `deul_overuse_rate`(T4↔A-17 **hold, 검증용 측정 유지**) · `relative_clause_nesting`(T5↔A-18) · `have_make_literal_count`(T6↔A-7·F-4) · `double_particle_count`(T7↔A-19) · `progressive_aspect_rate`(T8↔E-2·E-7).
|
||||
|
||||
본진 패턴 → metric 연계는 양방향이다. 패턴 위반 카운트가 임계 초과면 detector·rewriter가 본진 ID로 처방하고, 동시에 metric 합성 점수가 baseline 대비 이상치면 reviewer가 추가 검증한다. 한국어 baseline은 metric-engineer가 비번역 한국어 corpus(Sejong 등) 기준 산출한다.
|
||||
|
||||
## 버전 관리
|
||||
|
||||
- **v2.0** (2026-05-07): **본진 신규 5건 + 본진 보강 4건 + post-editese metric-only 트랙 도입** — 한국어 번역투 종합 연구보고서(540줄, 4기 1994~ AI 융합 계보) + 보고서 §III.3 8유형 통합 + Toral 2019 post-editese:
|
||||
- **본진 신규 4건**: `A-16` 영어 대명사 직역 [S1, 김도훈 2009 + Cho et al. 2019 ACL] · `A-18` 관계절 좌향 수식 [S2, 박옥수 2018 + 김채은 2021] · `A-19` 이중 조사 결합 [S2, 김정우 2007 + 김순영 2012, caveat C5로 단순 ~의 명시 제외] · `E-7` 청자 경어법 일관성 손실 [S2 estimated, 김혜영 2019, caveat C1로 estimated 플래그]
|
||||
- **본진 hold 1건**: `A-17` 무정물·추상명사 '-들' 부착 [학술 anchor 곽은주·진실로 2011 + 전영철 2007 + 김순영 2012 강함, 다만 외부 회차(2026-05-07 위키 6편) + v1.6 input 5편 모두 양성 0건 → NMT 원본 출력 회차 후 v2.1 재평가. ID 비워둠 — patternID 안정성 보존. metric `deul_overuse_rate` + 사전 25종은 검증용 보존]
|
||||
- **본진 보강**: `A-15`에 사역 타동사형·인지·발화 동사·이중주어 구문 3축 처방 추가(이영옥 2001 + 김정우 2007) · `A-7`에 light verb construction 일반화(have/make/take/give + 명사) 처방 + 5건 verbatim 예문(김정우 2007 + 이근희 2005) · `F-4`에 영어 명사화 접미사 4종(-tion/-ment/-ness/-ity) 한국어 명사 직역 통합 처방(김정우 2007) · `E-2`에 진행형 '~고 있다' 자동 매핑 처방 추가(김혜영 2019)
|
||||
- **post-editese metric-only 트랙**: simplification·normalisation·interference 3축은 본진 ID 미부여, `metrics_v2.py` 14개 신규 함수로 운영. caveat C3에 따라 모든 metric에 `speculative: true` 플래그 권고. 본진 패턴 → metric 양방향 연계
|
||||
- **외부 SSOT scholarship.md**: 학술 전문(8유형 한국 번역학계 계보 + Baker·Toury·Laviosa·Chesterman·Toral 등 국제 이론 + 보고서 caveat 6건 verbatim)을 외부 파일로 분리. 본진 SSOT는 패턴 행마다 `source_anchor` + `see_scholarship` 한 줄 메타로 가리킴 — 본진 슬림성 유지
|
||||
- **카테고리 호환성**: A·E 카테고리만 확장(A-15→A-19, E-6→E-7). 기존 A-1~A-15·E-1~E-6 본문 무수정. 새 K 카테고리 신설 거부 — 본진 패턴 ID 참조 안정성 보존
|
||||
- **caveat 적용 게이트**: C1(김혜영 2019 정량 미확인 → E-7 estimated 플래그) · C2(NMT 마케팅 편향 → 모델별 가중치 거부) · C3(post-editese 한국어 미검증 → metric-only 트랙) · C5(단순 ~의 학계 합의 부재 → A-19 정의에서 명시 제외) · C6(LLM 빠른 진화 → 'valid as of 2026-05' 명기)
|
||||
- **분류 체계의 새 차원**: v2.0은 한국 번역학계 정통성 계보(이영옥 2001~김혜영 2019)를 본진에 통합한 첫 회차. v1.6의 KatFish/LREAD 외부 정량 신호와 결합하여 **이론적 토대(8유형) + 정량 검증(KatFish) + 컴퓨테이션 검출(metric_v2)** 3축으로 확장
|
||||
|
||||
- **v1.6** (2026-05-06): **본진 신규 5건 + 본진 보강 2건 + hold 2건** — 외부 정량 연구(KatFish, Park et al. 인간 470 vs LLM 1,624편 / 에세이·시·초록 + LREAD 인간 판독 실험) 기반 9건 후보 중 7건 본진 반영, 2건 풀 보존:
|
||||
- **본진 신규**: `C-11` 연결어미 뒤 쉼표 [S1, 4.84배 분리도 — 단일 지표 최강] · `C-12` 쉼표 포함률 [S2, 2.32배] · `E-5` 쉼표 분절 평균 길이 [S2, 1.97배 · E-4 짝패턴 반대극] · `E-6` 쉼표 전후 POS 다양성 [S2, 2.44배 · 에세이/뉴스 한정 장르 가드] · `G-3` 안전 균형 lexicon [S2 · 정책·보고서 장르 한정]
|
||||
- **본진 보강**: `D-1`에 KatFish 검증 결산 lexicon 4종("결론적으로·따라서·이를 통해·그러므로") 정식 인용 + 합산 3회 초과 임계 + A-2·H-1과 가산 명시 · `F-4`에 한자어 명사화 접미사 3종("-성·-적·-화") 정식 명시 + 한 문서 12회 초과 S2 강화 임계
|
||||
- **hold (본진 미등재)**: BN/VX 띄어쓰기 규칙성(Park et al. 정량 셀 미공개, 사용자 코퍼스 baseline 확보 후 v1.7 검토) · 페르소나-레지스터 불일치(v1.5 monolith fast 1콜 + author-context 미주입과 충돌, opt-in 메타 부스터 설계 정리 후 재검토). 후보 발자취는 `_workspace/v1.6-2026-05-06/`에 보존
|
||||
- **분류 체계의 새 차원**: v1.6은 외부 학술 연구의 정량 신호를 본진에 통합한 첫 회차. 연결어미 뒤 쉼표 4.84배 분리도는 v1.1~v1.5.1까지 통틀어 가장 강한 단일 지표
|
||||
|
||||
- **v1.5.1** (2026-04-27): **본진 신규 1건** — `E-4` 단문 일변도 (복문·중문 부재) [S2]. 사용자 관찰: "지나친 단문은 AI 티가 난다. 사람이 작성할 때는 적절한 단문과 복문을 섞는다." E-1(문장 길이 표준편차)과 짝패턴이지만 별개 시그니처 — E-1은 "30~50자에 다 몰림", E-4는 "구조 자체가 단순 단문만". 인간 필자가 무의식적으로 만드는 단문+복문 혼합 리듬을 모사하지 못하는 AI 출력의 약점을 분류 체계로 명시화.
|
||||
|
||||
- **v1.3.1** (2026-04-25): **본진 신규 2건 + 본진 보강 3건** — 사용자 제공 Gemini API 키로 직접 호출한 회차 3 데이터(Gemini Pro 2.5 4편 약 10,058자) 분석 결과:
|
||||
- **본진 신규**: `C-10` 콜론 부제 헤딩 공식 [S2] · `D-7` 변환 공식 'X에서 Y로' [S2] (둘 다 Gemini-우세 시그니처)
|
||||
- **본진 보강**: `D-4` Gemini hype 어휘 셋 추가 (압도적·막강한·폭발적·파격적·대대적·강력한) · `J-2` 빈도 임계 명시(한 문서 5회 초과 S2 강화) · `I-4` 권고형 결말 변종 추가 (~해야 한다·~해야 합니다, 정책 보고서 5회 초과 임계)
|
||||
|
|
|
|||
|
|
@ -1,269 +0,0 @@
|
|||
# Author Context Schema (v1.2~)
|
||||
|
||||
작가/작품별 voice profile을 humanize-korean 파이프라인에 명시 주입하는 양식. 분류 체계의 패턴이 작가 의도와 충돌할 때 제한적 무력화를 허용하되, **권한 위계**(`ai-tell-taxonomy.md` § 권한 위계)의 6개 규칙을 강제한다.
|
||||
|
||||
## 사용 시점
|
||||
|
||||
작가별 고유 voice가 객관 분류 패턴과 정면 충돌할 때만 사용한다. 예:
|
||||
- 단단한 서술체 voice를 가진 작가가 em-dash를 의도적 리듬 장치로 활용 → J-3 임계 완화
|
||||
- 작가/책 mandate가 "~수 있다 사용 권장" → A-10 무력화
|
||||
- 책 고유 메타포가 도구의 일반 윤문 대상이 되는 것을 차단 → do_not_extra 키워드
|
||||
|
||||
장르가 다르다는 이유만으로(예: "내 책은 단행본이니까") 무력화하지 않는다. 장르는 SKILL.md의 `genre_hint`로 처리된다.
|
||||
|
||||
## 4대 제약
|
||||
|
||||
1. **opt-in 명시 주입만 허용**. 자동 로드(프로젝트 CLAUDE.md 자동 파싱 등)는 거절.
|
||||
2. **자유 텍스트 mandate 금지**. 모든 override는 패턴 ID 또는 키워드 단위로 구조화.
|
||||
3. **무력화 불가 패턴 존재**. A-8(이중 피동), C-5(이모지), D-1~D-6(AI 특유 관용구)은 어떤 이유로도 끄지 못한다. `pattern_overrides`에 들어와도 `naturalness-reviewer`가 잔존을 다시 잡는다.
|
||||
4. **`naturalness-reviewer` 미주입**. voice profile은 `ai-tell-detector`·`korean-style-rewriter`·`content-fidelity-auditor` 3개 에이전트에만 주입된다. 분리 검증층을 한 층 남겨 두는 것이 도구의 신뢰성 근거다.
|
||||
|
||||
## 파일 위치
|
||||
|
||||
작업 cwd 또는 `_workspace/{run_id}/` 둘 중 한 곳에 `author-context.yaml` 파일로 둔다. 오케스트레이터가 Phase 0에서 다음 우선순위로 탐색한다.
|
||||
|
||||
1. `<cwd>/_workspace/{run_id}/author-context.yaml` (이번 실행 전용)
|
||||
2. `<cwd>/author-context.yaml` (프로젝트 단위 기본값)
|
||||
|
||||
탐색 결과가 없으면 voice profile 미주입 모드로 진행(v1.1과 동일 동작).
|
||||
|
||||
## 스키마
|
||||
|
||||
```yaml
|
||||
# author-context.yaml — voice profile 양식 (humanize-korean v1.2~)
|
||||
#
|
||||
# 권한 위계: ai-tell-taxonomy.md § "권한 위계 (Authority Hierarchy)" 참조
|
||||
# 자유 텍스트 mandate 금지. 패턴 ID 또는 키워드 단위만 허용.
|
||||
|
||||
version: "1.0"
|
||||
|
||||
# 메타 정보 (검증 로직에 영향 없음, 리포트·로그용)
|
||||
profile:
|
||||
author: "작가 식별자"
|
||||
work: "작품/문서 식별자"
|
||||
notes: "voice 특성 한 줄 요약"
|
||||
|
||||
# 패턴 무력화 — 분류 체계의 특정 패턴 ID에 한해 적용 강도 조절
|
||||
pattern_overrides:
|
||||
- id: "J-3" # 무력화 대상 패턴 ID (taxonomy의 ID 그대로)
|
||||
action: "relax" # disable | relax (둘 중 하나, 자유 텍스트 금지)
|
||||
multiplier: 2.0 # action=relax 일 때만 사용. taxonomy 기본 임계 × 배율
|
||||
# 일반 패턴 캡: ≤ 2.0 / D-1~D-6 캡: ≤ 1.5 / A-8·C-5: 1.0 고정
|
||||
reason: "작가가 em-dash를 의도적 리듬 장치로 활용"
|
||||
|
||||
- id: "A-10"
|
||||
action: "disable"
|
||||
reason: "프로젝트 CLAUDE.md mandate: '~수 있다' 사용 권장"
|
||||
|
||||
# 보호 키워드 화이트리스트
|
||||
# 이 키워드들이 포함된 span은 detector 단계에서 탐지 대상에서 제외된다.
|
||||
# 책 제목·작가 고유 메타포·시리즈 명·고유 어휘 등 보호하고 싶은 표현만.
|
||||
do_not_extra:
|
||||
- "기계의 지갑"
|
||||
- "지하경제 렌즈"
|
||||
|
||||
# 분리 검증 계약 — naturalness-reviewer voice-blind 강제 (§5)
|
||||
# 이 필드는 반드시 true. false로 설정하면 schema validator가 거부한다.
|
||||
# v1.2 권한 위계 §5: voice profile은 naturalness-reviewer에 도달하지 않는다.
|
||||
reviewer_contract:
|
||||
naturalness_reviewer_voice_blind: true
|
||||
|
||||
# 무력화 불가 패턴 (참고용 — 여기에 적어도 적용되지 않는다)
|
||||
# 다음 ID는 pattern_overrides에 disable/relax로 들어와도 거부된다.
|
||||
# - A-8 (이중 피동 ~되어진다)
|
||||
# - C-5 (이모지 남발)
|
||||
# - D-1 ~ D-6 (AI 특유 관용구)
|
||||
```
|
||||
|
||||
## 필드 명세
|
||||
|
||||
### `version`
|
||||
스키마 버전. 현재 `"1.0"`. 향후 스키마 변경 시 호환성 검증에 사용.
|
||||
|
||||
### `profile`
|
||||
메타 정보 블록. 검증 로직에 영향 없으며 리포트·로그·디버깅용. 모든 필드 선택사항.
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `author` | string | 작가 식별자 |
|
||||
| `work` | string | 작품·문서 식별자 |
|
||||
| `notes` | string | voice 특성 메모 |
|
||||
|
||||
### `pattern_overrides`
|
||||
무력화 규칙 배열. 각 항목은 다음 구조.
|
||||
|
||||
| 필드 | 타입 | 필수 | 설명 |
|
||||
|------|------|------|------|
|
||||
| `id` | string | 필수 | taxonomy의 패턴 ID (예: `J-3`, `A-10`) |
|
||||
| `action` | enum | 필수 | `disable` 또는 `relax`만 허용 |
|
||||
| `multiplier` | number | `action=relax`일 때만 | taxonomy 기본 임계 × 배율. 캡 적용 (아래 표) |
|
||||
| `reason` | string | 권장 | 무력화 사유 (감사 추적용) |
|
||||
|
||||
**Multiplier 캡** (taxonomy 권한 위계 §4):
|
||||
|
||||
| 패턴 종류 | 캡 | 비고 |
|
||||
|----------|----|------|
|
||||
| 일반 패턴(A-1~A-7, A-9, A-10~A-15, B-*, C-1~C-4, C-6~C-8, E-*, F-*, G-*, H-*, I-*, J-*) | `0.5 ≤ multiplier ≤ 2.0` | 작가 voice 보존 |
|
||||
| **D-1 ~ D-6** AI 특유 관용구 | `0.5 ≤ multiplier ≤ 1.5` | 단일 사용 leniency만. 임계 우회를 통한 사실상 disable 방지 |
|
||||
| **A-8** 이중 피동, **C-5** 이모지 | `multiplier = 1.0` 고정 | 임계 완화 자체 불허 |
|
||||
|
||||
**`action: "disable"` 거부 ID** (schema validator가 항목째 거부 + 경고 출력):
|
||||
- `A-8`, `C-5`, `D-1` ~ `D-6`
|
||||
|
||||
**금지 필드:** 자유 텍스트 명령(`mandate`, `instruction`, `note`, `voice_anchors` 등 모든 자연어 advisory). 추가 필드는 schema validator가 거부한다(무시되지 않고 명시적 reject — 사용자가 의도한 것일 수 있어 silent ignore는 위험).
|
||||
|
||||
### `do_not_extra`
|
||||
키워드 문자열 배열. detector가 이 키워드를 포함하는 span을 탐지에서 제외한다. 정규식 미지원, 정확한 부분 문자열 매칭만.
|
||||
|
||||
기존 do-not list(`rewriting-playbook.md` § 3 — 수치·고유명사·법률 조문 등)는 항상 보호된다. `do_not_extra`는 그 위에 사용자 정의 키워드를 추가하는 슬롯.
|
||||
|
||||
### `reviewer_contract`
|
||||
v1.2 권한 위계 §5 강제 메커니즘. 분리 검증층 보존을 schema 단계에서 contract로 잠근다.
|
||||
|
||||
| 필드 | 타입 | 필수 | 허용 값 | 설명 |
|
||||
|------|------|------|---------|------|
|
||||
| `naturalness_reviewer_voice_blind` | bool | 필수 | `true` 고정 | `false`로 설정 시 schema validator가 파일 전체를 거부 |
|
||||
|
||||
이 필드는 미래 schema 확장에서도 `true` 외 값을 허용하지 않는다. 분리 검증층은 도구 신뢰성의 근거이며 voice profile 사용자가 임의로 끄지 못한다.
|
||||
|
||||
## Schema validator 책임 (v1.2~)
|
||||
|
||||
검증기는 다음 위반을 거부(reject)하며, 거부 시 파일 전체가 미적용 처리된다(silent fallback 금지 — 명시적 에러로 사용자에게 보고).
|
||||
|
||||
1. **무력화 불가 ID의 disable**: `A-8`, `C-5`, `D-1`~`D-6`이 `action: "disable"`로 등재
|
||||
2. **Multiplier 캡 초과**: 일반 패턴 `multiplier > 2.0`, D-1~D-6 `multiplier > 1.5`, A-8·C-5 `multiplier ≠ 1.0`
|
||||
3. **금지 필드 등재**: `mandate`, `instruction`, `voice_anchors`, `note`(profile 외) 등 자유 텍스트 advisory
|
||||
4. **`reviewer_contract.naturalness_reviewer_voice_blind: false`**
|
||||
5. **Schema 이스케이프 시도** (prompt injection 방어): 모든 string 필드(reason, do_not_extra, profile.notes 등)에서 다음 패턴 거부
|
||||
- Triple backtick (` ``` `)
|
||||
- Role-prefix 토큰 (`system:`, `assistant:`, `user:`, `<|...|>` 등)
|
||||
- Markdown 헤딩 (`#`로 시작하는 줄)
|
||||
- 모델 제어 시퀀스 (`<|im_start|>`, `<|endoftext|>` 등)
|
||||
|
||||
거부 발생 시 출력 예:
|
||||
```
|
||||
[author-context error] Validation failed: pattern_overrides[0] — D-3 cannot be disabled (taxonomy authority §4). File rejected, voice profile not applied.
|
||||
```
|
||||
|
||||
## Telemetry (감사 추적)
|
||||
|
||||
오케스트레이터는 voice profile 적용 시 다음 정보를 `_workspace/{run_id}/voice_profile_log.json`에 기록한다.
|
||||
|
||||
```json
|
||||
{
|
||||
"applied": true,
|
||||
"schema_version": "1.0",
|
||||
"profile_meta": {"author": "...", "work": "..."},
|
||||
"overrides_accepted": [
|
||||
{"id": "J-3", "action": "relax", "multiplier": 2.0, "applied_threshold": 4},
|
||||
{"id": "A-10", "action": "disable"}
|
||||
],
|
||||
"overrides_rejected": [
|
||||
{"id": "D-3", "action": "disable", "reason": "permanent default-on per taxonomy §4"}
|
||||
],
|
||||
"do_not_extra_triggered": [
|
||||
{"keyword": "기계의 지갑", "occurrences": 3, "spans_protected": [142, 891, 1502]}
|
||||
],
|
||||
"naturalness_reviewer_received_voice_profile": false
|
||||
}
|
||||
```
|
||||
|
||||
이 로그는 §6 회귀 테스트 게이트가 measurable 하기 위한 필수 산출물. 외부 케이스 비교 시 voice profile이 없는 baseline run과 voice profile 있는 run의 finding 차이를 이 로그로 추적한다.
|
||||
|
||||
마지막 필드 `naturalness_reviewer_received_voice_profile: false`는 §5 분리 검증층 보존이 실제로 작동했음을 입증하는 audit 항목이다. 이 값이 `true`로 기록된 run은 즉시 incident로 처리하고 v1.2 회귀 게이트 리포트에 포함한다.
|
||||
|
||||
## Hard-block (caller/adapter 책임)
|
||||
|
||||
특정 경로(fiction·legal·third-party 등)에서 humanize-korean 호출 자체를 거부하는 hard-block은 **caller/adapter 책임**이다. 메인테이너 schema는 입력된 텍스트를 처리할 뿐, 어떤 종류의 텍스트인지 판단하지 않는다.
|
||||
|
||||
caller/adapter가 자체 schema에 `hard_blocks` 필드를 정의해 사용하는 것은 권장되나, 메인테이너 schema의 표준 부분이 아니다. PR #3의 `proposals/voice-aware-adapter.md`에 caller-side hard-block 패턴이 reference로 제공된다.
|
||||
|
||||
## 무력화 불가 패턴 처리
|
||||
|
||||
다음 ID는 `pattern_overrides`의 `action: "disable"`로 등재되면 schema validator가 거부한다(silent ignore 아닌 명시적 reject).
|
||||
|
||||
- **A-8** 이중 피동 "~되어진다 / ~지게 된다"
|
||||
- **C-5** 이모지 남발
|
||||
- **D-1 ~ D-6** AI 특유 관용구 카테고리 전체
|
||||
|
||||
`action: "relax"`의 경우:
|
||||
- A-8·C-5: `multiplier = 1.0` 외 거부 (사실상 임계 완화 불허)
|
||||
- D-1~D-6: `multiplier ≤ 1.5` 까지만 허용 (단일 사용 leniency)
|
||||
|
||||
근거: `ai-tell-taxonomy.md` § 권한 위계 §4. 어떤 작가 의도로도 정당화되지 않는 결정적 AI 시그니처.
|
||||
|
||||
## 에이전트별 주입 정책
|
||||
|
||||
| 에이전트 | voice profile 주입 | 비고 |
|
||||
|----------|-------------------|------|
|
||||
| `korean-ai-tell-taxonomist` | 미주입 | 분류 체계 자체를 정의하는 역할. voice profile에 좌우되면 SSOT가 흔들림 |
|
||||
| `ai-tell-detector` | **주입** | `pattern_overrides`로 탐지 우회, `do_not_extra`로 보호 키워드 |
|
||||
| `korean-style-rewriter` | **주입** | 무력화된 패턴은 윤문 대상에서 제외 |
|
||||
| `content-fidelity-auditor` | **주입** | `do_not_extra` 키워드는 절대 보존 대상 추가 |
|
||||
| `naturalness-reviewer` | **미주입** | 분리 검증층 보존. voice profile을 모르는 외부 시각이 한 층 남아야 함 |
|
||||
| `humanize-web-architect` | 해당 없음 | 웹 확장 모드 전용 |
|
||||
|
||||
## 잔존 패턴의 처리
|
||||
|
||||
`naturalness-reviewer`가 voice profile을 모르기 때문에, 무력화된 패턴이 잔존 finding으로 다시 잡힐 수 있다. 이 경우 오케스트레이터가 다음 규칙으로 처리한다.
|
||||
|
||||
- 무력화된 패턴 ID에 해당하는 잔존 finding → `accepted_by_voice_profile` 플래그를 달고 등급 계산에서 제외
|
||||
- 무력화 불가 패턴(A-8/C-5/D-*)이 잔존하면 → 무력화 시도와 무관하게 정상 잔존으로 처리, 2차 윤문 트리거
|
||||
|
||||
이 규칙으로 분리 검증층의 독립성을 유지하면서도 voice profile 사용자가 같은 패턴으로 반복 재윤문되지 않도록 한다.
|
||||
|
||||
## 회귀 테스트 게이트
|
||||
|
||||
이 스키마에 새로운 무력화 옵션(예: 새 `action` 값, 새 필드)을 추가할 때는 외부 케이스 2~3건(다른 작가·다른 장르)에서 false positive·false negative 비교 리포트를 통과해야 한다. 단일 사용자 self-reported 결과만으로 스키마를 확장하지 않는다(taxonomy 권한 위계 §6).
|
||||
|
||||
## 예시 — 단행본 비소설 작가
|
||||
|
||||
Issue #1에서 보고된 케이스: 단행본 비소설 작가가 단단한 서술체 voice + em-dash 리듬 장치를 사용. 프로젝트 CLAUDE.md mandate에 "~수 있다 사용 권장" 명시.
|
||||
|
||||
```yaml
|
||||
version: "1.0"
|
||||
|
||||
profile:
|
||||
author: "Won Seongmuk"
|
||||
work: "단행본 비소설 (8.5만 자, 9챕터+에필로그)"
|
||||
notes: "단단한 서술체, em-dash 리듬 장치, 1인칭 진입+분석 결합"
|
||||
|
||||
pattern_overrides:
|
||||
- id: "J-3"
|
||||
action: "relax"
|
||||
multiplier: 2.0
|
||||
reason: "em-dash를 의도적 리듬 장치로 채용 — 8.5만 자에서 150회 자연 등장"
|
||||
- id: "A-10"
|
||||
action: "disable"
|
||||
reason: "프로젝트 CLAUDE.md mandate: '단정적 예측 ~할 것이다 금지, ~수 있다 사용'"
|
||||
- id: "E-2"
|
||||
action: "relax"
|
||||
multiplier: 1.8
|
||||
reason: "단단한 서술체 voice의 의도된 종결어미 반복"
|
||||
|
||||
do_not_extra:
|
||||
- "1인칭 진입"
|
||||
- "장면→충돌→시도→결과→성찰→원칙"
|
||||
|
||||
reviewer_contract:
|
||||
naturalness_reviewer_voice_blind: true
|
||||
```
|
||||
|
||||
이 예시에서 `E-2`는 동일 종결어미 반복 패턴이지만 D 카테고리가 아니므로 일반 캡(2.0) 적용 가능. 단, `naturalness-reviewer`는 voice profile을 모르므로 잔존 시그널을 다시 잡을 것이고, 오케스트레이터가 `accepted_by_voice_profile` 플래그로 처리한다.
|
||||
|
||||
## 관련 자료
|
||||
|
||||
- **메인테이너 SSOT**: `references/ai-tell-taxonomy.md` § "권한 위계 (Authority Hierarchy)" §1~§6
|
||||
- **다운스트림 caller reference (PR #3 proposal)**: `references/proposals/voice-aware-adapter.md` — 어댑터가 humanize-korean을 wrap할 때의 호출 envelope·hard-block·post-pass 패턴 reference. proposals/와 메인테이너 schema가 다르면 메인테이너 schema가 우선.
|
||||
- **Telemetry 로그 위치**: `_workspace/{run_id}/voice_profile_log.json`
|
||||
|
||||
## 변경 이력
|
||||
|
||||
- **v1.0** (2026-04-25): 초기 스키마 정의. v1.2 PR #3에서 도입.
|
||||
- **v1.0.1** (2026-04-25): PR #3 어댑터 reference의 통찰 반영(PR #4).
|
||||
- `threshold` (절대값) → `multiplier` (taxonomy 기본 임계 × 배율)로 표현 통일
|
||||
- Multiplier 캡: 일반 ≤ 2.0, D-1~D-6 ≤ 1.5, A-8·C-5 = 1.0 고정
|
||||
- `reviewer_contract.naturalness_reviewer_voice_blind: true` 강제 필드 신설 (§5 schema 단계 강제)
|
||||
- Schema validator 책임 강화: 무력화 불가 disable 거부, multiplier 캡 위반 거부, 자유 텍스트 advisory 거부, prompt injection 방어 (escape character 검증)
|
||||
- Telemetry 정책 명문화: `voice_profile_log.json` 발행, `naturalness_reviewer_received_voice_profile` 감사 항목 포함
|
||||
- Hard-block은 caller/adapter 책임 명시 (메인테이너 schema 외)
|
||||
102
.claude/skills/humanize-korean/references/baseline.json
Normal file
102
.claude/skills/humanize-korean/references/baseline.json
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
{
|
||||
"version": "v1.6-2026-05-06",
|
||||
"source": "KatFish (Park et al., 인간 470 vs LLM 1,624편 / 에세이 771·시 945·초록 378) + LREAD 인간 판독 실험",
|
||||
"notes": "metric-engineer는 이 baseline을 그대로 import해 z-score 계산에 사용한다. 미공개 셀은 null. 사용자 코퍼스로 보강 시 source에 ',user-corpus-{date}' 추가.",
|
||||
"genres": {
|
||||
"essay": {
|
||||
"comma_inclusion_rate": {"human": 26.31, "ai": 61.03, "ratio": 2.32, "unit": "percent"},
|
||||
"comma_usage_rate": {"human": 1.13, "ai": 2.56, "ratio": 2.27, "unit": "per_sentence"},
|
||||
"comma_relative_position": {"human": 0.10, "ai": 0.20, "ratio": 2.00, "unit": "normalized_0to1"},
|
||||
"comma_segment_length": {"human": 4.35, "ai": 8.56, "ratio": 1.97, "unit": "eojeol_per_segment"},
|
||||
"comma_pos_diversity": {"human": 24.38, "ai": 59.39, "ratio": 2.44, "unit": "pos_count"},
|
||||
"ending_comma_rate": {"human": 4.10, "ai": 19.83, "ratio": 4.84, "unit": "percent"}
|
||||
},
|
||||
"poetry": {
|
||||
"comma_inclusion_rate": {"human": 27.01, "ai": 42.90, "ratio": 1.59, "unit": "percent"},
|
||||
"comma_usage_rate": {"human": 2.61, "ai": 4.84, "ratio": 1.85, "unit": "per_sentence"},
|
||||
"comma_relative_position": {"human": 0.18, "ai": 0.27, "ratio": 1.50, "unit": "normalized_0to1"},
|
||||
"comma_segment_length": null,
|
||||
"comma_pos_diversity": {"human": 23.13, "ai": 23.86, "ratio": 1.03, "unit": "pos_count", "note": "시 장르는 분리도 약함 — 장르 가드 필요"},
|
||||
"ending_comma_rate": {"human": 4.68, "ai": 15.57, "ratio": 3.33, "unit": "percent"}
|
||||
},
|
||||
"abstract": {
|
||||
"comma_inclusion_rate": {"human": 47.48, "ai": 65.21, "ratio": 1.37, "unit": "percent"},
|
||||
"comma_usage_rate": {"human": 1.73, "ai": 2.40, "ratio": 1.39, "unit": "per_sentence"},
|
||||
"comma_relative_position": {"human": 0.15, "ai": 0.24, "ratio": 1.60, "unit": "normalized_0to1", "note": "보고서 본문 추정치(상세 셀 미공개)"},
|
||||
"comma_segment_length": null,
|
||||
"comma_pos_diversity": null,
|
||||
"ending_comma_rate": {"human": 13.27, "ai": 28.01, "ratio": 2.11, "unit": "percent"}
|
||||
},
|
||||
"news": null,
|
||||
"qa": null,
|
||||
"blog": null
|
||||
},
|
||||
"global_average": {
|
||||
"comma_inclusion_rate": {"human": 33.60, "ai": 56.38, "ratio": 1.68, "unit": "percent"},
|
||||
"comma_usage_rate": {"human": 1.82, "ai": 3.27, "ratio": 1.79, "unit": "per_sentence"},
|
||||
"comma_relative_position": {"human": 0.14, "ai": 0.24, "ratio": 1.65, "unit": "normalized_0to1"},
|
||||
"comma_segment_length": {"human": 5.13, "ai": 7.41, "ratio": 1.45, "unit": "eojeol_per_segment"},
|
||||
"comma_pos_diversity": {"human": 30.12, "ai": 48.40, "ratio": 1.61, "unit": "pos_count"}
|
||||
},
|
||||
"z_score_thresholds": {
|
||||
"high_risk": 1.0,
|
||||
"comments": "보고서 §탐지 알고리즘에서 z>1.0 시 가산. 문서별 측정값을 장르 baseline과 비교한 z-score가 1.0 초과면 해당 지표 가산. news/qa/blog는 사용자 코퍼스로 보강 필요. 시 장르는 comma_pos_diversity 분리도가 약하므로 (genre=='poetry') 가드 권장."
|
||||
},
|
||||
"lexicons": {
|
||||
"conclusion_pivot": [
|
||||
"결론적으로",
|
||||
"따라서",
|
||||
"이를 통해",
|
||||
"그러므로"
|
||||
],
|
||||
"safe_balance": [
|
||||
"양쪽 모두",
|
||||
"두 가지 모두",
|
||||
"장점도 있지만",
|
||||
"신중하게",
|
||||
"균형"
|
||||
],
|
||||
"hanja_nominalizers": [
|
||||
"성",
|
||||
"적",
|
||||
"화"
|
||||
],
|
||||
"lexicon_thresholds": {
|
||||
"conclusion_pivot": {"per_doc_count": 3, "severity": "S1", "merges_into": "D-1"},
|
||||
"safe_balance": {"per_doc_count": 4, "severity": "S2", "new_code": "G-3"},
|
||||
"hanja_nominalizers": {"per_doc_density": 12, "severity": "S2", "merges_into": "F-4", "unit": "occurrences_per_doc"}
|
||||
}
|
||||
},
|
||||
"lread_calibration": {
|
||||
"human_intuition_acc": 0.60,
|
||||
"human_intuition_p_value": 0.362,
|
||||
"human_intuition_chance_diff": "no statistical difference",
|
||||
"rubric_acc": 0.90,
|
||||
"rubric_fisher_p": 0.015,
|
||||
"rubric_cohens_h": 0.73,
|
||||
"ai_essay_false_negative_drop": {
|
||||
"before": 0.500,
|
||||
"after": 0.083,
|
||||
"fisher_p": 0.003
|
||||
},
|
||||
"zero_shot_llm_majority_acc": 0.9667,
|
||||
"comments": "Phase 1 직관 60%는 chance와 통계적 차이 없음(p=.362) — 일반 독자는 AI 한글 식별 어려움. Phase 2 루브릭(쉼표·연결어미·결산·균형 어휘 등 우리 분류 체계와 정렬)을 제공하면 90%로 급상승. v1.6 detector는 LREAD 루브릭의 측정 지표를 그대로 흡수해 인간 판독 90% 수준 도달이 1차 KPI."
|
||||
},
|
||||
"v1_6_promotion_targets": {
|
||||
"promote": [
|
||||
{"cand_id": "cand-v16-001", "new_code": "C-11", "name": "ending_comma_rate", "severity": "S1", "primary_metric": "ending_comma_rate"},
|
||||
{"cand_id": "cand-v16-002", "new_code": "C-12", "name": "comma_inclusion_rate", "severity": "S2", "primary_metric": "comma_inclusion_rate"},
|
||||
{"cand_id": "cand-v16-003", "new_code": "E-5", "name": "comma_segment_length", "severity": "S2", "primary_metric": "comma_segment_length"},
|
||||
{"cand_id": "cand-v16-004", "new_code": "E-6", "name": "comma_pos_diversity", "severity": "S2", "primary_metric": "comma_pos_diversity", "genre_guard": ["poetry"]},
|
||||
{"cand_id": "cand-v16-007", "new_code": "G-3", "name": "safe_balance_lexicon", "severity": "S2", "primary_metric": "safe_balance"}
|
||||
],
|
||||
"merge": [
|
||||
{"cand_id": "cand-v16-006", "into": ["D-1", "H-1", "A-2"], "patch": "lexicon 4종 정식 인용 + 임계"},
|
||||
{"cand_id": "cand-v16-008", "into": ["F-4"], "patch": "hanja_nominalizers 3종 명시 + 한 문서 12회 초과 S2 강화 임계"}
|
||||
],
|
||||
"hold": [
|
||||
{"cand_id": "cand-v16-005", "name": "spacing_regularness", "reason": "보고서 본문에 정량 셀 미공개. 사용자 코퍼스 baseline 확보 후 v1.7 검토. 탐지 전용 신호로만 채용 가능, 윤문 처방 없음(맞춤법 일부러 틀리게 만들지 않음)"},
|
||||
{"cand_id": "cand-v16-009", "name": "persona_register_mismatch", "reason": "v1.5 monolith fast + author-context 미주입과 충돌. 메타 부스터로만 작동하는 옵트인 설계가 정리되면 v1.7+ 검토"}
|
||||
]
|
||||
}
|
||||
}
|
||||
118
.claude/skills/humanize-korean/references/baseline_v2.json
Normal file
118
.claude/skills/humanize-korean/references/baseline_v2.json
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
{
|
||||
"version": "v2.0-baseline-diff-2026-05-07",
|
||||
"source": "Placeholder. Toral 2019 simplification·normalisation·interference 정의 + 보고서 T1~T8 ko_manifestation 추정치. 비번역 한국어 (Sejong corpus·국립국어원 모두의 말뭉치 등) 정밀 측정은 별도 calibration 회차에서 수행.",
|
||||
"notes": "metric-engineer(Phase 3b) 산출. ALL cells carry _placeholder: true. Phase 6 integrator는 본진 baseline.json에 merge할 때 placeholder 플래그를 반드시 보존하고, calibration 완료 셀만 플래그를 해제한다.",
|
||||
"calibration_due": true,
|
||||
"genres": {
|
||||
"essay": {
|
||||
"lexical_diversity_ttr": {"mean": 0.62, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "simplification", "interpretation": "low = AI-like (repetition)"},
|
||||
"lexical_density": {"mean": 0.30, "stdev": 0.07, "_placeholder": true, "calibration_due": true, "axis": "simplification", "interpretation": "low = AI-like (function-word heavy)"},
|
||||
"ending_diversity": {"mean": 0.55, "stdev": 0.12, "_placeholder": true, "calibration_due": true, "axis": "simplification", "interpretation": "low = monotonic endings"},
|
||||
"normalisation_score": {"mean": 0.50, "stdev": 0.15, "_placeholder": true, "calibration_due": true, "axis": "normalisation", "interpretation": "high = AI-like (-한다/-된다/-이다 concentration)"},
|
||||
"da_streak_rate": {"mean": 0.4, "stdev": 0.6, "_placeholder": true, "calibration_due": true, "axis": "normalisation", "unit": "streaks_per_doc", "interpretation": "high = AI-like"},
|
||||
"inanimate_subject_rate": {"mean": 0.10, "stdev": 0.06, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T1", "interpretation": "high = AI-like (translationese)"},
|
||||
"by_passive_count": {"mean": 0.5, "stdev": 1.0, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2a", "unit": "per_doc", "interpretation": "high = AI-like"},
|
||||
"double_passive_count": {"mean": 0.2, "stdev": 0.6, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2b", "unit": "per_doc", "interpretation": "high = strong S2 signal"},
|
||||
"pronoun_density": {"mean": 0.012, "stdev": 0.010, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T3", "interpretation": "high = AI-like (he/she literal mapping)"},
|
||||
"deul_overuse_rate": {"mean": 0.005, "stdev": 0.008, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T4", "interpretation": "high = AI-like (-들 over-use)"},
|
||||
"relative_clause_nesting": {"mean": 0.3, "stdev": 0.5, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T5", "unit": "sentences_per_doc", "interpretation": "high = AI-like (left-modifier overload)"},
|
||||
"have_make_literal_count": {"mean": 0.4, "stdev": 0.8, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T6", "unit": "per_doc", "interpretation": "high = AI-like"},
|
||||
"double_particle_count": {"mean": 0.3, "stdev": 0.7, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T7", "unit": "per_doc", "interpretation": "high = AI-like"},
|
||||
"progressive_aspect_rate": {"mean": 0.10, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T8b", "interpretation": "high = AI-like (~고 있다 literal mapping)"}
|
||||
},
|
||||
"news": {
|
||||
"lexical_diversity_ttr": {"mean": 0.65, "stdev": 0.07, "_placeholder": true, "calibration_due": true, "axis": "simplification"},
|
||||
"lexical_density": {"mean": 0.34, "stdev": 0.07, "_placeholder": true, "calibration_due": true, "axis": "simplification"},
|
||||
"ending_diversity": {"mean": 0.40, "stdev": 0.10, "_placeholder": true, "calibration_due": true, "axis": "simplification", "note": "뉴스는 평서형 단조성이 일부 정상"},
|
||||
"normalisation_score": {"mean": 0.75, "stdev": 0.12, "_placeholder": true, "calibration_due": true, "axis": "normalisation", "note": "뉴스 장르는 -이다/-한다 비중이 높음"},
|
||||
"da_streak_rate": {"mean": 1.0, "stdev": 1.0, "_placeholder": true, "calibration_due": true, "axis": "normalisation", "unit": "streaks_per_doc"},
|
||||
"inanimate_subject_rate": {"mean": 0.18, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T1"},
|
||||
"by_passive_count": {"mean": 0.8, "stdev": 1.2, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2a", "unit": "per_doc"},
|
||||
"double_passive_count": {"mean": 0.3, "stdev": 0.7, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2b", "unit": "per_doc"},
|
||||
"pronoun_density": {"mean": 0.015, "stdev": 0.010, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T3"},
|
||||
"deul_overuse_rate": {"mean": 0.006, "stdev": 0.008, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T4"},
|
||||
"relative_clause_nesting": {"mean": 0.4, "stdev": 0.6, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T5", "unit": "sentences_per_doc"},
|
||||
"have_make_literal_count": {"mean": 0.5, "stdev": 0.9, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T6", "unit": "per_doc"},
|
||||
"double_particle_count": {"mean": 0.4, "stdev": 0.8, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T7", "unit": "per_doc"},
|
||||
"progressive_aspect_rate": {"mean": 0.08, "stdev": 0.06, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T8b"}
|
||||
},
|
||||
"blog": {
|
||||
"lexical_diversity_ttr": {"mean": 0.60, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "simplification"},
|
||||
"lexical_density": {"mean": 0.27, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "simplification"},
|
||||
"ending_diversity": {"mean": 0.65, "stdev": 0.12, "_placeholder": true, "calibration_due": true, "axis": "simplification", "note": "블로그는 종결 다양성 자연 높음"},
|
||||
"normalisation_score": {"mean": 0.40, "stdev": 0.18, "_placeholder": true, "calibration_due": true, "axis": "normalisation"},
|
||||
"da_streak_rate": {"mean": 0.3, "stdev": 0.6, "_placeholder": true, "calibration_due": true, "axis": "normalisation", "unit": "streaks_per_doc"},
|
||||
"inanimate_subject_rate": {"mean": 0.08, "stdev": 0.06, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T1"},
|
||||
"by_passive_count": {"mean": 0.3, "stdev": 0.7, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2a", "unit": "per_doc"},
|
||||
"double_passive_count": {"mean": 0.2, "stdev": 0.5, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2b", "unit": "per_doc"},
|
||||
"pronoun_density": {"mean": 0.018, "stdev": 0.012, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T3", "note": "1인칭 사용 빈도 높음"},
|
||||
"deul_overuse_rate": {"mean": 0.004, "stdev": 0.007, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T4"},
|
||||
"relative_clause_nesting": {"mean": 0.2, "stdev": 0.4, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T5", "unit": "sentences_per_doc"},
|
||||
"have_make_literal_count": {"mean": 0.3, "stdev": 0.7, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T6", "unit": "per_doc"},
|
||||
"double_particle_count": {"mean": 0.2, "stdev": 0.5, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T7", "unit": "per_doc"},
|
||||
"progressive_aspect_rate": {"mean": 0.12, "stdev": 0.10, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T8b"}
|
||||
},
|
||||
"qa": {
|
||||
"lexical_diversity_ttr": {"mean": 0.58, "stdev": 0.09, "_placeholder": true, "calibration_due": true, "axis": "simplification"},
|
||||
"lexical_density": {"mean": 0.25, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "simplification"},
|
||||
"ending_diversity": {"mean": 0.50, "stdev": 0.13, "_placeholder": true, "calibration_due": true, "axis": "simplification"},
|
||||
"normalisation_score": {"mean": 0.55, "stdev": 0.18, "_placeholder": true, "calibration_due": true, "axis": "normalisation"},
|
||||
"da_streak_rate": {"mean": 0.5, "stdev": 0.7, "_placeholder": true, "calibration_due": true, "axis": "normalisation", "unit": "streaks_per_doc"},
|
||||
"inanimate_subject_rate": {"mean": 0.15, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T1"},
|
||||
"by_passive_count": {"mean": 0.4, "stdev": 0.8, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2a", "unit": "per_doc"},
|
||||
"double_passive_count": {"mean": 0.3, "stdev": 0.6, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2b", "unit": "per_doc"},
|
||||
"pronoun_density": {"mean": 0.014, "stdev": 0.010, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T3"},
|
||||
"deul_overuse_rate": {"mean": 0.007, "stdev": 0.010, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T4"},
|
||||
"relative_clause_nesting": {"mean": 0.25, "stdev": 0.5, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T5", "unit": "sentences_per_doc"},
|
||||
"have_make_literal_count": {"mean": 0.5, "stdev": 0.9, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T6", "unit": "per_doc"},
|
||||
"double_particle_count": {"mean": 0.3, "stdev": 0.7, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T7", "unit": "per_doc"},
|
||||
"progressive_aspect_rate": {"mean": 0.10, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T8b"}
|
||||
},
|
||||
"dialogue": {
|
||||
"lexical_diversity_ttr": {"mean": 0.55, "stdev": 0.10, "_placeholder": true, "calibration_due": true, "axis": "simplification", "note": "대화는 반복이 자연스러움"},
|
||||
"lexical_density": {"mean": 0.22, "stdev": 0.08, "_placeholder": true, "calibration_due": true, "axis": "simplification"},
|
||||
"ending_diversity": {"mean": 0.70, "stdev": 0.12, "_placeholder": true, "calibration_due": true, "axis": "simplification", "note": "대화는 종결어미 자연 다양"},
|
||||
"normalisation_score": {"mean": 0.20, "stdev": 0.15, "_placeholder": true, "calibration_due": true, "axis": "normalisation", "note": "대화는 -한다/-된다/-이다 거의 안 씀"},
|
||||
"da_streak_rate": {"mean": 0.1, "stdev": 0.3, "_placeholder": true, "calibration_due": true, "axis": "normalisation", "unit": "streaks_per_doc"},
|
||||
"inanimate_subject_rate": {"mean": 0.05, "stdev": 0.05, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T1"},
|
||||
"by_passive_count": {"mean": 0.1, "stdev": 0.3, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2a", "unit": "per_doc"},
|
||||
"double_passive_count": {"mean": 0.1, "stdev": 0.4, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T2b", "unit": "per_doc"},
|
||||
"pronoun_density": {"mean": 0.020, "stdev": 0.015, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T3"},
|
||||
"deul_overuse_rate": {"mean": 0.003, "stdev": 0.006, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T4"},
|
||||
"relative_clause_nesting": {"mean": 0.1, "stdev": 0.3, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T5", "unit": "sentences_per_doc"},
|
||||
"have_make_literal_count": {"mean": 0.2, "stdev": 0.5, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T6", "unit": "per_doc"},
|
||||
"double_particle_count": {"mean": 0.1, "stdev": 0.3, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T7", "unit": "per_doc"},
|
||||
"progressive_aspect_rate": {"mean": 0.15, "stdev": 0.10, "_placeholder": true, "calibration_due": true, "axis": "interference", "type": "T8b"}
|
||||
}
|
||||
},
|
||||
"axis_definitions": {
|
||||
"simplification": {
|
||||
"source": "Toral 2019; 보고서 line 351",
|
||||
"metrics": ["lexical_diversity_ttr", "lexical_density", "ending_diversity"],
|
||||
"interpretation": "low values → AI-like (repetition, function-word heavy, monotonic endings)"
|
||||
},
|
||||
"normalisation": {
|
||||
"source": "Baker 1993; 보고서 line 352",
|
||||
"metrics": ["normalisation_score", "da_streak_rate"],
|
||||
"interpretation": "high values → AI-like (-한다/-된다/-이다 concentration, '-다' streak runs)"
|
||||
},
|
||||
"interference": {
|
||||
"source": "Toury 1995 law of interference; 보고서 line 353",
|
||||
"metrics": [
|
||||
"inanimate_subject_rate",
|
||||
"by_passive_count",
|
||||
"double_passive_count",
|
||||
"pronoun_density",
|
||||
"deul_overuse_rate",
|
||||
"relative_clause_nesting",
|
||||
"have_make_literal_count",
|
||||
"double_particle_count",
|
||||
"progressive_aspect_rate"
|
||||
],
|
||||
"interpretation": "high values → AI-like (영어 통사 보존)"
|
||||
}
|
||||
},
|
||||
"z_score_thresholds": {
|
||||
"comments": "Phase 6 integrator는 v1.6 thresholds(z>1.0 가산)를 그대로 계승. v2.0 placeholder는 calibration 완료 후 본진 baseline.json에 merge."
|
||||
}
|
||||
}
|
||||
404
.claude/skills/humanize-korean/references/metrics.py
Normal file
404
.claude/skills/humanize-korean/references/metrics.py
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
"""Humanize KR v1.6 quantitative metrics calculator.
|
||||
|
||||
External pre-processor for the monolith fast path. Run BEFORE the monolith
|
||||
agent — its output (prepended to the input text) gives the LLM a numerical
|
||||
baseline read so it does not waste tool-call budget computing comma rates
|
||||
or counting hanja suffixes.
|
||||
|
||||
Hard rule: standard library ONLY (json/re/math/collections/os/sys/argparse).
|
||||
No konlpy/bareun/mecab/spaCy. We approximate morphological analysis with
|
||||
regex + a small hanja suffix dictionary. Final judgement is monolith's job.
|
||||
|
||||
CLI:
|
||||
python metrics.py --input run/01_input.txt \
|
||||
--genre essay --output run/00_metrics.json
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from typing import Any
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module-level constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
VERSION = "v1.6"
|
||||
|
||||
# Connective endings (-고, -며, -지만, -면서, -아서, -어서) followed by a comma.
|
||||
# All of these end at a syllable boundary, so we anchor to the syllable + ",".
|
||||
# Use a non-capturing group; allow space before comma (Korean writers
|
||||
# sometimes type "...고 ,").
|
||||
_ENDING_COMMA_RE = re.compile(
|
||||
r"(?:고|며|지만|면서|아서|어서)\s*,"
|
||||
)
|
||||
|
||||
# Eojeol = whitespace-separated token. Strip trailing punctuation for length
|
||||
# accounting but keep raw token for diversity / suffix tests.
|
||||
_EOJEOL_SPLIT_RE = re.compile(r"\s+")
|
||||
|
||||
# Sentence boundary: . ! ? + closing quote/bracket optional + whitespace or EOS.
|
||||
# Korean text rarely uses semicolons; we keep them out to avoid false splits.
|
||||
_SENTENCE_SPLIT_RE = re.compile(r"(?<=[\.!?。])\s+")
|
||||
|
||||
# Hanja-style nominalizer suffixes: 성, 적, 화. We only count them when the
|
||||
# *token* ends with one of these AND has at least 2 chars before — that
|
||||
# excludes the standalone particles "적" / "성" / "화" and short adverbial
|
||||
# uses. We also skip pure-Hangul exact matches in a small block-list.
|
||||
_HANJA_SUFFIXES = ("성", "적", "화")
|
||||
_HANJA_BLOCK = {
|
||||
# Common false positives — bare verbs / nouns that happen to end in these
|
||||
# syllables but are not -성/-적/-화 nominalizations.
|
||||
"있는화", "되는화", # placeholder — extend as needed
|
||||
"맞아", "와서", # not actually -화 but caught for safety
|
||||
}
|
||||
|
||||
# Tokens we never count for hanja density: numerals, English, single-char.
|
||||
_PUNCT_STRIP_RE = re.compile(r"[\.,!?;:\(\)\[\]\{\}\"'`~、。“”‘’\-]+")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _split_sentences(text: str) -> list[str]:
|
||||
text = text.strip()
|
||||
if not text:
|
||||
return []
|
||||
parts = _SENTENCE_SPLIT_RE.split(text)
|
||||
# Each `parts[i]` may contain newlines; flatten on \n too.
|
||||
out: list[str] = []
|
||||
for p in parts:
|
||||
for line in p.split("\n"):
|
||||
line = line.strip()
|
||||
if line:
|
||||
out.append(line)
|
||||
return out
|
||||
|
||||
|
||||
def _eojeols(text: str) -> list[str]:
|
||||
return [tok for tok in _EOJEOL_SPLIT_RE.split(text.strip()) if tok]
|
||||
|
||||
|
||||
def _strip_punct(token: str) -> str:
|
||||
return _PUNCT_STRIP_RE.sub("", token)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6 + 2 metric functions (signatures requested in the brief)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def comma_inclusion_rate(text: str) -> float:
|
||||
"""Ratio of sentences containing 1+ commas (0~1)."""
|
||||
sents = _split_sentences(text)
|
||||
if not sents:
|
||||
return 0.0
|
||||
with_comma = sum(1 for s in sents if "," in s)
|
||||
return with_comma / len(sents)
|
||||
|
||||
|
||||
def comma_usage_rate(text: str) -> float:
|
||||
"""Average comma count per sentence."""
|
||||
sents = _split_sentences(text)
|
||||
if not sents:
|
||||
return 0.0
|
||||
return sum(s.count(",") for s in sents) / len(sents)
|
||||
|
||||
|
||||
def ending_comma_rate(text: str) -> float:
|
||||
"""Ratio of connective-ending positions immediately followed by a comma.
|
||||
|
||||
Denominator = total connective-ending occurrences (with or without comma).
|
||||
Numerator = ending + comma matches.
|
||||
Returns 0.0 when the denominator is 0.
|
||||
"""
|
||||
if not text.strip():
|
||||
return 0.0
|
||||
# All occurrences of the endings (with optional trailing comma).
|
||||
all_endings = re.findall(r"(?:고|며|지만|면서|아서|어서)(?:\s*,)?", text)
|
||||
# Filter to those that actually represent a connective ending. The bare
|
||||
# syllable can occur inside other words (e.g. "고기"), so we require that
|
||||
# the syllable sit at an eojeol's end OR be followed by space/punct.
|
||||
# Approximation: count regex hits whose match ends at a token boundary.
|
||||
boundary_endings = re.findall(
|
||||
r"(?:고|며|지만|면서|아서|어서)(?=[\s,\.!?、。]|$)", text
|
||||
)
|
||||
if not boundary_endings:
|
||||
return 0.0
|
||||
# Count those followed by comma.
|
||||
with_comma = len(_ENDING_COMMA_RE.findall(text))
|
||||
return with_comma / len(boundary_endings)
|
||||
|
||||
|
||||
def comma_segment_length(text: str) -> float:
|
||||
"""Average eojeol-count of comma-delimited segments across sentences."""
|
||||
sents = _split_sentences(text)
|
||||
seg_lens: list[int] = []
|
||||
for s in sents:
|
||||
if "," not in s:
|
||||
seg_lens.append(len(_eojeols(s)))
|
||||
continue
|
||||
for seg in s.split(","):
|
||||
seg = seg.strip()
|
||||
if seg:
|
||||
seg_lens.append(len(_eojeols(seg)))
|
||||
if not seg_lens:
|
||||
return 0.0
|
||||
return sum(seg_lens) / len(seg_lens)
|
||||
|
||||
|
||||
def conclusion_pivot_count(text: str, lexicon: list[str] | None = None) -> int:
|
||||
"""Count occurrences of conclusion-pivot lexicon items."""
|
||||
items = lexicon or ["결론적으로", "따라서", "이를 통해", "그러므로"]
|
||||
return sum(text.count(w) for w in items)
|
||||
|
||||
|
||||
def safe_balance_count(text: str, lexicon: list[str] | None = None) -> int:
|
||||
"""Count occurrences of safe-balance hedge lexicon."""
|
||||
items = lexicon or ["양쪽 모두", "두 가지 모두", "장점도 있지만", "신중하게", "균형"]
|
||||
return sum(text.count(w) for w in items)
|
||||
|
||||
|
||||
def hanja_nominalizer_density(text: str) -> float:
|
||||
"""Token-level density of -성 / -적 / -화 endings (0~1).
|
||||
|
||||
Token = whitespace-split eojeol after stripping trailing punctuation.
|
||||
A token "counts" only if it has >= 2 chars total (so bare "성", "적",
|
||||
"화" don't count) and its final char is one of the three suffixes.
|
||||
"""
|
||||
tokens = [_strip_punct(t) for t in _eojeols(text)]
|
||||
tokens = [t for t in tokens if t]
|
||||
if not tokens:
|
||||
return 0.0
|
||||
hits = 0
|
||||
for t in tokens:
|
||||
if len(t) < 2:
|
||||
continue
|
||||
if t in _HANJA_BLOCK:
|
||||
continue
|
||||
if t[-1] in _HANJA_SUFFIXES:
|
||||
hits += 1
|
||||
return hits / len(tokens)
|
||||
|
||||
|
||||
def lexical_diversity(text: str) -> float:
|
||||
"""Type-token ratio over eojeols (unique / total)."""
|
||||
toks = [_strip_punct(t) for t in _eojeols(text)]
|
||||
toks = [t for t in toks if t]
|
||||
if not toks:
|
||||
return 0.0
|
||||
return len(set(toks)) / len(toks)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Baseline + z-score
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _default_baseline_path() -> str:
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
# Baseline ships next to metrics.py: references/baseline.json
|
||||
return os.path.join(here, "baseline.json")
|
||||
|
||||
|
||||
def _load_baseline(path: str | None) -> dict[str, Any]:
|
||||
p = path or _default_baseline_path()
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _resolve_genre_cells(
|
||||
baseline: dict[str, Any], genre: str
|
||||
) -> tuple[dict[str, Any], str | None]:
|
||||
"""Return (cells, fallback_warning_or_None).
|
||||
|
||||
Cells = mapping metric_key -> {"human": x, "ai": y, ...} merged across
|
||||
requested genre with global_average fill for missing fields.
|
||||
"""
|
||||
genres = baseline.get("genres", {}) or {}
|
||||
requested = genres.get(genre)
|
||||
fallback = None
|
||||
if requested is None:
|
||||
fallback = f"baseline_genre_null:{genre}->essay"
|
||||
requested = genres.get("essay") or {}
|
||||
# Merge with global average for any missing keys.
|
||||
g = baseline.get("global_average", {}) or {}
|
||||
merged: dict[str, Any] = {}
|
||||
keys = set(requested.keys()) | set(g.keys())
|
||||
for k in keys:
|
||||
cell = requested.get(k) or g.get(k)
|
||||
if cell:
|
||||
merged[k] = cell
|
||||
return merged, fallback
|
||||
|
||||
|
||||
def _z(value: float, human: float, ai: float, *, percent: bool) -> float | None:
|
||||
"""Approximate z-score using (ai - human) / 2 as standard deviation.
|
||||
|
||||
The KatFish report only gives two means per metric; with no spread
|
||||
published, we treat half the human-vs-AI gap as a one-sigma proxy.
|
||||
Direction: positive z means closer to AI. percent=True converts the
|
||||
measured value (0~1) to percent before subtracting human.
|
||||
"""
|
||||
if human is None or ai is None:
|
||||
return None
|
||||
val = value * 100 if percent else value
|
||||
sd = abs(ai - human) / 2.0
|
||||
if sd == 0:
|
||||
return 0.0
|
||||
return (val - human) / sd
|
||||
|
||||
|
||||
def _classify_risk(z_scores: dict[str, float | None], lexicon_hits: dict[str, int]) -> tuple[str, int]:
|
||||
score = 0
|
||||
for key in ("comma_inclusion_rate", "ending_comma_rate", "comma_segment_length"):
|
||||
z = z_scores.get(key)
|
||||
if z is not None and z > 1.0:
|
||||
score += 2
|
||||
ld = z_scores.get("lexical_diversity")
|
||||
if ld is not None and ld < -1.0:
|
||||
score += 1
|
||||
if lexicon_hits.get("conclusion_pivot_count", 0) >= 2:
|
||||
score += 1
|
||||
if lexicon_hits.get("safe_balance_count", 0) >= 2:
|
||||
score += 1
|
||||
hz = z_scores.get("hanja_nominalizer_density")
|
||||
if hz is not None and hz > 1.0:
|
||||
score += 1
|
||||
if score >= 6:
|
||||
band = "high"
|
||||
elif score >= 4:
|
||||
band = "medium"
|
||||
else:
|
||||
band = "low"
|
||||
return band, score
|
||||
|
||||
|
||||
def _evidence_spans(text: str, lexicon: list[str]) -> list[str]:
|
||||
found: list[str] = []
|
||||
for w in lexicon:
|
||||
if w in text:
|
||||
found.append(w)
|
||||
return found
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def compute_all(
|
||||
text: str,
|
||||
genre: str = "essay",
|
||||
baseline_path: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Compute all v1.6 metrics + z-scores + risk band for a single document."""
|
||||
baseline = _load_baseline(baseline_path)
|
||||
cells, fallback_warning = _resolve_genre_cells(baseline, genre)
|
||||
lex = baseline.get("lexicons", {}) or {}
|
||||
pivot_lex = lex.get("conclusion_pivot") or [
|
||||
"결론적으로", "따라서", "이를 통해", "그러므로",
|
||||
]
|
||||
safe_lex = lex.get("safe_balance") or [
|
||||
"양쪽 모두", "두 가지 모두", "장점도 있지만", "신중하게", "균형",
|
||||
]
|
||||
|
||||
metrics: dict[str, float | int] = {
|
||||
"comma_inclusion_rate": comma_inclusion_rate(text),
|
||||
"comma_usage_rate": comma_usage_rate(text),
|
||||
"ending_comma_rate": ending_comma_rate(text),
|
||||
"comma_segment_length": comma_segment_length(text),
|
||||
"conclusion_pivot_count": conclusion_pivot_count(text, pivot_lex),
|
||||
"safe_balance_count": safe_balance_count(text, safe_lex),
|
||||
"hanja_nominalizer_density": hanja_nominalizer_density(text),
|
||||
"lexical_diversity": lexical_diversity(text),
|
||||
}
|
||||
|
||||
# baseline cells use percent for inclusion/ending rates.
|
||||
z_scores: dict[str, float | None] = {}
|
||||
for key, percent in (
|
||||
("comma_inclusion_rate", True),
|
||||
("comma_usage_rate", False),
|
||||
("ending_comma_rate", True),
|
||||
("comma_segment_length", False),
|
||||
):
|
||||
cell = cells.get(key)
|
||||
if cell:
|
||||
z_scores[key] = _z(metrics[key], cell.get("human"), cell.get("ai"), percent=percent)
|
||||
else:
|
||||
z_scores[key] = None
|
||||
|
||||
# hanja_nominalizer_density baseline: report says 12 occurrences per doc
|
||||
# = S2 strong signal. We approximate by treating density 0.06 as human
|
||||
# reference and 0.12 as AI reference (rough proxy when no per-doc cells).
|
||||
z_scores["hanja_nominalizer_density"] = _z(
|
||||
metrics["hanja_nominalizer_density"] * 100, 6.0, 12.0, percent=False
|
||||
)
|
||||
# lexical_diversity has no baseline cell either; use rough 0.65 human /
|
||||
# 0.55 AI from typical Korean essay corpora as a placeholder. AI tends
|
||||
# to repeat tokens slightly more.
|
||||
z_scores["lexical_diversity"] = _z(metrics["lexical_diversity"], 0.65, 0.55, percent=False)
|
||||
|
||||
lexicon_hits = {
|
||||
"conclusion_pivot_count": int(metrics["conclusion_pivot_count"]),
|
||||
"safe_balance_count": int(metrics["safe_balance_count"]),
|
||||
}
|
||||
risk_band, risk_score = _classify_risk(z_scores, lexicon_hits)
|
||||
|
||||
out: dict[str, Any] = {
|
||||
"version": VERSION,
|
||||
"genre": genre,
|
||||
"char_count": len(text),
|
||||
"metrics": metrics,
|
||||
"z_scores": z_scores,
|
||||
"risk_band": risk_band,
|
||||
"risk_score": risk_score,
|
||||
"evidence": {
|
||||
"conclusion_pivots": _evidence_spans(text, pivot_lex),
|
||||
"safe_balances": _evidence_spans(text, safe_lex),
|
||||
},
|
||||
}
|
||||
if fallback_warning:
|
||||
out["warning"] = fallback_warning
|
||||
return out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Humanize KR v1.6 metric runner")
|
||||
parser.add_argument("--input", required=True, help="Input text file path")
|
||||
parser.add_argument("--genre", default="essay", help="essay/poetry/abstract/...")
|
||||
parser.add_argument("--output", default=None, help="Output JSON path (optional)")
|
||||
parser.add_argument(
|
||||
"--baseline", default=None, help="Override baseline JSON path"
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
with open(args.input, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
|
||||
result = compute_all(text, genre=args.genre, baseline_path=args.baseline)
|
||||
|
||||
if args.output:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True)
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(result["risk_band"])
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(_main())
|
||||
746
.claude/skills/humanize-korean/references/metrics_v2.py
Normal file
746
.claude/skills/humanize-korean/references/metrics_v2.py
Normal file
|
|
@ -0,0 +1,746 @@
|
|||
"""Humanize KR v2.0 quantitative metrics calculator.
|
||||
|
||||
Extends v1.6 metrics.py with post-editese 3축 (simplification·normalisation·
|
||||
interference) and 8 translation-type detection signals from the Korean
|
||||
machine-translation/post-editing literature (Toral 2019; Schmaltz 2020;
|
||||
보고서 T1~T8).
|
||||
|
||||
Hard rule: standard library ONLY (json/re/math/collections/os/sys/argparse/
|
||||
statistics). No konlpy/bareun/mecab/spaCy. Morphological analysis is
|
||||
approximated with regex + suffix dictionaries (한자어 -성·-적·-화·-도·-력·-감·-원,
|
||||
평서형 -한다·-된다·-이다, 진행형 -고 있다, 이중 조사 -에서의·-에로의·-으로의·-에의·-으로부터의·-로부터의).
|
||||
|
||||
Versioning:
|
||||
- v1.6 8 functions (comma_inclusion_rate ... lexical_diversity) are imported
|
||||
*as-is* from references/metrics.py (signature + return preserved). DO NOT
|
||||
redefine them here. Regression-safe.
|
||||
- v2.0 adds 14 NEW pure functions for post-editese + T1~T8 detection.
|
||||
|
||||
This file lives in `_workspace/v2.0-YYYY-MM-DD/03_metrics/`. Phase 6
|
||||
integrator will merge it into the project's references/metrics.py.
|
||||
|
||||
CLI:
|
||||
python metrics_v2.py --input run/01_input.txt \
|
||||
--genre essay --output run/00_metrics_v2.json
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from statistics import StatisticsError, mean, pstdev
|
||||
from typing import Any
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Import v1.6 metrics module (regression-safe — signatures untouched)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_PROJECT_ROOT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
|
||||
_V1_METRICS_DIR = os.path.join(
|
||||
_PROJECT_ROOT, ".claude", "skills", "humanize-korean", "references"
|
||||
)
|
||||
if _V1_METRICS_DIR not in sys.path:
|
||||
sys.path.insert(0, _V1_METRICS_DIR)
|
||||
|
||||
import metrics as _v1 # noqa: E402 (sys.path mutation is intentional)
|
||||
|
||||
# Re-export the 8 v1.6 metric callables verbatim. They keep their original
|
||||
# signatures and return shapes — `metrics_v2.comma_inclusion_rate(text)`
|
||||
# is byte-identical to `metrics.comma_inclusion_rate(text)`.
|
||||
comma_inclusion_rate = _v1.comma_inclusion_rate
|
||||
comma_usage_rate = _v1.comma_usage_rate
|
||||
ending_comma_rate = _v1.ending_comma_rate
|
||||
comma_segment_length = _v1.comma_segment_length
|
||||
conclusion_pivot_count = _v1.conclusion_pivot_count
|
||||
safe_balance_count = _v1.safe_balance_count
|
||||
hanja_nominalizer_density = _v1.hanja_nominalizer_density
|
||||
lexical_diversity = _v1.lexical_diversity
|
||||
|
||||
# Reuse v1.6 internal helpers (private, regression-safe — we never mutate).
|
||||
_split_sentences = _v1._split_sentences
|
||||
_eojeols = _v1._eojeols
|
||||
_strip_punct = _v1._strip_punct
|
||||
|
||||
VERSION = "v2.0"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# v2.0 module-level constants — sufix / lexicon dictionaries
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 한자어 명사화 접미사 v2.0 확장 — v1.6의 -성·-적·-화 + 보고서 T6 보강 4종.
|
||||
# token-final 1글자 매칭. 토큰 길이 >= 2 가드는 함수 내부에서.
|
||||
_HANJA_SUFFIXES_V2 = ("성", "적", "화", "도", "력", "감", "원")
|
||||
|
||||
# 평서형 종결 사전 — normalisation 축. 문장 마지막 어절의 어미를 매칭.
|
||||
# 한자어 + 한다/된다/이다 형태가 가장 흔한 정규화 시그널.
|
||||
_DECLARATIVE_ENDINGS = ("한다", "된다", "이다")
|
||||
|
||||
# 진행형 어미 — T8b. "~고 있다" 표층 매칭. 종결형/연결형 모두 포함.
|
||||
# 부정형 "있지 않다", 의존명사 "있는" 은 별개. 정규식은 "고 있" 토큰
|
||||
# 시작점 + 후속 "다/었/는" 등을 폭넓게 캡처.
|
||||
_PROGRESSIVE_RE = re.compile(r"고\s*있(?:다|었|는|을|던|는다)")
|
||||
|
||||
# T2b 이중 피동 표층 어휘. 모두 "되어진/여진/혀진/려진" 등 피동 보조어간 +
|
||||
# 피동 보조용언 중첩의 표층형. 단순 "되다" 는 정상 표현이므로 제외.
|
||||
_DOUBLE_PASSIVE_TOKENS = (
|
||||
"되어진다",
|
||||
"되어졌다",
|
||||
"되어진",
|
||||
"되어지는",
|
||||
"여지다",
|
||||
"여진다",
|
||||
"여졌다",
|
||||
"여진",
|
||||
"잊혀진",
|
||||
"잊혀졌",
|
||||
"잊혀진다",
|
||||
"보여진다",
|
||||
"보여졌다",
|
||||
"보여진",
|
||||
"쓰여진다",
|
||||
"쓰여졌다",
|
||||
"쓰여진",
|
||||
"닫혀진",
|
||||
"열려진",
|
||||
"불려진",
|
||||
"놓여진",
|
||||
)
|
||||
|
||||
# T2a "~에 의해 + 피동" — 피동 동사가 직후 N어절 안에 등장해야 매칭.
|
||||
# 단순 "에 의해" 는 빈번한 자연 한국어이므로 제외 (보고서 T2 caveat).
|
||||
_BY_PASSIVE_RE = re.compile(
|
||||
r"에\s*의(?:해|하여)\s+\S{0,12}?(?:되|받|당하|지)(?:다|었|어|ㄴ다|는다|는|ㄹ|을)"
|
||||
)
|
||||
|
||||
# T3 인칭 대명사 — 영어 he/she/it/they 의 1대1 매핑.
|
||||
# "그" 단독은 지시사·관형사로도 자주 쓰이므로 보수적으로 처리:
|
||||
# - "그" 뒤에 조사 "는/가/를/의/에게/에서/와/도/만" 이 붙은 경우만 인칭으로 본다.
|
||||
# - 그녀/그들/그것 은 거의 항상 인칭 대명사이므로 단독 매칭.
|
||||
_PRONOUN_RE = re.compile(
|
||||
r"(?:그녀(?:는|가|를|의|에게|와|도|만)?"
|
||||
r"|그것(?:은|이|을|의|에|에게)?"
|
||||
r"|그들(?:은|이|을|의|에게|과|도)?"
|
||||
r"|그(?:는|가|를|의|에게|와|도|만)(?=\s|[\.,!?]|$))"
|
||||
)
|
||||
|
||||
# T4 무정물·추상명사 + -들. 토큰 단위 매칭.
|
||||
# 보고서 III.3.4.2 + pe_checklist PE5에서 "거의 모두 삭제 후보" 로 거론된
|
||||
# 핵심 어휘셋. 사전은 보수적(false positive 줄임).
|
||||
_INANIMATE_DEUL_TOKENS = (
|
||||
"데이터들",
|
||||
"정보들",
|
||||
"결과들",
|
||||
"연구들",
|
||||
"아이디어들",
|
||||
"방법들",
|
||||
"문제들",
|
||||
"의견들",
|
||||
"시스템들",
|
||||
"기술들",
|
||||
"사실들",
|
||||
"사례들",
|
||||
"이론들",
|
||||
"개념들",
|
||||
"현상들",
|
||||
"특징들",
|
||||
"요소들",
|
||||
"원인들",
|
||||
"영향들",
|
||||
"변화들",
|
||||
"기능들",
|
||||
"조건들",
|
||||
"기준들",
|
||||
"관점들",
|
||||
"원리들",
|
||||
)
|
||||
|
||||
# T6 light verb construction — have/make 류 직역.
|
||||
# "회의를 가지다·결정을 내리다" 식 light verb.
|
||||
_HAVE_MAKE_LITERAL_TOKENS = (
|
||||
"가지고 있다",
|
||||
"가지고있다",
|
||||
"가지고 있는",
|
||||
"가지고있는",
|
||||
"가지고 있었",
|
||||
"가지고있었",
|
||||
"가지고 있으",
|
||||
"가지고있으",
|
||||
"갖고 있다",
|
||||
"갖고있다",
|
||||
"갖고 있는",
|
||||
"갖고있는",
|
||||
"을 가지다",
|
||||
"를 가지다",
|
||||
"을 가졌",
|
||||
"를 가졌",
|
||||
"을 가진다",
|
||||
"를 가진다",
|
||||
"을 만들다",
|
||||
"를 만들다",
|
||||
"을 만들었",
|
||||
"를 만들었",
|
||||
"을 만들어 낸",
|
||||
"를 만들어 낸",
|
||||
"을 만들어낸",
|
||||
"를 만들어낸",
|
||||
"회의를 가지",
|
||||
"회의를 가졌",
|
||||
"한번 봄을 가지",
|
||||
"결정을 내리",
|
||||
"결정을 내렸",
|
||||
)
|
||||
|
||||
# T7 이중 조사 결합. caveat #5 (단순 ~의 제외) 정확히 반영.
|
||||
# "에서의" 등 6종만 매칭 — 단일 ~의는 절대 매칭 안 됨.
|
||||
_DOUBLE_PARTICLE_RE = re.compile(
|
||||
r"(?:에서의|에로의|으로의|에의|으로부터의|로부터의)"
|
||||
)
|
||||
|
||||
# 단락 분리: 빈 줄 1개 이상.
|
||||
_PARAGRAPH_SPLIT_RE = re.compile(r"\n\s*\n")
|
||||
|
||||
# 종결어미 다양성 — 문장 마지막 종결어미 표층(보통 1~2음절 끝마디)을 키로 사용.
|
||||
# verb stem(예: "결정한다"의 "결정") 부분은 제외하고 어미 부분(예: "한다")만 봐야
|
||||
# 다양성 신호가 의미를 가진다. 따라서 마지막 2음절을 우선 키로 사용.
|
||||
_ENDING_FINAL_RE = re.compile(r"([가-힣]{2})[\.!?]\s*$")
|
||||
# 한 음절만 있는 문장(예: "와.")은 별도로 1음절 매칭.
|
||||
_ENDING_FINAL_FALLBACK_RE = re.compile(r"([가-힣])[\.!?]\s*$")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Local helpers (do not shadow v1.6)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _split_paragraphs(text: str) -> list[str]:
|
||||
text = text.strip()
|
||||
if not text:
|
||||
return []
|
||||
return [p.strip() for p in _PARAGRAPH_SPLIT_RE.split(text) if p.strip()]
|
||||
|
||||
|
||||
def _last_eojeol(sentence: str) -> str:
|
||||
toks = _eojeols(sentence)
|
||||
if not toks:
|
||||
return ""
|
||||
return _strip_punct(toks[-1])
|
||||
|
||||
|
||||
def _all_tokens(text: str) -> list[str]:
|
||||
toks = [_strip_punct(t) for t in _eojeols(text)]
|
||||
return [t for t in toks if t]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# === v2.0 NEW METRICS ===
|
||||
# Group A: simplification 축
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def lexical_diversity_ttr(text: str) -> float:
|
||||
"""Type-token ratio (TTR) over Korean eojeols — simplification axis.
|
||||
|
||||
Identical computation to v1.6 ``lexical_diversity`` but exposed under the
|
||||
Toral 2019 simplification-axis name so the post-editese score can map
|
||||
cleanly. Returns 0.0 on empty input.
|
||||
"""
|
||||
return lexical_diversity(text)
|
||||
|
||||
|
||||
def lexical_density(text: str) -> float:
|
||||
"""Content-word ratio — proxy for lexical density (simplification axis).
|
||||
|
||||
Standard-library proxy: a token is counted as a *content word* if its
|
||||
final character is one of the v2.0 hanja nominalizer suffixes
|
||||
(-성·-적·-화·-도·-력·-감·-원), or if it ends with a verb/adjective
|
||||
declarative marker (-한다·-된다·-이다·-했다·-된다·-였다·-이었다·-답다·-스럽다·-롭다).
|
||||
Function words (조사·접속부사) are filtered out by length<2 and a small
|
||||
stopword list.
|
||||
|
||||
Returns content_word_count / total_token_count in [0, 1].
|
||||
"""
|
||||
tokens = _all_tokens(text)
|
||||
if not tokens:
|
||||
return 0.0
|
||||
stop = {
|
||||
"그리고", "그러나", "하지만", "또한", "또는", "혹은", "즉", "예를", "예컨대",
|
||||
"이는", "이것은", "그것은", "그러므로", "따라서",
|
||||
}
|
||||
content_suffixes = ("성", "적", "화", "도", "력", "감", "원")
|
||||
content_endings = (
|
||||
"한다", "된다", "이다", "했다", "였다", "었다",
|
||||
"답다", "스럽다", "롭다", "하다", "되다",
|
||||
)
|
||||
hits = 0
|
||||
for t in tokens:
|
||||
if len(t) < 2:
|
||||
continue
|
||||
if t in stop:
|
||||
continue
|
||||
if t[-1] in content_suffixes:
|
||||
hits += 1
|
||||
continue
|
||||
if any(t.endswith(end) for end in content_endings):
|
||||
hits += 1
|
||||
return hits / len(tokens)
|
||||
|
||||
|
||||
def ending_diversity(text: str) -> float:
|
||||
"""Sentence-ending diversity — unique endings / total sentences.
|
||||
|
||||
Approximates 종결어미 다양성. Sentence is split via v1.6 helper; the
|
||||
last 1~3 syllables (Hangul only) before the terminal punctuation are
|
||||
used as the ending key. Higher = more diverse (more human-like).
|
||||
Returns 0.0 when no sentence ends with valid punctuation.
|
||||
"""
|
||||
sents = _split_sentences(text)
|
||||
keys: list[str] = []
|
||||
for s in sents:
|
||||
m = _ENDING_FINAL_RE.search(s)
|
||||
if m:
|
||||
keys.append(m.group(1))
|
||||
continue
|
||||
m2 = _ENDING_FINAL_FALLBACK_RE.search(s)
|
||||
if m2:
|
||||
keys.append(m2.group(1))
|
||||
if not keys:
|
||||
return 0.0
|
||||
return len(set(keys)) / len(keys)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Group B: normalisation 축
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def normalisation_score(text: str) -> float:
|
||||
"""Declarative-form (~한다/~된다/~이다) concentration — normalisation axis.
|
||||
|
||||
Returns the ratio of sentences whose final eojeol ends with one of the
|
||||
three canonical declarative markers (~한다·~된다·~이다 — variants
|
||||
`-한다.`, `-한다!` 등은 punctuation-stripped). High values (>0.7) signal
|
||||
normalised, AI-like prose; very low values (<0.3) often signal informal
|
||||
speech (해체) or heterogeneous registers. Range [0, 1].
|
||||
"""
|
||||
sents = _split_sentences(text)
|
||||
if not sents:
|
||||
return 0.0
|
||||
hits = 0
|
||||
for s in sents:
|
||||
last = _last_eojeol(s)
|
||||
if not last:
|
||||
continue
|
||||
for ending in _DECLARATIVE_ENDINGS:
|
||||
if last.endswith(ending):
|
||||
hits += 1
|
||||
break
|
||||
return hits / len(sents)
|
||||
|
||||
|
||||
def da_streak_rate(text: str) -> int:
|
||||
"""Count of '-다' streak runs of length >= 4 — T8a normalisation signal.
|
||||
|
||||
A *streak* = consecutive sentences whose final eojeol ends in '다'
|
||||
(any '~다' — 한다·된다·이다·었다·았다·였다 등). Streaks of length 4+
|
||||
are reported. The return value is the number of distinct streaks
|
||||
(not the total streak length). Documents with one long uniform run
|
||||
of '-다' will return 1; truly diverse docs return 0.
|
||||
"""
|
||||
sents = _split_sentences(text)
|
||||
streaks = 0
|
||||
cur = 0
|
||||
for s in sents:
|
||||
last = _last_eojeol(s)
|
||||
if last.endswith("다"):
|
||||
cur += 1
|
||||
else:
|
||||
if cur >= 4:
|
||||
streaks += 1
|
||||
cur = 0
|
||||
if cur >= 4:
|
||||
streaks += 1
|
||||
return streaks
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Group C: interference 축 — T1~T8 detection signals
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def inanimate_subject_rate(text: str) -> float:
|
||||
"""T1: inanimate-subject + universal-verb pattern rate.
|
||||
|
||||
Approximation: count sentences whose first content noun ends with one
|
||||
of the v2.0 hanja suffixes (-성·-적·-화·-도·-력·-감·-원) OR matches a
|
||||
short list of inanimate/abstract subjects (`연구·데이터·분석·결과·시스템·
|
||||
기술·사례·현상·이론·정책·보고서`) AND whose verb is a universal
|
||||
cognitive/declarative verb (보여준다·시사한다·만든다·드러낸다·제시한다·
|
||||
나타낸다·증명한다·말해준다·의미한다·가져온다). Returns
|
||||
matching_sents / total_sents in [0, 1].
|
||||
"""
|
||||
sents = _split_sentences(text)
|
||||
if not sents:
|
||||
return 0.0
|
||||
inanimate_subjects = (
|
||||
"연구", "데이터", "분석", "결과", "시스템", "기술", "사례",
|
||||
"현상", "이론", "정책", "보고서", "AI", "인공지능", "모델",
|
||||
"알고리즘", "변화", "위기", "혁신", "사회", "경제",
|
||||
)
|
||||
universal_verbs = (
|
||||
"보여준다", "보여줬다", "보여주는", "시사한다", "시사하는",
|
||||
"만든다", "만들어", "드러낸다", "드러냈다", "드러내는",
|
||||
"제시한다", "제시했다", "나타낸다", "나타냈다", "나타내는",
|
||||
"증명한다", "증명했다", "말해준다", "말해주는",
|
||||
"의미한다", "의미하는", "가져온다", "가져왔다", "가져오는",
|
||||
)
|
||||
hits = 0
|
||||
for s in sents:
|
||||
toks = _all_tokens(s)
|
||||
if not toks:
|
||||
continue
|
||||
head = toks[0]
|
||||
# Subject heuristic: first token, optionally followed by 은/는/이/가.
|
||||
head_stem = head
|
||||
for josa in ("은", "는", "이", "가", "도"):
|
||||
if head.endswith(josa) and len(head) > 1:
|
||||
head_stem = head[:-1]
|
||||
break
|
||||
is_inanimate = (
|
||||
head_stem in inanimate_subjects
|
||||
or (len(head_stem) >= 2 and head_stem[-1] in _HANJA_SUFFIXES_V2)
|
||||
)
|
||||
if not is_inanimate:
|
||||
continue
|
||||
# Verb heuristic: any later token in `universal_verbs`.
|
||||
if any(any(uv in t for uv in universal_verbs) for t in toks[1:]):
|
||||
hits += 1
|
||||
return hits / len(sents)
|
||||
|
||||
|
||||
def by_passive_count(text: str) -> int:
|
||||
"""T2a: ~에 의해 + passive-verb co-occurrence count.
|
||||
|
||||
Bare '에 의해' is excluded. Only the regex-anchored
|
||||
'에 의해 ... 되/받/당하/지' pattern is counted. Returns int >= 0.
|
||||
"""
|
||||
if not text.strip():
|
||||
return 0
|
||||
return len(_BY_PASSIVE_RE.findall(text))
|
||||
|
||||
|
||||
def double_passive_count(text: str) -> int:
|
||||
"""T2b: double-passive (잊혀지다·보여지다·되어진다·여지다·쓰여지다 …) count.
|
||||
|
||||
Surface-form lexicon. 단순 '되다' 는 제외 (자연 표현). Returns int >= 0.
|
||||
"""
|
||||
if not text.strip():
|
||||
return 0
|
||||
n = 0
|
||||
for tok in _DOUBLE_PASSIVE_TOKENS:
|
||||
n += text.count(tok)
|
||||
return n
|
||||
|
||||
|
||||
def pronoun_density(text: str) -> float:
|
||||
"""T3: personal-pronoun density per paragraph (avg).
|
||||
|
||||
Counts 그/그녀/그것/그들 (+ 조사 fused forms). Bare '그' is only counted
|
||||
when followed by 는/가/를/의/에게/와/도/만 to filter out demonstrative use.
|
||||
Returns paragraph-mean of (pronoun_tokens / paragraph_eojeols).
|
||||
Range [0, 1]. Empty input returns 0.0.
|
||||
"""
|
||||
paragraphs = _split_paragraphs(text)
|
||||
if not paragraphs:
|
||||
return 0.0
|
||||
densities: list[float] = []
|
||||
for p in paragraphs:
|
||||
toks = _all_tokens(p)
|
||||
if not toks:
|
||||
continue
|
||||
pronoun_hits = len(_PRONOUN_RE.findall(p))
|
||||
densities.append(pronoun_hits / len(toks))
|
||||
if not densities:
|
||||
return 0.0
|
||||
try:
|
||||
return mean(densities)
|
||||
except StatisticsError:
|
||||
return 0.0
|
||||
|
||||
|
||||
def deul_overuse_rate(text: str) -> float:
|
||||
"""T4: inanimate / abstract noun + '-들' over-use ratio.
|
||||
|
||||
Returns deul_overuse_hits / total_eojeols. The numerator counts
|
||||
occurrences of any token in `_INANIMATE_DEUL_TOKENS` (데이터들·정보들·
|
||||
결과들·연구들·아이디어들·방법들·문제들·의견들·시스템들·기술들 …).
|
||||
Range [0, 1] — practical AI text seldom exceeds ~0.05.
|
||||
"""
|
||||
toks = _all_tokens(text)
|
||||
if not toks:
|
||||
return 0.0
|
||||
hits = 0
|
||||
for t in toks:
|
||||
# Match exact OR with one short josa suffix (-과/와/이/가/을/를/의/에/은/는/도)
|
||||
if t in _INANIMATE_DEUL_TOKENS:
|
||||
hits += 1
|
||||
continue
|
||||
for base in _INANIMATE_DEUL_TOKENS:
|
||||
if t.startswith(base) and len(t) - len(base) in (1, 2):
|
||||
# remaining tail must be hangul (likely josa)
|
||||
tail = t[len(base):]
|
||||
if all("가" <= ch <= "힣" for ch in tail):
|
||||
hits += 1
|
||||
break
|
||||
return hits / len(toks)
|
||||
|
||||
|
||||
def relative_clause_nesting(text: str) -> int:
|
||||
"""T5: count of sentences with relative-clause nesting depth >= 3.
|
||||
|
||||
Approximation: a sentence is nested when it contains 3+ adnominal
|
||||
clause endings -ㄴ/-는/-ㄹ/-한/-된/-할 followed by a noun (heuristic:
|
||||
the syllable before whitespace). We check every sentence for the
|
||||
count of token endings in `(ㄴ|는|ㄹ|던|할|한|된|될)` followed by a
|
||||
short space-separated noun. Returns the *number of sentences*
|
||||
(not total nestings) with depth >= 3.
|
||||
"""
|
||||
sents = _split_sentences(text)
|
||||
if not sents:
|
||||
return 0
|
||||
# 관형형 어미 종결 음절 매칭 — 어절 끝이 (ㄴ|는|ㄹ|던|한|된|할|될|온) 인 토큰 수.
|
||||
adnominal_re = re.compile(r"[가-힣]+(?:ㄴ|는|ㄹ|던|한|된|할|될|온|간)\s+[가-힣]")
|
||||
matches_per_sent = []
|
||||
for s in sents:
|
||||
m = adnominal_re.findall(s)
|
||||
matches_per_sent.append(len(m))
|
||||
return sum(1 for c in matches_per_sent if c >= 3)
|
||||
|
||||
|
||||
def have_make_literal_count(text: str) -> int:
|
||||
"""T6: count of literal have/make light-verb constructions.
|
||||
|
||||
가지고 있다·갖고 있다·~을 가지다·~을 만들다·회의를 가지다·결정을 내리다 …
|
||||
Returns int >= 0.
|
||||
"""
|
||||
if not text.strip():
|
||||
return 0
|
||||
n = 0
|
||||
for tok in _HAVE_MAKE_LITERAL_TOKENS:
|
||||
n += text.count(tok)
|
||||
return n
|
||||
|
||||
|
||||
def double_particle_count(text: str) -> int:
|
||||
"""T7: double-particle (에서의·에로의·으로의·에의·으로부터의·로부터의) count.
|
||||
|
||||
Caveat #5 (single ~의 excluded) is *enforced by construction* — the
|
||||
regex never matches a bare ~의. Returns int >= 0.
|
||||
"""
|
||||
if not text.strip():
|
||||
return 0
|
||||
return len(_DOUBLE_PARTICLE_RE.findall(text))
|
||||
|
||||
|
||||
def progressive_aspect_rate(text: str) -> float:
|
||||
"""T8b: progressive aspect '~고 있다' rate per sentence.
|
||||
|
||||
Returns progressive_hits / total_sentences. Surface-form match; not
|
||||
every '~고 있다' is reducible (예: 진행 의미가 본질적인 동사) but
|
||||
high rates flag automatic 1대1 매핑. Range typically [0, 1+] — values
|
||||
>0.5 signal heavy literal mapping.
|
||||
"""
|
||||
sents = _split_sentences(text)
|
||||
if not sents:
|
||||
return 0.0
|
||||
hits = sum(len(_PROGRESSIVE_RE.findall(s)) for s in sents)
|
||||
return hits / len(sents)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# === v2.0 INTERFERENCE INDEX ===
|
||||
# Composite signal weighted across T1~T8.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def interference_index(text: str) -> dict[str, Any]:
|
||||
"""T1~T8 weighted interference signal — interference axis composite.
|
||||
|
||||
Returns a dict with each sub-signal score plus a `weighted_total`
|
||||
that sums per-type contributions (each capped to [0, 1] by simple
|
||||
rescaling). This is descriptive, not a z-score — calibration to
|
||||
baseline happens in compute_all_v2.
|
||||
"""
|
||||
n_sents = max(len(_split_sentences(text)), 1)
|
||||
chars = max(len(text), 1)
|
||||
components = {
|
||||
"T1_inanimate_subject_rate": inanimate_subject_rate(text),
|
||||
"T2a_by_passive_per_1k": by_passive_count(text) / chars * 1000,
|
||||
"T2b_double_passive_per_1k": double_passive_count(text) / chars * 1000,
|
||||
"T3_pronoun_density": pronoun_density(text),
|
||||
"T4_deul_overuse_rate": deul_overuse_rate(text),
|
||||
"T5_nested_clause_count": relative_clause_nesting(text),
|
||||
"T6_have_make_per_1k": have_make_literal_count(text) / chars * 1000,
|
||||
"T7_double_particle_per_1k": double_particle_count(text) / chars * 1000,
|
||||
"T8b_progressive_rate": progressive_aspect_rate(text),
|
||||
}
|
||||
# Each component clamped to [0, 1] heuristically:
|
||||
weights = {
|
||||
"T1_inanimate_subject_rate": 1.0, # already in [0,1]
|
||||
"T2a_by_passive_per_1k": 0.2, # /5
|
||||
"T2b_double_passive_per_1k": 0.2,
|
||||
"T3_pronoun_density": 4.0, # human <0.015, scale up
|
||||
"T4_deul_overuse_rate": 4.0,
|
||||
"T5_nested_clause_count": 0.05, # /20
|
||||
"T6_have_make_per_1k": 0.2,
|
||||
"T7_double_particle_per_1k": 0.5,
|
||||
"T8b_progressive_rate": 1.0,
|
||||
}
|
||||
weighted_total = 0.0
|
||||
for k, v in components.items():
|
||||
weighted_total += min(1.0, max(0.0, v * weights[k]))
|
||||
return {
|
||||
"components": components,
|
||||
"weighted_total": weighted_total,
|
||||
"n_sentences": n_sents,
|
||||
"n_chars": chars,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Baseline + z-score (v2.0 extension)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _default_baseline_v2_path() -> str:
|
||||
return os.path.join(_HERE, "baseline_v2_diff.json")
|
||||
|
||||
|
||||
def _load_baseline_v2(path: str | None) -> dict[str, Any]:
|
||||
p = path or _default_baseline_v2_path()
|
||||
if not os.path.exists(p):
|
||||
return {}
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _z_simple(value: float, mean_v: float, stdev: float) -> float | None:
|
||||
if stdev is None or stdev <= 0:
|
||||
return None
|
||||
return (value - mean_v) / stdev
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public entry point — v2.0 superset
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def compute_all_v2(
|
||||
text: str,
|
||||
genre: str = "essay",
|
||||
baseline_path: str | None = None,
|
||||
baseline_v2_path: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Compute v1.6 metrics + v2.0 post-editese + T1~T8 signals.
|
||||
|
||||
Returns the v1.6 ``compute_all`` payload extended with:
|
||||
- ``v2_metrics``: dict of new metric values
|
||||
- ``v2_z_scores``: per-metric z against baseline_v2 (None if placeholder)
|
||||
- ``v2_baseline_warnings``: list of metric keys whose baseline cell
|
||||
carries `_placeholder: true`.
|
||||
"""
|
||||
base = _v1.compute_all(text, genre=genre, baseline_path=baseline_path)
|
||||
v2_metrics: dict[str, float | int] = {
|
||||
"lexical_diversity_ttr": lexical_diversity_ttr(text),
|
||||
"lexical_density": lexical_density(text),
|
||||
"ending_diversity": ending_diversity(text),
|
||||
"normalisation_score": normalisation_score(text),
|
||||
"da_streak_rate": da_streak_rate(text),
|
||||
"inanimate_subject_rate": inanimate_subject_rate(text),
|
||||
"by_passive_count": by_passive_count(text),
|
||||
"double_passive_count": double_passive_count(text),
|
||||
"pronoun_density": pronoun_density(text),
|
||||
"deul_overuse_rate": deul_overuse_rate(text),
|
||||
"relative_clause_nesting": relative_clause_nesting(text),
|
||||
"have_make_literal_count": have_make_literal_count(text),
|
||||
"double_particle_count": double_particle_count(text),
|
||||
"progressive_aspect_rate": progressive_aspect_rate(text),
|
||||
}
|
||||
interference = interference_index(text)
|
||||
|
||||
bv2 = _load_baseline_v2(baseline_v2_path)
|
||||
cells = {}
|
||||
warnings: list[str] = []
|
||||
if bv2:
|
||||
genres = bv2.get("genres", {}) or {}
|
||||
cells = genres.get(genre) or genres.get("essay") or {}
|
||||
z_scores: dict[str, float | None] = {}
|
||||
for k, v in v2_metrics.items():
|
||||
cell = cells.get(k)
|
||||
if not cell:
|
||||
z_scores[k] = None
|
||||
continue
|
||||
if cell.get("_placeholder"):
|
||||
warnings.append(k)
|
||||
z_scores[k] = _z_simple(
|
||||
float(v), float(cell.get("mean", 0.0)), float(cell.get("stdev", 0.0))
|
||||
)
|
||||
|
||||
base["version"] = VERSION
|
||||
base["v2_metrics"] = v2_metrics
|
||||
base["v2_interference_index"] = interference
|
||||
base["v2_z_scores"] = z_scores
|
||||
base["v2_baseline_warnings"] = warnings
|
||||
return base
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Humanize KR v2.0 metric runner")
|
||||
parser.add_argument("--input", required=True, help="Input text file path")
|
||||
parser.add_argument("--genre", default="essay", help="essay/news/blog/qa/dialogue")
|
||||
parser.add_argument("--output", default=None, help="Output JSON path (optional)")
|
||||
parser.add_argument(
|
||||
"--baseline", default=None, help="Override v1.6 baseline JSON path"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--baseline-v2", default=None, help="Override v2.0 baseline JSON path"
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
with open(args.input, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
|
||||
result = compute_all_v2(
|
||||
text,
|
||||
genre=args.genre,
|
||||
baseline_path=args.baseline,
|
||||
baseline_v2_path=args.baseline_v2,
|
||||
)
|
||||
|
||||
if args.output:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True)
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(result["risk_band"])
|
||||
return 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# v1.6 호환 별칭 (prepare_monolith_input.py가 _metrics_mod.compute_all 호출)
|
||||
# ---------------------------------------------------------------------------
|
||||
compute_all = compute_all_v2 # v2.0 출력은 v1.6의 상위집합 (integration_note §1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(_main())
|
||||
|
|
@ -1,653 +0,0 @@
|
|||
# Pattern Candidates Pool (v1.3~)
|
||||
|
||||
분류 체계 본진(`ai-tell-taxonomy.md`)에 승격되기 전, 에이전트들이 실전에서 발견한 "AI 티 의심 패턴" 후보를 단일 그릇으로 누적하는 곳. taxonomist가 이 풀을 주기적으로 점검해 v1.x로 승격하거나 기각한다.
|
||||
|
||||
## 역할 분담
|
||||
|
||||
- **detector·rewriter·naturalness-reviewer**: 미분류 의심 span을 발견하면 본 풀에 후보로 적재(또는 기존 후보의 `occurrences`를 +1).
|
||||
- **korean-ai-tell-taxonomist**: 풀 운영자. 재현 2회 이상·심각도 일관 후보를 본진으로 승격, 부적합·중복은 기각.
|
||||
|
||||
본 풀은 SSOT가 아니라 **승격 전 작업대**다. 탐지기·윤문가는 풀의 후보를 직접 적용하지 않는다 — 본진(`ai-tell-taxonomy.md`)에 승격된 패턴만 운영 규칙이다.
|
||||
|
||||
## 후보 스키마
|
||||
|
||||
각 후보는 다음 YAML 항목으로 풀에 누적된다.
|
||||
|
||||
```yaml
|
||||
- id: "cand-A-2026-001" # 임시 ID. 형식: cand-{대분류 힌트}-{YYYY}-{NNN}
|
||||
pattern_label: "한 줄 라벨" # taxonomy 본진 항목명과 동일 톤
|
||||
proposed_category: "A | B | C | D | E | F | G | H | I | J"
|
||||
proposed_severity: "S1 | S2 | S3"
|
||||
description: |
|
||||
이 패턴이 무엇이고 왜 AI 티인지 1~3줄 설명.
|
||||
signature_examples: # 실제 발견된 원문 span (최소 1건, 승격 시 2건+)
|
||||
- text: "원문 span"
|
||||
context: "한 문장 정도의 주변 맥락"
|
||||
source_run_id: "2026-04-25-001" # _workspace/{run_id}/01_input.txt에서 발견
|
||||
discovered_by: "ai-tell-detector | korean-style-rewriter | naturalness-reviewer | external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: "윤문 처방 초안 (있을 때만)"
|
||||
occurrences: 1 # 동일 패턴이 다른 run에서 재현될 때마다 +1
|
||||
status: "pending" # pending | promoted | rejected | merged
|
||||
status_reason: "" # 기각·병합 시 사유 + 머지된 본진 ID (있을 때)
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: false
|
||||
```
|
||||
|
||||
## 적재 절차 (에이전트용)
|
||||
|
||||
1. **중복 검사**: 새 후보를 추가하기 전 풀의 기존 `pending` 항목을 훑어 동일 패턴이 있는지 확인. 같은 패턴이면 신규 항목 생성 대신 다음을 갱신:
|
||||
- `occurrences` +1
|
||||
- `signature_examples`에 새 사례 append (최대 5건까지, 그 이상은 누락)
|
||||
- `last_seen_at` 갱신
|
||||
2. **본진 중복 검사**: `ai-tell-taxonomy.md`의 기존 패턴(`A-1`~`J-N`)과 의미가 겹치면 후보로 추가하지 말고, run 산출물에 "기존 ID `X-N`로 분류 가능" 기록만 남긴다.
|
||||
3. **신규 항목 ID 발급**: `cand-{대분류 힌트}-{YYYY}-{NNN}`. NNN은 해당 연도 안에서 풀 전체 누적 카운트(대분류와 무관). 대분류 힌트가 불확실하면 `cand-X-YYYY-NNN`로 두고 taxonomist가 분류.
|
||||
4. **원문 보존 필수**: `signature_examples[].text`는 원문 그대로. 윤문된 형태나 일반화된 형태로 적지 않는다.
|
||||
5. **기록 후 알림**: 적재 직후 본 run의 산출물(예: `05_naturalness_review.json`의 `unclassified_candidates_appended`)에 신규/갱신된 후보 ID 목록을 남겨 taxonomist 점검 trigger.
|
||||
|
||||
## 승격 기준 (taxonomist 운영)
|
||||
|
||||
후보가 다음 조건을 모두 충족하면 본진으로 승격 가능 (taxonomist 최종 판정):
|
||||
|
||||
1. **재현**: `occurrences ≥ 2` (서로 다른 source_run_id 기준 2건 이상)
|
||||
2. **장르 분산**: 같은 작가·같은 run에서만 발견된 패턴은 보류 — 최소 2개 이상의 서로 다른 입력 맥락 필요
|
||||
3. **수술 가능성**: `suggested_fix_draft`가 의미 불변·장르 유지·과윤문 금지 4대 철칙과 충돌하지 않음
|
||||
4. **본진 중복 아님**: 기존 패턴의 변종이면 본진 항목에 보강(예: `A-2`의 시그니처 예문 추가)으로 처리하고 후보는 `merged`로 닫음
|
||||
|
||||
승격 시: 본 후보의 `status`를 `promoted`로 갱신, `status_reason`에 머지된 본진 ID(예: `promoted to A-16`) 기재. 기각 시: `status: rejected` + 사유. 본진 보강으로 흡수: `status: merged` + 본진 ID.
|
||||
|
||||
**기각 사유 표준 라벨:**
|
||||
- `not_ai_specific` — 인간 필자도 흔히 쓰는 표현
|
||||
- `single_run_only` — 한 run에서만 발견, 장르 분산 미달
|
||||
- `genre_dependent` — 특정 장르(에세이·SNS)에서만 자연스러운 변별, 일반화 불가
|
||||
- `subjective_aesthetic` — "어색하다"는 주관 평가에 가깝고 객관 시그니처 부족
|
||||
- `ambiguous_overlap` — 본진 패턴과 경계가 흐려 어느 쪽으로도 분류 불안정
|
||||
|
||||
## 라이프사이클 정책
|
||||
|
||||
- `pending` 상태로 90일 이상 머무르면서 `occurrences == 1`인 후보는 다음 taxonomist 점검 회차에서 자동 후보 만료(`status: rejected`, `status_reason: "single_run_only — 90일 미재현"`).
|
||||
- `promoted`·`rejected`·`merged` 상태는 본 풀에 계속 보존(삭제 금지). taxonomy 변경 이력의 한 축이며, 같은 후보가 재제안되는 것을 방지한다.
|
||||
- 본 풀의 항목 수가 200을 넘으면 `references/archive/pattern-candidates-{YYYY}.md`로 closed(promoted/rejected/merged) 항목을 분리해 본 파일은 pending 중심으로 유지.
|
||||
|
||||
## 외부 contributor 적재 (Issue/PR 경로)
|
||||
|
||||
GitHub Issue 또는 PR로 외부에서 후보를 제출할 때도 본 스키마를 그대로 사용한다.
|
||||
|
||||
- `discovered_by: "external"`
|
||||
- `signature_examples[].source_run_id`: 외부 사례면 Issue/PR 번호 (예: `gh-issue-12`)
|
||||
- 외부 contributor는 `id`를 직접 발급하지 말고 `cand-X-YYYY-pending`로 두면 머지 시 maintainer가 정식 ID 발급
|
||||
- 외부 후보도 동일 승격 기준(재현 2회+·장르 분산·본진 중복 아님)을 적용
|
||||
|
||||
`§6 외부 회귀 검증 케이스 모집`(Issue #4)과 별개 트랙 — Issue #4는 voice profile 회귀용, 본 풀은 신규 패턴 발굴용이다.
|
||||
|
||||
## 풀 운영 주기 (taxonomist)
|
||||
|
||||
기본은 사용자 trigger 기반(taxonomist는 호출되어야 작동). 다음 4가지 trigger 중 하나에 해당하면 풀 점검:
|
||||
|
||||
1. **사용자 명시 요청**: "패턴 풀 점검", "후보 승격 검토", "v1.x 분류 확장"
|
||||
2. **풀 누적 임계**: `pending` 항목 10건 이상
|
||||
3. **고빈도 후보 등장**: 단일 후보의 `occurrences ≥ 3`
|
||||
4. **외부 PR/Issue 도착**: 외부 contributor 후보가 새로 등재됨
|
||||
|
||||
점검 산출물은 `_workspace/taxonomy_changelog.md`에 회차별로 누적(어떤 후보가 어떤 사유로 승격·기각되었는지 추적 가능).
|
||||
|
||||
---
|
||||
|
||||
## 풀
|
||||
|
||||
<!-- pending: 아직 점검 전 -->
|
||||
<!-- 신규 후보는 이 섹션에 append. 적재 절차의 중복 검사·필수 필드를 지킬 것. -->
|
||||
|
||||
(현재 0건 — 회차 1에서 모두 closed 상태로 이동)
|
||||
|
||||
---
|
||||
|
||||
<!-- promoted: 본진 승격 완료 -->
|
||||
|
||||
```yaml
|
||||
- id: "cand-C-2026-001"
|
||||
pattern_label: "숫자 괄호 인덱싱 1) 2) 3)"
|
||||
proposed_category: "C"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
동일 문단 또는 인접 문장에서 항목을 `1) ... 2) ... 3) ...` 형식으로 나열.
|
||||
C-1(첫째·둘째·셋째)·C-2(불릿)와 별개의 표기 시그니처. 한국어 인간 필자도
|
||||
보고서에서 가끔 쓰지만 LLM 산출물에서는 3개 항목이 있을 때 거의 자동으로
|
||||
숫자 괄호 인덱싱이 등장하는 빈도가 압도적으로 높음.
|
||||
signature_examples:
|
||||
- text: "1) 표준화된 OT(Operational Technology) 데이터 수집 인프라가 일반화되면서 학습 데이터 확보가 용이해졌다. 2) 도메인 특화 LLM이 성숙하면서 한국어 운영 매뉴얼·작업 일지 같은 비정형 텍스트의 활용 범위가 넓어졌다. 3) 클라우드 GPU 단가가 2년 전 대비 크게 하락하면서 단일 공장 단위의 자체 학습이 비용 측면에서 정당화 가능한 수준에 들어왔다."
|
||||
context: "샘플 A 둘째 문단 — 제조업 디지털 전환 칼럼톤"
|
||||
source_run_id: "synthetic-claude-pilot-2026-04-25-001a"
|
||||
discovered_by: "ai-tell-detector"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "1) 카카오뱅크·케이뱅크·토스뱅크의 합산 순이익은 전년 동기 대비 18% 증가했지만, 사용자 1인당 평균 매출은 오히려 2.4% 감소했다. 2) 마이데이터 사업자 중 흑자 전환에 성공한 곳은 전체의 12%에 불과하며, 절반 이상은 데이터 활용 사례 발굴 단계에 머물러 있다. 3) 보험 비교·추천 플랫폼은 전년 대비 거래액이 두 배 가까이 늘었으나, 수수료 단가 인하 압력이 동시에 강화되어 영업이익률은 정체되고 있다."
|
||||
context: "샘플 B 둘째 문단 — 핀테크 리포트톤"
|
||||
source_run_id: "synthetic-claude-pilot-2026-04-25-001b"
|
||||
discovered_by: "ai-tell-detector"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
3개 중 1개는 서술문으로 녹이고, 나머지 2개도 1)·2) 표기 대신 "우선~",
|
||||
"다음으로~" 형식으로 어휘 변주. 정말 동일 구조 나열이 의미 있을 때만
|
||||
숫자 괄호를 유지하되 한 문서에 1회 이하.
|
||||
occurrences: 2
|
||||
status: "promoted"
|
||||
status_reason: "promoted to C-9 (회차 1, 2026-04-25)"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- rejected: 기각 -->
|
||||
|
||||
(현재 0건)
|
||||
|
||||
---
|
||||
|
||||
<!-- hold: 데이터 부족으로 다음 회차 이월 -->
|
||||
|
||||
```yaml
|
||||
- id: "cand-A-2026-002"
|
||||
pattern_label: "메타 진입 '~을 살펴보면 / 들여다보면'"
|
||||
proposed_category: "A"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
글의 첫 문장 또는 단락 진입에서 본격 서술 전 "X를 살펴보면 / 들여다보면"
|
||||
형태로 메타 단계를 한 번 거치는 LLM 특유 도입 패턴. 영어 `looking at / when
|
||||
we examine` 직역. 본진 A-1(에 대해) · A-3(에 있어) 인접하지만 형태소가 다름.
|
||||
signature_examples:
|
||||
- text: "최근 한국 제조업의 디지털 전환 흐름을 살펴보면 흥미로운 변화가 감지된다."
|
||||
context: "샘플 A 첫 문장 — 제조업 칼럼톤 도입부"
|
||||
source_run_id: "synthetic-claude-pilot-2026-04-25-001a"
|
||||
discovered_by: "ai-tell-detector"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "2026년 상반기 국내 핀테크 시장의 경쟁 구도를 들여다보면 몇 가지 중요한 신호가 잡힌다."
|
||||
context: "샘플 B 첫 문장 — 핀테크 리포트톤 도입부"
|
||||
source_run_id: "synthetic-claude-pilot-2026-04-25-001b"
|
||||
discovered_by: "ai-tell-detector"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
메타 진입 자체를 삭제하고 본 서술로 직진. "최근 한국 제조업의 디지털 전환 흐름에서
|
||||
흥미로운 변화가 감지된다." 형태로 첫 문장에서 바로 결론 진입.
|
||||
occurrences: 2
|
||||
status: "hold"
|
||||
status_reason: "Gate 1.3 fail — 같은 합성 회차 출처라 작가/도메인 분산 미충족. 다음 회차에 다른 작가·다른 장르 샘플에서 재현되면 재판정"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-H-2026-004"
|
||||
pattern_label: "'결국' 문두 단언 남발"
|
||||
proposed_category: "H"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
GPT가 단언으로 결산할 때 거의 자동으로 문두에 "결국"이 붙음. 본진 H-1의 명시
|
||||
어휘("또한·따라서·즉·나아가·아울러·게다가·더욱이")에 "결국"이 빠져 있는데,
|
||||
한국 매체에 게재된 GPT 출력에서는 H-1 명시 어휘보다 "결국·다시 말해·특히"가
|
||||
압도적으로 많음. 한국어 인간 필자도 사용하지만 한 문서에 9회 이상은 결정적
|
||||
AI 시그니처. 본진 H-1 어휘 셋 자체가 한국 매체 GPT 출력의 실제 분포와 어긋나
|
||||
있다는 발견의 일환.
|
||||
signature_examples:
|
||||
- text: "결국 'AI의 핵심은 일자리 제거보다 일의 방식과 시장의 구조를 바꾸는 것'에 있다는 뜻이다"
|
||||
context: "뉴스핌 ① 본문 핵심 해석"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "결국 산업 구조는 두 층으로 갈린다"
|
||||
context: "뉴스핌 ① 본문 응용 산업"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "결국 한국의 비교우위는 ~ 기존 강한 산업에 AI를 깊숙이 붙여 현장을 바꾸는 나라에 더 가깝다"
|
||||
context: "뉴스핌 ② 본문 한국 승부처"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "결국 살아남는 사람은 '내가 하던 일을 그대로 지키는 사람'이 아니라, 내 일을 AI 시대 방식으로 다시 조립할 줄 아는 사람이다"
|
||||
context: "뉴스핌 ② 본문 인재론 결산"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
한 문서에 "결국" 2회 이하로 제한. 결산 문장은 "결국" 없이 단언으로 직결
|
||||
(예: "결국 산업 구조는 두 층으로 갈린다" → "산업 구조는 두 층으로 갈린다").
|
||||
필요하다면 "정리하면·이는 곧·따져 보면" 등으로 어휘 변주, 단 한 문서에
|
||||
이런 결산 어휘 자체를 3회 이상 누적하지 않음.
|
||||
occurrences: 10
|
||||
status: "hold"
|
||||
status_reason: "회차 3 검증 결과 GPT-우세 시그니처 가능성 강함 — Gemini 4파일에서 1회만 재현(GPT 9회+ vs Gemini 1회). source distinct 3건으로 갱신(GPT 시리즈 2 + Gemini 1)이지만 모델 빈도 격차가 결정적. 회차 4 국내 모델·Claude 검증 시 GPT-특유 시그니처로 메타 분류 검토"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-D-2026-005"
|
||||
pattern_label: "'X은 A가 아니라 B다' 부정-긍정 대구 결산"
|
||||
proposed_category: "D"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
GPT가 명제·결산 문장을 만들 때 거의 자동으로 "A가 아니라 B" 또는 "A보다 B"
|
||||
형태의 부정-긍정 대구로 결산. 한 줄 요약·소제목·문단 결말에서 빈출. C-8(A인가
|
||||
B인가, 질문형)·D-1(종결류)와 시그니처 유형이 다른 결산 공식. 한국어 인간
|
||||
필자도 사용하지만 한 문서에 7회 이상은 GPT 특유.
|
||||
signature_examples:
|
||||
- text: "AI 시대의 승자는 기술 보유자가 아니라 산업 재설계자다"
|
||||
context: "뉴스핌 ① 한 줄 요약"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "위험한 사람은 직급이 낮은 사람이 아니라, 업무가 쉽게 쪼개지고 문서화되며 규칙화되는 사람이다"
|
||||
context: "뉴스핌 ② 인재론 도입"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "AI는 사람을 완전히 밀어내는 기술이라기보다, 사람 1명이 만들어낼 수 있는 가치의 범위를 크게 넓히는 기술에 가깝다"
|
||||
context: "뉴스핌 ① 본문 결산"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "감원보다 확장, 자동화보다 재설계, 기술보다 확산"
|
||||
context: "뉴스핌 ① 결말부 — 3중 대구"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "결국 살아남는 사람은 '내가 하던 일을 그대로 지키는 사람'이 아니라, 내 일을 AI 시대 방식으로 다시 조립할 줄 아는 사람이다"
|
||||
context: "뉴스핌 ② 인재론 결산"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
한 문서에 "A가 아니라 B" 형태의 결산 대구 2회 이하. 결산이 필요한 곳은 단언
|
||||
하나로 (예: "AI 시대의 승자는 기술 보유자가 아니라 산업 재설계자다" → "AI
|
||||
시대의 승부는 산업 재설계에서 갈린다"). 부정-긍정 대구 자체보다 무엇을
|
||||
긍정하는지가 더 또렷한 표현으로.
|
||||
occurrences: 9
|
||||
status: "hold"
|
||||
status_reason: "회차 3 검증 결과 GPT-우세 시그니처 — Gemini 약한 재현(2회). 회차 3에서 D-7 슬롯은 변환 공식 'X에서 Y로'(Gemini 7회·본 후보와 별개 시그니처)에 발급되어 본 후보는 D-8 후보로 보존. 회차 4에서 다른 모델 데이터로 분포 확정"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-C-2026-006"
|
||||
pattern_label: "5~8개 영역 콤마 빠른 나열"
|
||||
proposed_category: "C"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
5개 이상 영역을 콤마로 빠르게 나열하는 패턴이 한 기사에 3~4회 등장. 한국어
|
||||
인간 필자는 보통 3~4개 이상 나열하면 불릿이나 번호를 붙이는데, GPT는 콤마
|
||||
나열을 선호. C-1(첫째·둘째·셋째)·C-2(불릿)와 다른 시그니처.
|
||||
signature_examples:
|
||||
- text: "검색, 번역, 요약, 설계 초안, 데이터 분석, 개발 보조, 고객 응대의 단가를 낮추면서"
|
||||
context: "뉴스핌 ① 7개 영역 콤마 나열"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "광고, 영상, 게임, 출판, 뉴스, 마케팅에서 소량 다품종 제작이 쉬워지며"
|
||||
context: "뉴스핌 ① 6개 영역"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "공정 데이터, 설비 운영, 품질 검사, 예지보전, 수요예측, 공급망 관리 등 AI가 침투할 수 있는 지점이 많다"
|
||||
context: "뉴스핌 ② 6개 영역"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "제조, 금융, 유통, 물류, 의료, 교육, 건설, 공공행정에 붙는 기업간거래(B2B) 소프트웨어 시장이다"
|
||||
context: "뉴스핌 ① 8개 영역"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
5개 이상 나열은 (a) 핵심 2~3개로 압축 + "등"으로 마무리, (b) 정말 모두
|
||||
중요하면 불릿으로 분리, (c) 그룹화해 "제조·물류 같은 산업 현장 영역과
|
||||
의료·교육 같은 서비스 영역" 형태의 의미 묶음으로 재서술. 콤마로 5개 이상
|
||||
한 줄에 늘어놓지 않음.
|
||||
occurrences: 4
|
||||
status: "hold"
|
||||
status_reason: "회차 3 검증 결과 GPT-특유 시그니처 가능성 매우 강함 — Gemini 4파일에서 0회 재현. 회차 4 국내 모델에서도 미재현 시 'GPT-특유' 메타로 promoted 검토 (모델 분포 명시 신규 패턴)"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-C-2026-009"
|
||||
pattern_label: "콜론 부제 헤딩 공식 'X: Y' 또는 'X: A에서 B로'"
|
||||
proposed_category: "C"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
Gemini가 헤딩에 거의 자동으로 콜론을 사용해 '메인 라벨: 부제' 또는 '메인 라벨:
|
||||
주제 명사구' 형태로 구조화. 본진 C-3(반복 헤딩) 인접하지만 별개 — C-3는 도식적
|
||||
분절('## 도입 ## 본론 ## 결론')이고 본 후보는 헤딩 자체에 메타 라벨 + 콜론 +
|
||||
부제를 박는 공식.
|
||||
signature_examples:
|
||||
- text: "## 2026년 핀테크, '일상'을 넘어 '생태계'로 진화하다"
|
||||
context: "Gemini 핀테크 칼럼 메인 헤딩"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "### 서론: 제조업의 미래, AI에 달려있다"
|
||||
context: "Gemini 제조업 보고서 서론 헤딩"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "### 본론 1: 빛과 그림자, 대기업과 중소기업의 디지털 격차"
|
||||
context: "Gemini 제조업 보고서 본론 헤딩"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "### 서론: 기회와 도전의 기로에 선 한국 의료 AI"
|
||||
context: "Gemini 의료 정책문 서론 헤딩"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-004"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
헤딩에서 콜론 + 부제 자체를 제거하고 단일 명사구·동사구로 압축. 정말 부제가
|
||||
필요하면 (a) 본문 첫 문장에 녹이기, (b) 콜론 대신 — 또는 줄바꿈 활용. 한
|
||||
문서에 콜론 부제 헤딩 1회 이하.
|
||||
occurrences: 8
|
||||
status: "promoted"
|
||||
status_reason: "promoted to C-10 (회차 3, 2026-04-25) — 6게이트 모두 통과 (8회·3파일·3도메인 분산)"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-D-2026-010"
|
||||
pattern_label: "변환 공식 'X에서 Y로 / X을 넘어 Y로'"
|
||||
proposed_category: "D"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
Gemini가 패러다임 전환·진화·고도화를 표현할 때 거의 자동으로 사용. D-1·D-2·D-6와
|
||||
별개의 결산/슬로건 공식. C-8(A인가 B인가, 질문형)·D-8 후보(A가 아니라 B,
|
||||
부정-긍정 결산)와도 다른 시그니처 — 변환의 방향성을 강조.
|
||||
signature_examples:
|
||||
- text: "'규모의 경쟁'에서 '전략의 경쟁'으로"
|
||||
context: "Gemini 핀테크 칼럼 인터넷전문은행 헤딩"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "'데이터 조회'를 넘어 '맞춤형 금융 비서'로"
|
||||
context: "Gemini 핀테크 칼럼 마이데이터 헤딩"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "'지식 전달자'에서 '학습 조력자'로"
|
||||
context: "Gemini 교육 블로그 교사 헤딩"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-003"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "'무엇을'에서 '어떻게'로"
|
||||
context: "Gemini 교육 블로그 교육과정 헤딩"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-003"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
"X에서 Y로" 변환 공식을 직접 단언으로 (예: "'지식 전달자'에서 '학습 조력자'로"
|
||||
→ "교사는 더 이상 지식 전달자가 아니다. 학생 곁에서 학습을 돕는다"). 한 문서에
|
||||
변환 공식 1회 이하. 정말 패러다임 전환이 핵심 메시지일 때만 본문 결산에서 1회.
|
||||
occurrences: 7
|
||||
status: "promoted"
|
||||
status_reason: "promoted to D-7 (회차 3, 2026-04-25) — 6게이트 모두 통과 (7회·2파일·2도메인 분산). cand-D-2026-005 'A가 아니라 B'는 별개 시그니처라 D-8 후보로 보존"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-C-2026-011"
|
||||
pattern_label: "굵은 번호 부제 '**N. X:**' 또는 '**N. X: Y**'"
|
||||
proposed_category: "C"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
굵은 글씨 + 번호 + 점 + 부제 + 콜론 + 메타 라벨 형태의 번호 매기기. C-1(첫째·
|
||||
둘째·셋째 평문)·C-9(숫자 괄호)와 다른 변종.
|
||||
signature_examples:
|
||||
- text: "**1. 의료기기 인허가: 속도와 안전의 균형**"
|
||||
context: "Gemini 의료 정책문 본론 1번"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-004"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "**2. 데이터 표준화: 고립된 섬들을 연결해야**"
|
||||
context: "Gemini 의료 정책문 본론 2번"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-004"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "**3. 의료진 수용성 및 환자 안전: 신뢰 구축의 문제**"
|
||||
context: "Gemini 의료 정책문 본론 3번"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-004"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
굵은 글씨와 콜론 부제 모두 제거. 평문 산문으로 풀어쓰거나 헤딩 1단계 낮춰서
|
||||
"1. X" 정도로 단순화. 정책 보고서 장르라도 굵은 번호 + 부제 4개 연속은 회피.
|
||||
occurrences: 4
|
||||
status: "hold"
|
||||
status_reason: "Gate 1.2 fail — 4회 occurrences이지만 단 1개 파일(004 의료 정책문)에서만 발견. source distinct 1건. 회차 4에서 다른 도메인 정책 보고서에서 재현 시 promoted 가능"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- merged: 본진 패턴에 흡수 -->
|
||||
|
||||
```yaml
|
||||
- id: "cand-I-2026-003"
|
||||
pattern_label: "'X은 ~라는 점에 있다' 강조 위치 서술"
|
||||
proposed_category: "I"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
"핵심은 ~라는 점에 있다 / 의의는 ~라는 점에 있다 / 주목할 부분은 ~라는
|
||||
점에 있다" 형식의 강조 위치 서술. 본진 I-2(점·바·수·데), I-3(~라는 것)과
|
||||
의미상 강하게 겹침. 단일 ID 분리 가능성 검토 결과, I-2의 결합형 변종으로
|
||||
판정.
|
||||
signature_examples:
|
||||
- text: "핵심은 AI 도입의 진입장벽이 빠르게 낮아지고 있다는 점에 있다"
|
||||
context: "샘플 A 첫 문단 결말"
|
||||
source_run_id: "synthetic-claude-pilot-2026-04-25-001a"
|
||||
discovered_by: "ai-tell-detector"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "의의는 OT 데이터의 표준화가 여전히 사업장별로 들쭉날쭉하다는 점에 있다"
|
||||
context: "샘플 A 마지막 문단 중간"
|
||||
source_run_id: "synthetic-claude-pilot-2026-04-25-001a"
|
||||
discovered_by: "ai-tell-detector"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "특히 주목할 부분은 마이데이터 사업의 수익 모델이 여전히 정착되지 않았다는 점에 있다"
|
||||
context: "샘플 B 첫 문단 결말"
|
||||
source_run_id: "synthetic-claude-pilot-2026-04-25-001b"
|
||||
discovered_by: "ai-tell-detector"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "결국 핵심은 이용자 데이터 활용의 명확한 기준선을 어디에 그을지에 있다는 점에 있다"
|
||||
context: "샘플 B 마지막 문단 중간"
|
||||
source_run_id: "synthetic-claude-pilot-2026-04-25-001b"
|
||||
discovered_by: "ai-tell-detector"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
"핵심은 ~다" 형식으로 "점에 있다"를 직결 단언으로 치환. 또는 술어 자체를
|
||||
구체 동사로(예: "AI 도입의 진입장벽이 빠르게 낮아진다"). I-2 처방과 동일 라인.
|
||||
occurrences: 4
|
||||
status: "merged"
|
||||
status_reason: "merged to I-2 (회차 1, 2026-04-25) — I-2 본진 항목의 시그니처 예문에 'X은 ~라는 점에 있다' 결합형 추가"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-I-2026-007"
|
||||
pattern_label: "'~라는 뜻이다 / ~다는 뜻이다' 결말 단언 공식"
|
||||
proposed_category: "I"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
GPT가 서술을 형식명사로 결산할 때 거의 자동으로 "~라는 뜻이다" 또는
|
||||
"~다는 뜻이다"로 마무리. 본진 I-3 "~라는 것이다"의 명확한 변종이며
|
||||
의미·기능 동일(서술 결산을 형식명사로 마무리). Gate 2.2 본진 변종 판정으로
|
||||
별도 ID 분리 대신 본진 I-3 보강.
|
||||
signature_examples:
|
||||
- text: "결국 'AI의 핵심은 일자리 제거보다 일의 방식과 시장의 구조를 바꾸는 것'에 있다는 뜻이다"
|
||||
context: "뉴스핌 ① 본문"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "기술 도입보다 인력 전환이 더 큰 병목이 될 수 있다는 뜻이다"
|
||||
context: "뉴스핌 ② 본문"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "한국에서는 이 문제가 더 민감하다는 뜻이다"
|
||||
context: "뉴스핌 ② 본문"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "더 큰 시장은 그 위에 올라가는 응용 산업에서 나올 가능성이 높다는 뜻이다"
|
||||
context: "뉴스핌 ① 본문"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
"~라는 뜻이다 / ~다는 뜻이다"를 "~다" 직접 종결로 (예: "기술 도입보다
|
||||
인력 전환이 더 큰 병목이 될 수 있다는 뜻이다" → "병목은 기술 도입이
|
||||
아니라 인력 전환이다"). 한 문서에 형식명사 결산("~다는 것이다 / ~다는
|
||||
뜻이다 / ~다는 점이다") 합산 2회 이하.
|
||||
occurrences: 4
|
||||
status: "merged"
|
||||
status_reason: "merged to I-3 (회차 2, 2026-04-25) — I-3 본진 항목의 시그니처 예문에 '~라는 뜻이다 / ~다는 뜻이다' 결말 변종 추가"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-H-2026-008"
|
||||
pattern_label: "'이 점에서 / 이 관점에서 / 이 말은' 메타 진입"
|
||||
proposed_category: "H"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
본진 H-3 "이는 ~"의 변종. 앞 문장을 받아 부연 설명할 때 "이 점에서 ~",
|
||||
"이 관점에서 보면 ~", "이 말은 ~" 형태가 빈번. Gate 2.2 본진 변종 판정으로
|
||||
별도 ID 분리 대신 본진 H-3 보강.
|
||||
signature_examples:
|
||||
- text: "이 관점에서 보면 AI 시대 유망 직무도 다시 보인다"
|
||||
context: "뉴스핌 ① 본문"
|
||||
source_run_id: "external-newspim-gpt-2026-04-23-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "이 말은 결국 기술 도입보다 인력 전환이 더 큰 병목이 될 수 있다는 뜻이다"
|
||||
context: "뉴스핌 ② 본문"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "이 점에서 앞으로 강해질 인재는 크게 다섯 부류다"
|
||||
context: "뉴스핌 ② 본문"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "이 점에서 한국은 승산이 있다"
|
||||
context: "뉴스핌 ② 본문"
|
||||
source_run_id: "external-newspim-gpt-2026-04-24-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
"이 점에서 / 이 관점에서 / 이 말은"을 앞 문장과 직접 붙이거나 구체 서술로
|
||||
치환. 예: "이 관점에서 보면 AI 시대 유망 직무도 다시 보인다" → "AI 시대
|
||||
유망 직무도 같은 흐름에서 보인다". H-3 처방 라인.
|
||||
occurrences: 4
|
||||
status: "merged"
|
||||
status_reason: "merged to H-3 (회차 2, 2026-04-25) — H-3 본진 항목의 시그니처 예문에 '이 점에서 / 이 관점에서 / 이 말은' 변종 4건 추가"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-D-2026-012"
|
||||
pattern_label: "Gemini-우세 hype 어휘 (압도적·막강한·폭발적·파격적·대대적·강력한)"
|
||||
proposed_category: "D"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
본진 D-4(혁신적·획기적·전례 없는) 변종. Gemini 출력에서 결정적으로 빈번한
|
||||
"압도적·막강한·폭발적·파격적·대대적·강력한·치열한·뜨거운" 어휘 셋을 본진
|
||||
D-4에 흡수.
|
||||
signature_examples:
|
||||
- text: "압도적 1위 카카오뱅크는 2,300만 명을 넘어선 막강한 월간 활성 이용자(MAU)를 기반으로"
|
||||
context: "Gemini 핀테크 칼럼 — 한 문단에 압도적·막강한 동시 등장"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "파격적인 예적금 금리와 간편한 대출로 가입자 유치에 열을 올렸던"
|
||||
context: "Gemini 핀테크 칼럼"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "젊은 층의 폭발적인 호응을 얻고 있다"
|
||||
context: "Gemini 핀테크 칼럼"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "교육과정 역시 대대적인 개편이 필요합니다"
|
||||
context: "Gemini 교육 블로그"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-003"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
hype 어휘 대부분 삭제 + 구체 수치·사례로 치환 (예: "막강한 월간 활성
|
||||
이용자(MAU)" → "월간 활성 이용자(MAU) 2,300만 명"). 한 문서에 hype 어휘
|
||||
합산 2회 이하.
|
||||
occurrences: 10
|
||||
status: "merged"
|
||||
status_reason: "merged to D-4 (회차 3, 2026-04-25) — D-4 시그니처 어휘 셋에 '압도적·막강한·폭발적·파격적·대대적·강력한' 추가, Gemini 4파일 사례 합류"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-J-2026-013"
|
||||
pattern_label: "Gemini-우세 따옴표 강조 빈도 (한 문서 5회 초과)"
|
||||
proposed_category: "J"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
본진 J-2(따옴표 과다)는 정성 정의만 있었으나 Gemini는 한 문서에 17~33회
|
||||
따옴표 강조 어휘 사용. 본진 J-2에 빈도 임계 명시(한 문서 5회 초과 시 S2 강화)
|
||||
+ Gemini 사례 합류.
|
||||
signature_examples:
|
||||
- text: "'옥석 가리기'·'금융 슈퍼앱'·'데이터 피로감'·'규제 샌드박스'·'동일기능 동일규제'·'심화(Deepening)'·'연결(Connecting)'"
|
||||
context: "Gemini 핀테크 칼럼 한 문서에 따옴표 강조 33회"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-001"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "'무대 위의 현자'·'곁에서 돕는 안내자'·'학습 경험 설계자'·'수포자'·'개별 맞춤형 교육'·'교육 격차'"
|
||||
context: "Gemini 교육 블로그 한 문서에 따옴표 강조 17회"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-003"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
따옴표 강조는 진짜 인용·특수 용례에만 한정. 한 문서 5회 초과 시 S2 강화 적용.
|
||||
개념어 강조는 (a) 본문 흐름에 녹이거나 (b) 첫 등장 시 1회만 따옴표 사용 후 이후
|
||||
한국어 평문으로.
|
||||
occurrences: 52
|
||||
status: "merged"
|
||||
status_reason: "merged to J-2 (회차 3, 2026-04-25) — J-2 본진 항목에 빈도 임계(한 문서 5회 초과 S2 강화) 명시 + Gemini 사례 합류"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
|
||||
- id: "cand-I-2026-014"
|
||||
pattern_label: "정책·보고서 권고형 결말 '~해야 한다 / ~해야 합니다'"
|
||||
proposed_category: "I"
|
||||
proposed_severity: "S2"
|
||||
description: |
|
||||
본진 I-4(~할 필요가 있다)의 변종. Gemini 정책·보고서 출력에서 결말마다 자동
|
||||
등장하는 권고형 단언 종결. 한 문서에 5회 초과 시 정책 칼럼 자동 생성 시그니처.
|
||||
signature_examples:
|
||||
- text: "공유 플랫폼을 구축해야 한다 / 바우처 지원 사업을 대폭 확대하여 ~ 낮춰야 한다 / 핵심 인재를 양성하는 것이 중요하다"
|
||||
context: "Gemini 제조업 보고서 정책 제언 — 4회 연속 권고형 결말"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-002"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
- text: "균형을 맞춰야 합니다 / 구축해야 합니다 / 마련해야 합니다 / 지원해야 합니다"
|
||||
context: "Gemini 의료 정책문 결론 — 5회 연속 권고형 결말"
|
||||
source_run_id: "gemini-pro-2-5-direct-2026-04-25-004"
|
||||
discovered_by: "external"
|
||||
discovered_at: "2026-04-25"
|
||||
suggested_fix_draft: |
|
||||
"~해야 한다 / ~해야 합니다"를 (a) 구체 동사 단언("~를 시급히 추진"), (b) 주체
|
||||
명시 동사("정부는 ~를 도입한다"), (c) 조건문("~이 충족되면 ~가 가능하다") 중
|
||||
하나로 변주. 정책 보고서 장르에서도 한 문서 5회 초과는 자동 생성 신호.
|
||||
occurrences: 10
|
||||
status: "merged"
|
||||
status_reason: "merged to I-4 (회차 3, 2026-04-25) — I-4 본진 항목에 '~해야 한다 / ~해야 합니다' 변종 추가 + 정책·보고서 장르 5회 초과 임계 명시"
|
||||
created_at: "2026-04-25"
|
||||
last_seen_at: "2026-04-25"
|
||||
reviewed_by_taxonomist: true
|
||||
```
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
# Promotion Checklist (v1.3~)
|
||||
|
||||
`pattern-candidates.md` 풀의 후보를 본진(`ai-tell-taxonomy.md`)으로 승격할지 결정할 때, taxonomist가 회차마다 동일한 정량 게이트를 통과시키도록 만든 체크리스트. 본 문서는 taxonomist의 판단 일관성을 시간·운영자에 무관하게 유지하기 위한 운영 표준입니다.
|
||||
|
||||
## 사용법
|
||||
|
||||
후보 1건마다 본 체크리스트를 위에서 아래로 순서대로 적용합니다. 한 게이트라도 fail이면 그 자리에서 판정 종료(승격 불가). 모든 게이트가 pass면 승격합니다. "hold"는 데이터 부족으로 다음 회차 이월(거부 아님).
|
||||
|
||||
판정 결과는 `_workspace/taxonomy_changelog.md`의 회차 표에 후보 ID·결과·통과/실패 게이트 번호 형식으로 기록합니다.
|
||||
|
||||
---
|
||||
|
||||
## Gate 0 — 사전 점검 (skip 시 다음 회차)
|
||||
|
||||
| # | 항목 | 통과 조건 | fail 처리 |
|
||||
|---|------|----------|----------|
|
||||
| 0.1 | 풀 항목 상태 | `status == "pending"` | skip(이미 처리됨) |
|
||||
| 0.2 | 필수 필드 완비 | `pattern_label`·`description`·`signature_examples ≥ 1`·`occurrences ≥ 1` 모두 비어있지 않음 | `hold + status_reason: "missing_required_fields"` |
|
||||
| 0.3 | 사례 신선도 | `last_seen_at`이 90일 이내 | `rejected: "single_run_only — 90일 미재현"` (occurrences == 1일 때만) |
|
||||
|
||||
---
|
||||
|
||||
## Gate 1 — 재현 게이트 (Reproducibility)
|
||||
|
||||
| # | 항목 | 통과 조건 | fail 처리 |
|
||||
|---|------|----------|----------|
|
||||
| 1.1 | 발견 횟수 | `occurrences ≥ 2` | `hold` (다음 회차 재검토) |
|
||||
| 1.2 | run 분산 | 서로 다른 `source_run_id` 2건 이상 | `hold` |
|
||||
| 1.3 | 작가/도메인 분산 | 같은 작가·같은 단일 도메인에서만 발견된 게 아님 (외부 메타로 확인 가능 시) | `rejected: "genre_dependent"` 또는 `hold` |
|
||||
|
||||
**판정 가이드:** 재현 게이트는 "이 패턴이 진짜 일반적인 AI 시그니처인지 vs 단발 사고인지"를 가른다. 1.1·1.2는 정량, 1.3은 정성. 1.3을 판정하기 어렵다면 보수적으로 `hold`.
|
||||
|
||||
---
|
||||
|
||||
## Gate 2 — 본진 중복 게이트 (Deduplication)
|
||||
|
||||
| # | 항목 | 통과 조건 | fail 처리 |
|
||||
|---|------|----------|----------|
|
||||
| 2.1 | 본진 정확 일치 | `ai-tell-taxonomy.md`에 동일 의미·동일 표현의 패턴이 없음 | `merged: "흡수 → {본진ID}"` |
|
||||
| 2.2 | 본진 변종 여부 | 본진 패턴의 변종이 아니거나, 변종이라도 본진 수정으로 흡수 불가능한 별개 시그니처 | `merged: "변종 → {본진ID} 보강"` (본진 항목의 시그니처 예문에 추가, 풀 후보는 닫음) |
|
||||
| 2.3 | 카테고리 단일성 | 후보의 `proposed_category`가 단일(2개 이상 카테고리 후보가 아님) | `hold + status_reason: "category_uncertain"` |
|
||||
|
||||
**판정 가이드:** 풀의 가장 흔한 "부풀려진 후보"는 본진 변종이다. 본진 보강(`merged`)으로 처리하면 본진의 표현력이 늘면서 풀이 가벼워지므로 적극 활용한다.
|
||||
|
||||
---
|
||||
|
||||
## Gate 3 — 분류 적합성 게이트 (Taxonomy Fit)
|
||||
|
||||
| # | 항목 | 통과 조건 | fail 처리 |
|
||||
|---|------|----------|----------|
|
||||
| 3.1 | AI 특이성 | 인간 필자가 "거의 안 쓴다"고 판단할 만한 시그니처 (한국어 필자 중간값 기준) | `rejected: "not_ai_specific"` |
|
||||
| 3.2 | 객관 시그니처 | 어휘·구문·구조·통계로 객관적으로 식별 가능 (주관적 "어색함"이 아님) | `rejected: "subjective_aesthetic"` |
|
||||
| 3.3 | 경계 안정성 | 본진의 인접 패턴과 경계가 명확 (같은 span에 두 패턴이 동시 매칭되는 모호 영역 작음) | `rejected: "ambiguous_overlap"` 또는 `hold` |
|
||||
| 3.4 | 심각도 일관성 | `proposed_severity`(S1/S2/S3)가 본진의 동급 패턴들과 일관 (S1은 "한 번 나와도 결정적"인 것만) | `hold + status_reason: "severity_recalibration_needed"` (taxonomist가 심각도 재산정 후 다음 회차) |
|
||||
|
||||
---
|
||||
|
||||
## Gate 4 — 처방 적합성 게이트 (Fix Feasibility)
|
||||
|
||||
| # | 항목 | 통과 조건 | fail 처리 |
|
||||
|---|------|----------|----------|
|
||||
| 4.1 | 처방 존재 | `suggested_fix_draft`가 비어있지 않거나, taxonomist가 본 회차에 작성 가능 | `hold + status_reason: "no_viable_fix_yet"` |
|
||||
| 4.2 | 의미 불변 충돌 없음 | 처방 적용 시 사실·주장·수치·인용이 보존됨 | `rejected: "subjective_aesthetic"` (처방 자체가 의미 훼손이면 패턴 분류로 부적합) |
|
||||
| 4.3 | 장르 유지 충돌 없음 | 처방이 입력 장르(칼럼·리포트·블로그·공적)에서 이탈하지 않음 | `rejected: "genre_dependent"` 또는 처방 수정 후 재판정 |
|
||||
| 4.4 | 과윤문 위험 낮음 | 처방이 변경률 30% 임계를 단독으로 끌어올리지 않음 | 처방 수정 후 재판정 (게이트 fail은 아님) |
|
||||
| 4.5 | rewriting-playbook 충돌 없음 | `references/rewriting-playbook.md`의 카테고리별 레시피와 일관 | 윤문가와 합의 후 재판정 (`hold + status_reason: "playbook_conflict"`) |
|
||||
|
||||
---
|
||||
|
||||
## Gate 5 — 본진 위계 게이트 (Authority Hierarchy)
|
||||
|
||||
| # | 항목 | 통과 조건 | fail 처리 |
|
||||
|---|------|----------|----------|
|
||||
| 5.1 | 무력화 정책 명시 | 새 패턴이 voice profile로 무력화 가능한지/불가능한지 명시 가능 | `hold + status_reason: "authority_unspecified"` |
|
||||
| 5.2 | multiplier 캡 충돌 없음 | 새 패턴의 무력화 가능 옵션이 본진 권한 위계 §4의 캡(일반 ≤2.0, D ≤1.5, A-8/C-5 = 1.0)과 정합 | 캡 정책 갱신 후 재판정 |
|
||||
|
||||
**판정 가이드:** 게이트 5는 v1.2 권한 위계 도입 이후 신규 게이트. 새 패턴이 D 카테고리에 들어간다면 multiplier 캡이 자동으로 1.5 적용되는지 확인. 만약 새 패턴이 voice profile로 끌 수 없는 결정적 시그니처라면 `disable_blocked: true` 메타와 함께 본진 등재.
|
||||
|
||||
---
|
||||
|
||||
## 종합 판정 매트릭스
|
||||
|
||||
| Gate 0 | Gate 1 | Gate 2 | Gate 3 | Gate 4 | Gate 5 | 결과 |
|
||||
|--------|--------|--------|--------|--------|--------|------|
|
||||
| pass | pass | pass | pass | pass | pass | **promoted** (본진에 새 ID 발급) |
|
||||
| pass | pass | 2.1 fail | - | - | - | **merged** (본진 흡수) |
|
||||
| pass | pass | 2.2 fail | - | - | - | **merged** (본진 항목 보강) |
|
||||
| pass | fail | - | - | - | - | **hold** (재현 부족) |
|
||||
| pass | pass | 2.3 fail | - | - | - | **hold** (카테고리 모호) |
|
||||
| pass | pass | pass | 3.1·3.2 fail | - | - | **rejected** |
|
||||
| pass | pass | pass | pass | 4.2·4.3 fail | - | **rejected** |
|
||||
| 0.3 fail | - | - | - | - | - | **rejected** (90일 미재현 단일) |
|
||||
|
||||
---
|
||||
|
||||
## 자동 검증 가능 항목
|
||||
|
||||
본 체크리스트의 일부는 풀 파일을 파싱해 정량적으로 자동 판정할 수 있습니다. 향후 스크립트화 시 우선순위:
|
||||
|
||||
- **Gate 0.2** (필수 필드): YAML 파서로 즉시 자동화 가능
|
||||
- **Gate 0.3** (90일 만료): `created_at` 비교로 자동화 가능
|
||||
- **Gate 1.1** (occurrences): 정수 비교
|
||||
- **Gate 1.2** (run 분산): `signature_examples[].source_run_id` distinct count
|
||||
- **Gate 5.2** (multiplier 캡): 본진 카테고리 매핑으로 자동 검증
|
||||
|
||||
나머지 게이트(2.x, 3.x, 4.x, 5.1)는 의미 판단이 들어가므로 taxonomist의 정성 판정이 필요합니다. 자동 검증 스크립트는 추후 별도 PR에서 `scripts/check_promotion.py`로 추가 예정 — 현 v1.3에서는 taxonomist가 본 체크리스트를 수기 적용합니다.
|
||||
|
||||
---
|
||||
|
||||
## 사용 예시
|
||||
|
||||
회차 N의 후보 `cand-A-2026-007` 점검:
|
||||
|
||||
```
|
||||
Gate 0.1 ✓ pending
|
||||
Gate 0.2 ✓ 필수 필드 완비
|
||||
Gate 0.3 ✓ last_seen_at 14일 전
|
||||
Gate 1.1 ✓ occurrences = 3
|
||||
Gate 1.2 ✓ source_run_id distinct = 3
|
||||
Gate 1.3 ✓ 작가 2명·장르 2종 분산
|
||||
Gate 2.1 ✓ 본진 정확 일치 없음
|
||||
Gate 2.2 ✗ 본진 A-2(`~를 통하여` 남발)의 변종으로 판단 — `~을 매개로` 표현
|
||||
→ merged: 흡수 → A-2 (본진 항목의 시그니처 예문에 `~을 매개로` 추가)
|
||||
```
|
||||
|
||||
회차 N의 후보 `cand-D-2026-012` 점검:
|
||||
|
||||
```
|
||||
Gate 0~1 ✓ all pass
|
||||
Gate 2.1 ✓ 본진 일치 없음
|
||||
Gate 2.2 ✓ 변종 아님 (별개 시그니처)
|
||||
Gate 2.3 ✓ proposed_category = D 단일
|
||||
Gate 3.1 ✓ AI 특이성 충족
|
||||
Gate 3.2 ✓ 객관 시그니처
|
||||
Gate 3.3 ✓ 경계 안정
|
||||
Gate 3.4 ✓ S2 적정
|
||||
Gate 4.1 ✓ suggested_fix_draft 존재
|
||||
Gate 4.2~4.5 ✓ 모두 통과
|
||||
Gate 5.1 ✓ 무력화 가능 (D 카테고리이지만 새 D-7로서 multiplier 캡 1.5 적용)
|
||||
Gate 5.2 ✓ 캡 충돌 없음
|
||||
→ promoted: 본진에 D-7로 신규 등재, taxonomy "버전 관리" 섹션에 v1.3 항목 추가
|
||||
```
|
||||
133
.claude/skills/humanize-korean/references/quick-rules.md
Normal file
133
.claude/skills/humanize-korean/references/quick-rules.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# Quick Rules — Monolith Fast Path 전용 (v2.0)
|
||||
|
||||
`humanize-monolith` 에이전트가 한 콜에서 탐지·윤문·자체검증을 끝내기 위해 사용하는 슬림 룰북. 본진 `ai-tell-taxonomy.md`(590줄)에서 S1·S2 핵심 패턴만 추려 처방과 함께 한 줄로 압축했다.
|
||||
|
||||
**원칙:** 정의 1줄 + 처방 1줄. 예문 생략. 본진 ID와 1:1 매칭.
|
||||
|
||||
**Do-NOT (탐지·윤문 모두 제외):** 고유명사·제품명·모델명·기관명, 수치·날짜·단위, 큰따옴표 안 직접 인용, 법률 조문, 수학·화학·통계 표기, 영어 약어(LLM·GPU·MCP·API 등 업계 표준).
|
||||
|
||||
**과윤문 가드:** 변경률 30% 초과 = 경고, 50% 초과 = 강제 중단·롤백.
|
||||
|
||||
---
|
||||
|
||||
## A. 번역투 (Translation-ese)
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| A-1 | "~에 대해(서)" | S1 | 목적격 조사로 직결("X에 대해 논의" → "X를 논의") |
|
||||
| A-2 | "~를 통해/통하여" 남발 | S1 | "~로", "~해서", "~함으로써"로 분산 |
|
||||
| A-3 | "~에 있어(서)" | S1 | "~에서", "~을 볼 때" |
|
||||
| A-4 | "~라는 점에서" 3회+ | S2 | "~서", "~라는 이유로" |
|
||||
| A-5 | "~와 관련하여/관련된" | S2 | "~에", "~의" |
|
||||
| A-6 | "~에 기반하여/바탕으로" 남발 | S2 | "~로", "~을 보고" |
|
||||
| A-7 | "가지고 있다" / have·make·take·give + N 직역 | S1 | 형용사·동사 환원 또는 이중주어("회의를 가지다" → "회의를 했다", "강한 경쟁력을 가지고 있다" → "경쟁력이 강하다") |
|
||||
| A-8 | 이중 피동 "~되어진다" | S1 | 능동 또는 단일 피동 ("판단되어진다" → "판단된다") |
|
||||
| A-9 | "~에 의해" 피동 | S2 | 행위자를 주어로("AI에 의해 생성" → "AI가 만든") |
|
||||
| A-10 | "~할 수 있다" 남발 | S2 | 단언으로("높일 수 있다" → "높인다") |
|
||||
| A-11 | "~을 위해" 목적절 남발 | S2 | "~려고", "~위한" |
|
||||
| A-15 | 추상 주어 + 만능 동사 / 사역·인지 동사 | S2 | 구체 주어로 환원, 사역은 "X 때문에/덕분에/로 인해" 부사절, 인지 동사(suggest/show/indicate/reveal)는 "~에 따르면 ~이다"·"~으로 ~이 드러났다" 분리 |
|
||||
| A-16 | "그/그녀/그것/그들" 단락 ≥3회 영어 대명사 직역 | S1 | 50%+ 영형(생략) 또는 호칭·명사구로 (김도훈 2009) |
|
||||
| A-18 | 명사 앞 ≥3어절 관형구·관계절 좌향 수식 | S2 | 문장 분리 또는 후치 동격절("X를 만났는데, 그 X는 …") (박옥수 2018) |
|
||||
| A-19 | 이중 조사 "~에서의/~에로의/~으로의/~에의/~으로부터의" | S2 | 절·구로 풀어쓰기. 단순 ~의는 비대상 (김정우 2007) |
|
||||
|
||||
## B. 영어 인용·용어 과다
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| B-1 | 한글 + 괄호 영어 매번 ("~(Sovereign AI)" 처럼) | S2 | 첫 등장만 병기, 이후 한글만 |
|
||||
| B-2 | 영어 어휘 직역 가능한데 그대로 | S2 | 한국어로 옮기되 업계 표준은 유지 |
|
||||
|
||||
## C. 구조적 AI 패턴
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| C-5 | 이모지 남발 | S1 | 장르 칼럼·리포트면 전부 삭제 |
|
||||
| C-7 | "먼저·반면·결국" 3단 공식 | S2 | 접속사 1~2개로 줄이거나 본문에 녹여 제거 |
|
||||
| C-8 | "A인가·B인가" 대구 반복 | S2 | 한 번만 살리고 나머지는 평서문으로 |
|
||||
| C-9 | 숫자 괄호 인덱싱 "(1)·(2)·(3)" | S2 | 본문에 녹이거나 단순 줄바꿈 |
|
||||
| C-10 | 콜론 부제 헤딩 "X: Y" 반복 | S1 | 헤딩 짧게 또는 평서 헤딩으로 |
|
||||
| C-11 | 연결어미 뒤 쉼표 (-고/-며/-지만/-며서/-아서/-어서 직후 쉼표) | S1 | 쉼표 제거. 6+회=강한 신호. KatFish 4.84배 분리도 |
|
||||
|
||||
## D. AI 특유의 관용구 (Signature Phrases)
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| D-1 | 결산 피벗 lexicon "결론적으로/따라서/이를 통해/그러므로/요약하면/정리하면" | S1 | 3회 초과 시 1~2건 다른 종결로 치환, 나머지 삭제 |
|
||||
| D-2 | "시사하는 바가 크다/주목할 만하다" | S1 | 삭제 또는 구체 결론으로 |
|
||||
| D-3 | "본질적으로/핵심적으로" | S1 | 삭제 |
|
||||
| D-4 | hype 어휘(파격적·압도적·강력한·획기적·치명적) 3회+ | S1 | 구체 수치·사실로 환원 |
|
||||
| D-5 | 의인화 추상 주어("기술이 묻는다·시대가 부른다") | S1 | 사람·기관 주어로 |
|
||||
| D-6 | 결말 공식 "~할 때다/~해야 한다/~지금이야말로" | S1 | 평서로 닫거나 삭제 |
|
||||
| D-7 | 변환 공식 "X에서 Y로" 반복 | S2 | 한 번만, 나머지는 일반 서술 |
|
||||
|
||||
## E. 리듬·종결어미
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| E-1 | 문장 길이 균일(stdev 8 미만) | S2 | 단문 1~2개 / 장문 1개를 각 문단에 의도적 삽입 |
|
||||
| E-2 | 동일 종결어미 "~다" 4문장 연속 + 진행형 "~고 있다" 자동 매핑 | S2 | "~었다·~ㄴ다·~는다·~기 마련이다·~ㄹ 것이다" 등 다양화. "~고 있다" 단순 시제 환원 가능 시 환원("읽고 있다" → "읽는다") |
|
||||
| E-7 | 청자 경어법 4단계(해라/하게/하오/해요/합쇼) 일관성 손실 (대화·구어 한정) | S2 | 한 단락 내 혼용 금지, 격식 일관 (김혜영 2019, estimated) |
|
||||
|
||||
## F. 과도한 수식·중복
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| F-4 | 한자어 명사화 -성/-적/-화 + 영어 명사화 -tion/-ment/-ness/-ity 누적 (한 글 12회+) | S2 | 동사·형용사 어근으로 환원("the implementation of the policy" → "정책 시행" 또는 "정책을 시행하기") |
|
||||
| F-5 | "~적 N" 추상 체인 ("전략적 함의·실천적 기반") | S2 | 명사+명사 또는 풀어쓰기("전략 함의·실천의 기반") |
|
||||
|
||||
## G. Hedging
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| G-1 | "~것이다/~할 것이다" 미래 단정 남발 | S2 | 현재형·확정형으로 |
|
||||
| G-2 | "~로 보인다/~인 듯하다" 추정 남발 | S2 | 단언 가능한 곳은 단언 |
|
||||
| G-3 | 안전 균형 lexicon "양쪽 모두/두 가지 모두/장점도 있지만/신중하게/균형" | S2 | 4회 초과 시 1~2건 화자 입장으로 치환 |
|
||||
|
||||
## H. 접속사 남발
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| H-1 | 문두 접속사 "또한·따라서·즉·나아가·아울러·게다가·더욱이" 5회+ | S1 | 대량 제거. 문장 자체가 흐름을 잡게 |
|
||||
| H-3 | 메타 진입 "이는·이 점에서·이 관점에서·이 말은" 3회+ | S1 | 본문에 녹이거나 삭제 |
|
||||
| H-4 | "즉" 남발 | S2 | 1회로 제한 |
|
||||
|
||||
## I. 형식명사·의존명사
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| I-1 | "~인 것이다/~한 것이다" 결말 | S1 | 평서형으로 |
|
||||
| I-2 | "X은 ~라는 점에 있다" | S2 | "X는 ~다" 직설로 |
|
||||
| I-3 | "~다는 뜻이다/~다는 의미다" 결말 | S2 | 본문에 풀어 쓰기 |
|
||||
| I-4 | 권고형 결말 "~해야 한다·~합니다" 반복 | S2 | 평서·단언으로 |
|
||||
|
||||
## J. 시각 장식
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| J-1 | 헤딩 마크다운 ** 강조 남발 | S2 | 칼럼·리포트면 거의 다 제거 |
|
||||
| J-2 | 따옴표 강조 5회+ | S1 | 핵심 한두 개만 살리고 평어로 |
|
||||
| J-3 | 불릿 리스트 (장르가 칼럼·리포트일 때) | S2 | 문단 산문으로 통합 |
|
||||
|
||||
---
|
||||
|
||||
## 자체검증 체크리스트 (monolith 윤문 후 자가 점검)
|
||||
|
||||
윤문 직후 5초 내에 다음을 자체 점검한다. 한 항목이라도 위반이면 해당 edit 롤백.
|
||||
|
||||
1. **고유명사·수치·날짜·인용 100% 보존**: 원문 대비 한 글자도 다르지 않은가
|
||||
2. **변경률**: 30% 이하인가 (50% 초과는 작업 중단)
|
||||
3. **장르 이탈 없음**: 칼럼이 에세이·문학으로 변하지 않았는가, 리포트가 블로그체로 떨어지지 않았는가
|
||||
4. **register 보존**: 원문 격식체면 결과도 격식체. 평어체로 떨어뜨리지 않는다
|
||||
5. **잔존 S1 패턴 0건**: D-1~D-7, A-7, A-8, A-16, C-5, C-10, C-11, H-1, I-1, J-2 핵심 S1이 남아있지 않은가
|
||||
6. **인공 표현 자제**: 원문에 없던 비유·수사·문학적 표현을 윤문 과정에서 임의로 추가하지 않았는가
|
||||
|
||||
위반 시: edit 롤백 → 다시 윤문 → 재점검. 자체 루프 최대 1회. 이상 미해결이면 결과를 그대로 출력하되 `summary.md`에 "자가검증 미통과 항목 N건" 표기.
|
||||
|
||||
## 등급 기준 (자가 채점)
|
||||
|
||||
- **A**: S1 잔존 0, S2 잔존 2 이하, 변경률 10~25%, 자체검증 6항 모두 통과
|
||||
- **B**: S1 잔존 0, S2 잔존 4 이하, 자체검증 5항 이상 통과
|
||||
- **C**: S1 잔존 1~2 또는 자체검증 4항 이하 통과 — 사용자에게 strict 모드 권고
|
||||
- **D**: S1 잔존 3+ 또는 변경률 50% 초과 — 작업 중단 권고
|
||||
|
||||
> v2.0 신규/보강은 A-7·A-15·A-16·A-18·A-19·E-2·E-7·F-4 **8건 (A-17 hold)**. 학술 인용 전문은 `references/scholarship.md`. post-editese 3축 metric은 본 룰북 미반영(metric only 트랙). A-17 무정물·추상명사 '-들'은 학술 anchor(전영철 2007·곽은주·진실로 2011) 강하나 외부 회차(2026-05-07 위키 6편)에서 양성 0건 — NMT 원본 출력 회차 후 v2.1에서 동일 ID로 재평가.
|
||||
|
|
@ -119,6 +119,34 @@
|
|||
- **따옴표**: 인용·특수 용례에만 한정.
|
||||
- **대시(—)**: 1문서 1~2회 이하. 나머지는 쉼표·괄호·문장 분리.
|
||||
|
||||
### 1.X. 영-한 PE 통합 체크리스트 (보고서 §5.1, 15항목 · v2.0 신규)
|
||||
|
||||
> Toral 2019·Baker 1993·Toury 1995 + 한국 PE 가이드라인(윤미선 외 2018·김혜림 2022·이상빈 2017·2018a·2018b·마승혜 2018) 통합. 본진 패턴 ID에 처방을 묶어 윤문가가 한 번에 적용 가능한 형태로 압축. 학술 출처 전문은 `references/scholarship.md`.
|
||||
|
||||
| PE# | 트리거 | 처방 한 줄 | 본진 ID |
|
||||
|---|---|---|---|
|
||||
| PE1 | 무생물 주어 + 사역·인지 동사 | "X 때문에/덕분에/로 인해 Y" 부사절 또는 "…에 따르면 …이다" 분리 구문 | A-15·D-5 |
|
||||
| PE2 | "~에 의해" by-passive | 능동태 복귀 또는 "~에/~에게"로 단순화 | A-9 |
|
||||
| PE3 | 이중 피동 "~되어지다·~여지다" | 단순 피동 "~되다·~지다·잊히다·보이다" | A-8 |
|
||||
| PE4 | "그/그녀/그것/그들" 단락 ≥3회 | 50% 이상 영형(생략) + 일부 호칭·명사구 | A-16 |
|
||||
| PE5 | 무정물·추상명사 + "-들" | 거의 모두 삭제. 분포성은 "여러·다양한·갖가지·저마다·각자" | (A-17 hold — v2.1 부활 대기, scholarship.md §4) |
|
||||
| PE6 | 명사 앞 ≥3어절 관형구 | 문장 분리 또는 후치 동격절 ("X를 만났는데, 그 X는 …") | A-18 |
|
||||
| PE7 | "have/make/take/give + N" 직역 ("회의를 가지다") | 동사 환원 ("회의를 했다") 또는 이중주어 ("X는 Y가 …") | A-7 |
|
||||
| PE8 | "-에서의·-에로의·-으로의·-에의" 이중 조사 (단순 ~의는 제외, C5) | 절·구로 풀어쓰기 ("주점 2층에서 시작한 살림") | A-19 |
|
||||
| PE9 | "~다" ≥4문장 연속 | "~었다·~ㄴ다·~는다·~기 마련이다·~ㄹ 것이다·~을 수 있다" 다양화 | E-2 |
|
||||
| PE10 | "~고 있다" 남발 | 단순 시제 환원 가능성 검토 ("읽고 있다 → 읽는다") | E-2 |
|
||||
| PE11 | "-tion·-ment·-ness·-ity" 한국어 명사 직역 ("the implementation of the policy") | 동사·형용사로 풀기 ("정책 시행" / "정책을 시행하기") | F-4 |
|
||||
| PE12 | "~로부터·~에 관하여·~을 통하여" | 문맥 자연 표현으로 대체 (전치사구 1대1 매핑 거부) | A-2·A-5 |
|
||||
| PE13 | 영어 단순 현재·과거 단조 매핑 | 한국어 서사 시제·서법 다양화 ("~었던·~었다가·~더라·~었으니") | E-2 |
|
||||
| PE14 | 대화체 화자–청자 관계 누락 | 해라/하게/하오/해요/합쇼체 일관 적용 (장르 가드: 대화·구어 한정) | E-7 (estimated, C1) |
|
||||
| PE15 | "Mr./Ms./Dr." 직역 ("그/그녀") | 한국어 호칭(선생님·박사님·과장님) 또는 생략 | A-16 |
|
||||
|
||||
> **caveat 가드**:
|
||||
> - C3 — post-editese 3축 직접 적용 시 "speculative: true" 플래그 (한국어 정량 검증 부재).
|
||||
> - C5 — PE8/A-19에서 단순 "~의"는 탐지·윤문 대상 명시적 제외.
|
||||
> - C1 — PE14 청자 경어법 임계는 김혜영 2019 PDF 원문 확보 전까지 "estimated" 유지.
|
||||
> - PE5(A-17 hold) — 학술 anchor·metric 검증용 보존, 본진 등재는 NMT 원본 회차 후 v2.1.
|
||||
|
||||
## 2. 변경률 모니터링
|
||||
|
||||
- 윤문가는 변경 전후 텍스트의 **레벤슈타인 거리 / 원문 길이**를 계산해 변경률을 기록한다.
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
# Sample Collection Pipeline (v1.3~)
|
||||
|
||||
서브 패턴 발굴 폭을 넓히기 위해 다양한 AI 출력 한글 샘플을 의도적으로 수집·익명화·풀 입력으로 투입하는 운영 가이드. `pattern-candidates.md`가 그릇이라면 본 문서는 그 그릇을 채우는 호스의 정의입니다.
|
||||
|
||||
## 왜 별도 파이프라인이 필요한가
|
||||
|
||||
기본 humanize-korean run은 사용자가 가져온 단일 입력 한 건만 처리합니다. 그 결과 풀에 누적되는 후보는 사용자의 일상 작업물(주로 한 작가·한 장르·한 모델 출력) 분포에 강하게 편향됩니다. 분류 체계가 "특정 사용자의 글" 전용으로 좁아지지 않으려면, taxonomist가 풀 점검 회차마다 서로 다른 출처의 샘플도 함께 들여다볼 필요가 있습니다.
|
||||
|
||||
본 파이프라인은 그 균형을 맞추는 의도적 다양성 투입 채널입니다.
|
||||
|
||||
## 4축 다양성 매트릭스
|
||||
|
||||
수집 회차마다 가능한 한 4축 모두에 분산된 샘플을 모읍니다.
|
||||
|
||||
### 1. 모델 다양성
|
||||
- **OpenAI**: GPT-4o, GPT-4-turbo, o-series 추론 모델
|
||||
- **Anthropic**: Claude Sonnet, Claude Opus
|
||||
- **Google**: Gemini 1.5/2.x Pro, Flash
|
||||
- **국내**: HyperCLOVA X, Solar, Exaone
|
||||
- **OSS**: LLaMA·Qwen·Mistral 한국어 finetune
|
||||
|
||||
같은 입력 프롬프트라도 모델별 출력 시그니처가 다릅니다. 특정 모델에서만 보이는 패턴(예: 특정 어미 선호·특정 관용구 빈도)을 발굴하려면 동일 prompt × 다중 모델 비교가 효과적입니다.
|
||||
|
||||
### 2. 장르 다양성
|
||||
- **칼럼·논평**: 신문·매거진 톤. 단언·논리 흐름 중심
|
||||
- **리포트·보고서**: 격식체. 수치·인용 풍부. 피동·하청체 빈도 높음
|
||||
- **블로그·SNS**: 구어체·이모지·해시태그 허용. C·J 카테고리 패턴 확인용
|
||||
- **공적 연설·축사**: 격식 최상. F·G 카테고리(수식·hedging) 확인용
|
||||
- **단행본 에세이/비소설**: 장문·서사. E(리듬)·D(관용구) 확인용
|
||||
- **기술 문서·튜토리얼**: 코드 블록 혼재. B(영어 용어) 카테고리 확인용
|
||||
- **마케팅 카피**: 짧고 강한 문장. C(구조) 카테고리 확인용
|
||||
|
||||
### 3. 길이 다양성
|
||||
- 단문(500자 이하): 짧은 답변·SNS 포스트 — 문서 전역 패턴(E·C) 약함, 어휘 패턴(D·F) 강조
|
||||
- 중문(500~3,000자): 일반 칼럼 — 균형 잡힌 검출
|
||||
- 장문(3,000~30,000자): 리포트·단행본 1장 — E(리듬)·H(접속사 분포) 정밀 측정
|
||||
|
||||
### 4. 작가/도메인 다양성
|
||||
- 단일 작가의 일관 voice 샘플(self-similar set)
|
||||
- 익명 다작가 샘플(diverse pool)
|
||||
- 특정 도메인(법률·의학·교육·금융) 텍스트
|
||||
|
||||
같은 작가에서만 발견된 패턴은 풀의 장르 분산 기준을 통과 못 하므로(`hold` 또는 `genre_dependent` 기각), 다작가 샘플 비중이 중요합니다.
|
||||
|
||||
## 수집 채널
|
||||
|
||||
### A. 사용자 자발 제출
|
||||
- 일상 humanize-korean run의 입력은 자동으로 풀 적재 입력이 됩니다(에이전트가 알아서 처리).
|
||||
- 별도 제출 채널: GitHub Issue (`pattern-sample` 라벨). 익명화·저작권은 제출자 책임.
|
||||
|
||||
### B. 합성 샘플 생성 (controlled experiment)
|
||||
같은 prompt × 다중 모델 → 모델별 출력 시그니처 비교. 운영자(taxonomist 호출자)가 다음 표준 prompt 세트로 정기 수집:
|
||||
|
||||
- "AI 규제에 대한 짧은 칼럼을 1,500자로 써주세요"
|
||||
- "Q3 영업실적 보고서 도입부를 2,000자로 작성해주세요"
|
||||
- "신규 SaaS 제품 출시 블로그 포스트를 800자로 써주세요"
|
||||
- "AI 윤리에 관한 학회 환영사를 1,000자로 작성해주세요"
|
||||
- "독서 에세이 한 챕터를 4,000자로 써주세요"
|
||||
|
||||
세트는 7개 장르 × 3~5개 모델 = 21~35개 합성 샘플. 한 번에 다 모으지 말고 풀 점검 회차마다 1개 장르 × 다중 모델로 들여다보면 점검 한 회차의 작업량이 균형 잡힙니다.
|
||||
|
||||
### C. 공개 데이터셋·웹 샘플
|
||||
- 익명 공개 코퍼스(저작권·라이선스 확인 후만)
|
||||
- 본인 동의 받은 외부 contributor의 일상 AI 출력 산출물
|
||||
|
||||
### D. 외부 contributor 후기
|
||||
Issue/PR 본문에 실전 적용 결과(예: simonsez9510의 단행본 8.5만 자 후기)가 들어오면, 그 안에 인용된 원문 span을 풀에 후보로 등재할 수 있습니다(`discovered_by: "external"`).
|
||||
|
||||
## 익명화·저작권 정책
|
||||
|
||||
풀에 들어가는 모든 `signature_examples[].text`는 다음 규칙을 지킵니다.
|
||||
|
||||
1. **개인 식별 정보 제거**: 사람 이름·이메일·전화번호·주소는 익명 토큰(`{이름}`·`{회사명}`·`{도시}`)으로 치환. 단, 공인의 공적 발언은 인용 가능.
|
||||
2. **고유명사 일반화 옵션**: 패턴이 어휘에 있는 게 아니라 구조에 있다면 고유명사를 일반화해도 무방(예: "삼성전자가 발표했다" → "{기업명}이 발표했다"). 어휘 자체가 패턴이면 원형 보존.
|
||||
3. **저작권 fair use 한계**: 한 출처에서 인용한 span 길이는 한 문장(보통 50~80자) 이하. 그 이상 인용이 필요하면 출처 명시 + 라이선스 확인.
|
||||
4. **상업 비밀·미공개 정보 제외**: 사용자 본인 작업물이라도 미공개 사업 정보·계약 내용 등은 익명화로 끝나지 않을 수 있으므로 제외.
|
||||
5. **출처 메타**: `signature_examples[].source_run_id`는 운영 안정성을 위해 사용자 식별 가능 정보를 포함하지 않는다(run_id는 날짜+숫자만). 외부 contributor의 PR 후보는 PR 번호 사용(`gh-pr-12`).
|
||||
|
||||
본 정책 위반이 의심되는 후보는 taxonomist가 점검 회차에서 `rejected: privacy_or_copyright_concern`로 닫고 풀에서 제외 (예외 사유 라벨로 `not_ai_specific` 등 표준 라벨 외에 추가).
|
||||
|
||||
## 회차별 수집 권장 분량
|
||||
|
||||
한 점검 회차 기준:
|
||||
- 합성 샘플(채널 B): 1개 장르 × 3개 모델 = 3건 (10~20분 작업)
|
||||
- 사용자 자발(채널 A): 직전 회차 이후 누적된 양 그대로
|
||||
- 외부(채널 C·D): 들어온 양 그대로
|
||||
|
||||
회차당 신규 후보 누적이 20건 이상이면 점검 부하가 커지므로, 합성 샘플 수집은 풀의 pending이 5건 이하로 떨어졌을 때만 실행합니다(taxonomist 자체 판단 가능).
|
||||
|
||||
## 다음 단계와의 관계
|
||||
|
||||
본 문서는 **무엇을·어디서 모으는지**까지만 정의합니다. 모은 후보가 실제로 본진에 승격되기까지의 정량 게이트는 PR 5의 승격 자동 검증 체크리스트에서 처리됩니다.
|
||||
|
||||
`§6 외부 회귀 검증 케이스 모집`(Issue #4)와 본 파이프라인은 별개입니다 — Issue #4는 voice profile 회귀용, 본 파이프라인은 신규 패턴 발굴용. 둘이 서로의 영역을 침범하지 않도록 라벨 분리(`pattern-sample` vs `voice-regression`).
|
||||
289
.claude/skills/humanize-korean/references/scholarship.md
Normal file
289
.claude/skills/humanize-korean/references/scholarship.md
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
# Humanize KR Scholarship Reference (v2.0)
|
||||
|
||||
> **외부 SSOT** — 본진 분류 체계(`references/ai-tell-taxonomy.md`)는 패턴 행마다 한 줄 메타(`source_short`)로 이 파일을 가리킨다. 학술 출처 전문(full text)은 본 파일에 보존하여 SSOT 룰북의 슬림성을 해치지 않는다.
|
||||
>
|
||||
> **출처 보고서**: 한국어 번역투(translationese) 종합 연구보고서: 영한 번역과 AI 후편집의 통합적 관점 (2026-05-07 distilled, 540 lines markdown).
|
||||
> 학자 이름·연도·저널·페이지·DOI는 보고서 verbatim. 자체 추정·확장 없음.
|
||||
|
||||
---
|
||||
|
||||
## 한국 번역학계 8대 번역투 정통성 계보
|
||||
|
||||
> 보고서 §III.3 "8대 번역투 유형의 통합" 매핑. 본진 SSOT 패턴 ID는 v2.0 신규 4건(A-16~19) + 보강 4건(A-15·A-7·F-4·E-2)에 부착 예정.
|
||||
|
||||
### 1. 무생물 주어 + 타동사 구문
|
||||
|
||||
> 보고서 §III.3.1 (line 92-127). 본진 매핑 — A-15(추상 주어 + 만능 동사), D-5(의인화된 추상 주어), 보강 (gap §3.1).
|
||||
|
||||
- **이영옥 (2001)**. 무생물 주어 타동사구문의 영한번역. 번역학연구 2(1): 53-76.
|
||||
- 한국 번역학계 효시 격 논문 (보고서 II.2.3 line 68).
|
||||
- 한국어 행위자 의미역의 [+animate] 자질 강조: "행위자(agent) 의미역이 [+animate] 자질을 강하게 요구하고, '주어 + 목적어 + 타동사' 구조에서 주어가 의미적으로 통제력(control)을 갖는다는 함의가 강하다."
|
||||
- **김정우 (2007)**. 번역학연구 8(1): 61-82. 8유형 정초.
|
||||
- **박옥수 (2017)**. 동아인문학 41: 155-183 — 한영 NMT ST 유형적 특징·번역 오류. 영한 방향 동일 메커니즘 작동 보고.
|
||||
|
||||
### 2. 피동 표현 과다 (~되어지다, ~에 의해, 이중 피동)
|
||||
|
||||
> 보고서 §III.3.2 (line 128-162). 본진 매핑 — A-8(이중 피동), A-9(~에 의해 피동문), A-12(만들어지다·이루어지다). 매핑 강도 full.
|
||||
|
||||
- **이근희 (2005)**. 박사학위논문 / 단행본 『이근희의 번역 산책—번역투에서 번역의 전략까지』, 한국문화사 / 동화와 번역 "말뭉치를 활용한 by의 번역투 연구".
|
||||
- 영한 번역문과 한국어 비번역문 비교 말뭉치. by 코퍼스·번역투 정의·"-ese" 폄하 함의 지적.
|
||||
- **김정우 (1996)**.
|
||||
- **오경순 (2010)**. 일본근대학연구. 일한 번역의 수동표현 번역투.
|
||||
- **김은일 (2015)**. 현대문법학회 83: 61-79.
|
||||
- **서보현·김순영 (2018)**. 번역학연구 19(1): 99-117, doi:10.15749/jts.2018.19.1.004 — 영-한 NMT 출력 4범주 오류 분류 ("Incorrect meaning error occurs rather frequently while omission error is found relatively few; Wrong word/phrase order error comes with the incomplete sentence error"). NMT는 통사적 이질감을 일으키는 주된 표지로 'by + 행위자 → ~에 의해' 직역이 빈출.
|
||||
|
||||
> 보고서 verbatim 이중 피동 처방: "'잊혀지다 → 잊히다', '보여지다 → 보이다', '쓰여지다 → 쓰이다', '~되어지다 → ~되다'. '~된다'는 그 자체로 피동의 의미를 담고 있어 '~어지다'를 덧붙이는 것은 잉여적이다."
|
||||
|
||||
### 3. 대명사 직역 (he/she/it/they → 그/그녀/그것/그들)
|
||||
|
||||
> 보고서 §III.3.3 (line 163-191). 본진 매핑 — 신규 A-16 (gap §2 후보 1순위, none).
|
||||
|
||||
- **김도훈 (2009)**. 통역과 번역 11(2): 3-19. "영한 번역시 발생하는 번역투에 대한 고찰 — 대명사·복수 표지·무생물 주어 3대 핵심 유형".
|
||||
- **Cho, Won Ik · Kim, Ji Won · Kim, Seok Min · Kim, Nam Soo (2019)**. "On Measuring Gender Bias in Translation of Gender-neutral Pronouns", ACL Workshop on Gender Bias for NLP (GeBNLP), arXiv:1905.11684.
|
||||
- 한국어 무표지 "걔는 [xx]-해" 템플릿으로 MT 시스템의 젠더 편향 측정 체계 제안. 번역 출력이 'She is [xx]', 'He is [xx]', 'The person is [xx]' 중 하나로 나뉨.
|
||||
|
||||
> 보고서 verbatim: "한국어는 (i) 영형(zero) 대명사를 통한 생략, (ii) 반복적 명사구의 재사용, (iii) 친족·지위 호칭으로 동일 기능을 수행한다. 한국어 '그/그녀'는 본래 19~20세기 번역 문학을 통해 도입된 인공 어휘에 가깝다."
|
||||
> NMT/LLM 재현: "한국어 출력문은 대명사 밀도가 비번역 한국어의 2~3배에 달하는 경우가 흔하다."
|
||||
|
||||
### 4. '-들' 복수 표지의 기계적 부착
|
||||
|
||||
> 보고서 §III.3.4 (line 193-225). 본진 매핑 — 신규 A-17 (gap §2 후보 1순위, none).
|
||||
|
||||
- **곽은주·진실로 (2011)**. 번역학연구. 텍스트 차원에서의 복수표현의 영한번역전략.
|
||||
- **조의연 (2012)**. 번역학연구. 사람명사 복수표현의 영한번역전략에 대한 비판적 소고.
|
||||
- **조의연 (2015)**. 번역학연구 16(1). 목표언어 중심 등가적 번역전략 비판 — "번역문(translated text)" vs "목표텍스트(target text)" 구분.
|
||||
- **김정우 (2013)**. 번역학연구.
|
||||
- **김순영 (2012)**. 새국어생활 22(1). "-들"의 무차별 부착 의미 왜곡.
|
||||
- **김정우 (1996)**.
|
||||
- **강범모 (2007)**. 언어학 47.
|
||||
- **전영철 (2007)**. 언어학 49.
|
||||
|
||||
> 보고서 verbatim 의미론: "'-들'이 단순 복수가 아니라 (a) 분포성(distributivity), (b) 사건성, (c) 한정성·개체성을 부각하는 기능을 한다."
|
||||
> NMT/LLM 재현: "DeepL은 다른 NMT보다 이 점에서 다소 우월하지만 여전히 30~50% 정도는 잉여적 '-들'을 생성한다."
|
||||
|
||||
### 5. 관계대명사절 직역 (긴 좌향 수식)
|
||||
|
||||
> 보고서 §III.3.5 (line 227-249). 본진 매핑 — 신규 A-18 (gap §2 후보 2순위, none). E-5(쉼표 분절 평균 길이)는 측정 차원 다름.
|
||||
|
||||
- **박옥수 (2018)**. 동아인문학 44: 151-171. 영한 방향 NMT 통사 처리 실패 (관계절).
|
||||
- **김채은 (2021)**. 21세기영어영문학회 34: 279-305. 한영 기계번역 관계절 연구.
|
||||
- **김성완·이효정 (2017)**. 미래영어영문학회 22: 123-147.
|
||||
|
||||
> 보고서 verbatim: "영어는 관계대명사절을 명사 뒤에 후치(right-branching)하지만, 한국어는 관형절을 명사 앞에 전치(left-branching)한다. … 핵 어휘에 도달하기 전에 독자가 길고 복잡한 관형구를 처리해야 하므로 작업기억 부담이 커진다."
|
||||
|
||||
### 6. 명사화 표현 및 'have/make' 류 직역
|
||||
|
||||
> 보고서 §III.3.6 (line 251-272). 본진 매핑 — A-7(가지고 있다), F-4(한자어 명사화 접미사 -성·-적·-화), 보강 (gap §3.2·§3.3).
|
||||
|
||||
- **김정우 (2007)**. 번역학연구 8(1): 61-82. 무생물 주어·have 직역·전치사구 직역.
|
||||
- "사랑하는 처자를 가진 가장은 부지런할 수밖에 없다" — 'have'의 흔적이 그대로 남은 대표 사례.
|
||||
- **이근희 (2005)**.
|
||||
|
||||
> 보고서 verbatim: "영어는 'have/make/take/give'와 명사를 결합한 가벼운 동사 구문(light verb construction)을 매우 많이 사용한다. … 한국어는 동사적 표현이 더 자연스러운데, 직역하면 '회의를 가지다, 결정을 만들다, 한번 봄을 가지다'가 되어 어색하다."
|
||||
> 영어 명사화 접미사 처방: "명사화('-tion, -ment, -ness, -ity')가 누적된 영어 명사구는 한국어에서 동사·형용사로 풀어낸다: 'the implementation of the policy' → '정책 시행' 또는 '정책을 시행하기'"
|
||||
> NMT/LLM 재현: Pega Devlog 2023 — "GPT는 어색한 번역투 문장이 자주 보입니다(ex. 에너지 공급을 가진다)".
|
||||
|
||||
### 7. 일본어·영어식 조사 결합 (-에서의, -에로의, -으로의, -에의)
|
||||
|
||||
> 보고서 §III.3.7 (line 273-294). 본진 매핑 — 신규 A-19 (gap §2 후보 2순위, none). 단순 '~의'는 caveat #5에 따라 탐지 대상 명시적 제외.
|
||||
|
||||
- **김정우 (2007)**. 번역학연구 8(1): 61-82.
|
||||
- **김순영 (2012)**. 새국어생활 22(1). 전치사구 직역 자연화.
|
||||
- **김정우 (1996)**.
|
||||
|
||||
> 보고서 verbatim: "근대 한국어는 일본어 'の(の/への/での)'의 영향과 영어 전치사구('of, in, to, from')의 영향을 동시에 받으면서 격조사를 이중·삼중으로 결합한 표현이 늘었다. … 본래 한국어는 이런 표현을 절·구로 풀어 쓰는 것이 자연스럽다."
|
||||
> "'관형격 조사 의' 자체는 일본어 번역투가 아니지만(중부일보 팩트체크 2020 기사 참고), 연속된 '의 의 의'는 거의 항상 부적절하다."
|
||||
|
||||
### 8. 종결어미·시제·서법 처리
|
||||
|
||||
> 보고서 §III.3.8 (line 295-322). 본진 매핑 — E-2(동일 종결어미 반복), G-1(추측·관측형 종결), I-1(것이다 종결), 보강 (gap §3.4·§3.5). 청자 경어법은 본진 미커버 단독 영역.
|
||||
|
||||
- **김혜영 (2019)**. 통번역교육연구 17(2): 133-162, doi:10.23903/kaited.2019.17.2.007 (KCI ART002506702).
|
||||
- 종결어미 의미론·화용론·화행·양태·공손성·언표내적행위·번역 글쓰기.
|
||||
|
||||
> 보고서 verbatim: "한국어는 교착어로서 종결어미가 (a) 문장종결법(평서·의문·명령·청유·감탄), (b) 화행, (c) 양태(modality), (d) 청자에 대한 공손성, (e) 화자–청자 관계까지 표시한다."
|
||||
> 시제·서법: "영어 진행형(be -ing)을 한국어 '~고 있다'로 자동 매핑하면 잉여적이다. 한국어에서 '~고 있다'는 (i) 진행, (ii) 결과 상태 두 의미가 있고, 단순 시제로도 진행 의미가 표현된다('지금 책을 읽는다 / 책을 읽고 있다' 모두 가능)."
|
||||
|
||||
---
|
||||
|
||||
## 국제 번역학 이론적 토대
|
||||
|
||||
### Baker 1993 — 번역 보편소 4축
|
||||
|
||||
**Mona Baker (1993)**. "Corpus Linguistics and Translation Studies", in Baker, Francis & Tognini-Bonelli eds., *Text and Technology*, Amsterdam: John Benjamins.
|
||||
|
||||
- 번역 보편소 4축 (보고서 II.2.2 line 53-58): **simplification·explicitation·normalisation·levelling-out (1996)**.
|
||||
- 정의 (보고서 verbatim):
|
||||
- **simplification** — "번역문은 원문보다 어휘적·통사적으로 단순한 경향이 있다."
|
||||
- **normalisation/conventionalisation** — "번역문은 목표언어의 전형적·관습적 형태를 과도하게 따르는 경향이 있다."
|
||||
- v2.0 메트릭 트랙 적용 (gap §5): TTR·종결어미 entropy·declarative_da_ratio·end_form_concentration.
|
||||
|
||||
### Toury 1995 — 두 법칙
|
||||
|
||||
**Gideon Toury (1995)**. *Descriptive Translation Studies and Beyond*, Amsterdam: John Benjamins.
|
||||
|
||||
- 두 법칙: (a) 증가하는 표준화의 법칙(growing standardisation), (b) 원천 텍스트 간섭의 법칙(law of interference).
|
||||
- 보고서 핵심 진술 (Key Findings 2 line 11): "한국어 번역투의 90% 이상이 간섭 법칙으로 환원 가능."
|
||||
- **Pym, Anthony (2008)**. "On Toury's laws of how translators translate" — Baker 보편소가 Toury 표준화 법칙에 치우쳐 있고 간섭 법칙을 등한시했다고 비판. Pym은 한국어 번역투처럼 '간섭'으로 환원되는 현상은 Toury의 두 번째 법칙으로 설명되어야 한다고 주장.
|
||||
|
||||
### Laviosa 2002 — 코퍼스 번역학 보편소 확장
|
||||
|
||||
**Laviosa (2002)**. 보고서 II.1.2(line 35) 외국 이론 인용으로 명기. 번역 보편소 코퍼스 기반 확장.
|
||||
|
||||
### Chesterman 2004 — S/T-universals 구분
|
||||
|
||||
**Chesterman (2004)**. 보고서 II.1.2(line 35) 외국 이론 인용으로 명기. 번역 보편소 — **S-universals(원천 → 목표) vs T-universals(목표언어 내) 구분**.
|
||||
|
||||
### Toral 2019 — post-editese (악화된 translationese)
|
||||
|
||||
**Antonio Toral (2019)**. "Post-editese: an Exacerbated Translationese", MT Summit XVII Dublin, pp. 273-281. arXiv:1907.00900.
|
||||
|
||||
- 보고서 verbatim 결론 (post_editese_axes.post_editese_definition_verbatim): "PE는 HT보다 (i) 어휘 다양성·밀도가 낮아 더 단순(simpler)하고, (ii) 목표언어 관습으로 더 정규화(normalised)되어 있으며, (iii) 원천언어로부터의 간섭이 더 강(higher interference)했다. 즉 'post-editese'는 'translationese의 악화된 형태(exacerbated translationese)'였다."
|
||||
- 검증 데이터셋: 5개 언어쌍 3개 데이터셋 (en→de, de→en, es→de Taraxa뉴스 / en→de en→fr IWSLT자막 / zh→en MS뉴스). **한국어는 미포함** — caveat C3 적용.
|
||||
- 한국적 함의 (post_editese_axes.korean_implication): "후편집이 단순 교정(post-editing)이 아니라 재구성(re-writing) 수준으로 수행되어야 함을 의미한다."
|
||||
- v2.0 메트릭 트랙 적용 (gap §5): post_editese_score 3축 가중 합. caveat C3에 따라 모든 metric에 `speculative: true` 플래그 권고.
|
||||
|
||||
### Sarti·Bisazza·Guerberof-Arenas·Toral 2022 — DivEMT
|
||||
|
||||
**Sarti, Bisazza, Guerberof-Arenas, Toral (2022)**. EMNLP pp. 7795-7816 (DivEMT).
|
||||
|
||||
- 18명 전문 번역가 영-아·네·이·터·우·베 6개 언어 PE 실험.
|
||||
- verbatim: "magnitude of productivity gains varies widely across systems and languages, highlighting major disparities in post-editing effectiveness for languages at different degrees of typological relatedness".
|
||||
|
||||
### Cho et al. 2019 — 한국어 MT 젠더 편향
|
||||
|
||||
**Cho, Won Ik · Kim, Ji Won · Kim, Seok Min · Kim, Nam Soo (2019)**. "On Measuring Gender Bias in Translation of Gender-neutral Pronouns", ACL Workshop on Gender Bias for NLP (GeBNLP), arXiv:1905.11684. (위 §3 대명사 직역 참조.)
|
||||
|
||||
### Frawley 1984 — third code
|
||||
|
||||
**Frawley (1984)**. 보고서 II.2.1 line 49 인용. 번역어를 원천언어와도 목표언어와도 다른 "제3의 부호(third code)"로 개념화.
|
||||
|
||||
### Hayase et al. 2024 — GPT-4o 한국어 학습 비중
|
||||
|
||||
**Hayase et al. (2024)**. "Data Mixture Inference: What do BPE Tokenizers Reveal about their Training Data?", arXiv:2407.16607.
|
||||
|
||||
- GPT-4o 비영어 학습 데이터 비중 39% (GPT-3.5 3% 대비 13배), 한국어 비중 1% 미만 추정. 보고서 IV.4.4 line 359, VI Caveat 6 line 539.
|
||||
|
||||
---
|
||||
|
||||
## NMT/LLM 시대 한국 PE 가이드라인 계보
|
||||
|
||||
> 보고서 §V.5 "한국어 PE 교육·연구 계보" 매핑.
|
||||
|
||||
- **윤미선·김택민·임진주·홍승연 (2018)**. 번역학연구 19(5): 43-76. 영-한 PE 가이드라인 — 한국어 PE 교육의 토대.
|
||||
- **김혜림 (2022)**. 중국언어연구 99: 277-312. 중-한 PE 가이드라인.
|
||||
- **이상빈 (2017)**. 통역과 번역 19(3): 37-64, doi:10.20305/it201703037064.
|
||||
- PE는 단순 번역기 결과 수정이 아니라 (a)메시지·(b)논리·(c)연어·(d)문법·(e)레이아웃 등 11개 항목 종합. 학부생 단어 차원 수정 한계.
|
||||
- **이상빈 (2018a)**. 통번역학연구 22(1): 117-143, doi:10.22844/its.2018.22.1.117.
|
||||
- 학부생 PE 경험 5요소 — (1) PE는 어렵다 (2) 교정교열 교육 필요 (3) MT 품질 나쁘지 않음 (4) 프리에디팅 필요 (5) PE 역량=기본 번역역량.
|
||||
- **이상빈 (2018b)**. 번역학연구 19(3): 259-286, doi:10.15749/jts.2018.19.3.010.
|
||||
- 사고발화(TAP) + 화면녹화 PE 행위 분석 — 사전 과의존·단어구 단위 수정·over-revision 위험.
|
||||
- **마승혜 (2018)**. 통번역학연구 22(1): 53-88, doi:10.22844/its.2018.22.1.53. 텍스트 유형별 PE 문제 — 정보적·표현적·설득적 텍스트 차이.
|
||||
- **이주리애 (2018)**. 통역과 번역 20(1): 43-71, doi:10.20305/it201801043071. 한일/일한 NMT 어휘·구·통사·텍스트 4층위 분석.
|
||||
|
||||
---
|
||||
|
||||
## 15항목 PE 체크리스트 학술 anchoring (보고서 §5.1)
|
||||
|
||||
> 보고서 §V.5.1 (line 388-406) 15항목을 본진 분류 ID + 8유형 anchor에 매핑. 처방 적용은 `playbook_patch.md` 참조 (분류 vs 처방 분리 원칙).
|
||||
|
||||
| PE# | 라벨 | 트리거 질문 | 처치 | type_anchor | 본진 매핑 |
|
||||
|---|---|---|---|---|---|
|
||||
| PE1 | 무생물 주어 | 주어가 무생물·추상명사인데 '하다/만들다/시키다' 류 타동사 결합? | 부사절·원인절 또는 인간 주어로 전환 | T1 | A-15·D-5 (보강) |
|
||||
| PE2 | by-수동태 | '~에 의해' 또는 '~으로 인해'? | 능동태 또는 자동사 / '에' 또는 '에게' 단순화 | T2 | A-9 |
|
||||
| PE3 | 이중 피동 | '~되어지다, ~여지다, 잊혀지다, 보여지다' | 단순 피동 환원 | T2 | A-8 |
|
||||
| PE4 | 대명사 | '그/그녀/그것/그들' 한 단락 ≥3회 | 50% 이상 영형(생략), 일부 호칭·명사구 | T3 | 신규 A-16 |
|
||||
| PE5 | 복수 표지 '-들' | 무정물·추상명사에 '-들' 부착? | 거의 모두 삭제. 분포성 강조 시만 유지 | T4 | 신규 A-17 |
|
||||
| PE6 | 관계절 | 명사 앞 ≥3어절 관형구? | 문장 분리 또는 후치 동격절 | T5 | 신규 A-18 |
|
||||
| PE7 | have/make | '~을 가지다 / ~을 만들다 / ~을 가지고 있다' | 동사 환원 또는 이중주어 구문 | T6 | A-7 (보강) |
|
||||
| PE8 | 조사 결합 | '-에서의, -에로의, -으로의, -에의' | 절·구로 풀어쓰기 | T7 | 신규 A-19 |
|
||||
| PE9 | 종결어미 | '~다' ≥4문장 연속 | 다양화 ('~었다·~ㄴ다·~는다·~기 마련이다·~ㄹ 것이다·~을 수 있다') | T8 | E-2 (보강) |
|
||||
| PE10 | 진행형 | '~고 있다' 남발 | 단순 시제로 환원 가능성 검토 | T8 | E-2 (보강) |
|
||||
| PE11 | 명사화 | '-tion, -ment, -ness'의 한국어 명사 직역 | 동사·형용사로 풀기 | T6 | F-4 (보강) |
|
||||
| PE12 | 전치사구 | '~로부터, ~에 관하여, ~을 통하여' | 문맥 자연 표현 | T7 | A-2·A-5 인접 (단일 어휘) |
|
||||
| PE13 | 시제·서법 | 영어 단순 현재·과거 단조 매핑 | 한국어 서사 시제·서법 다양화 | T8 | E-2 (보강) |
|
||||
| PE14 | 청자 경어법 | 대화체에서 화자–청자 관계 점검 | 해라/하게/하오/해요/합쇼체 일관 적용 | T8 | 본진 미커버 — taxonomist 결정 |
|
||||
| PE15 | 호칭 | 'Mr./Ms./Dr.' 직역 | 한국어 호칭(선생님·박사님·과장님) 또는 생략 | T3 | 신규 A-16 인접 |
|
||||
|
||||
> 보고서 §5.1 verbatim 출처: 윤미선·김택민·임진주·홍승연 2018(line 388, 425), 김혜림 2022(line 425), 이상빈 2017·2018a·2018b(line 469-473), 마승혜 2018(line 332).
|
||||
|
||||
---
|
||||
|
||||
## post-editese 3축 (보고서 §IV.4.3)
|
||||
|
||||
> 본진 직접 채택은 caveat C3에 따라 hold (gap §4.1). v2.0 별도 메트릭 트랙(metric-engineer)에서 정량 지표로 운영.
|
||||
|
||||
### simplification 축
|
||||
- 보고서 정의 verbatim: "PE는 어휘 다양성·밀도가 인간 번역보다 낮다."
|
||||
- ko_manifestation: 한국어 영-한 후편집에서 종결어미 단조성 / 어휘 반복 / 사전적 1차 의미 선호 경향.
|
||||
- 보고서 line 55, 351.
|
||||
|
||||
### normalisation 축
|
||||
- 보고서 정의 verbatim: "PE는 목표언어의 가장 흔한 형태를 과도하게 따르는 경향이 있다."
|
||||
- ko_manifestation: 한국어 '~한다 / ~된다 / ~이다' 평서형 정형구로 수렴.
|
||||
- 보고서 line 57, 352.
|
||||
|
||||
### interference 축
|
||||
- 보고서 정의 verbatim: "PE는 원천언어의 통사 구조를 더 강하게 보존한다." (Toury 1995, law of interference)
|
||||
- ko_manifestation: 영어식 SVO / 무생물 주어 / 관계절 좌향 수식 / by-수동태 유지.
|
||||
- 보고서 line 60, 353.
|
||||
|
||||
### 통합 결론 (Toral 2019 verbatim)
|
||||
"PE는 HT보다 (i) 어휘 다양성·밀도가 낮아 더 단순(simpler)하고, (ii) 목표언어 관습으로 더 정규화(normalised)되어 있으며, (iii) 원천언어로부터의 간섭이 더 강(higher interference)했다. 즉 'post-editese'는 'translationese의 악화된 형태(exacerbated translationese)'였다."
|
||||
|
||||
---
|
||||
|
||||
## Caveats (이 SSOT의 한계, 보고서 §VI verbatim 6건)
|
||||
|
||||
> 분류학자·메트릭 엔지니어·리뷰어 모두 신뢰도 평가 시 본 절을 참조한다. 본진 v2.0 발행 시 'valid as of 2026-05' 명기.
|
||||
|
||||
### C1. 김혜영(2019) 본문 정량 수치 미확인
|
||||
> "본 보고서는 KCI(ART002506702) 영문 초록과 키워드(종결어미·서법·양태·화행·언표내적행위·번역 글쓰기)를 근거로 김혜영(2019)의 핵심 논지를 정리했다. 평서형 '-다'의 정확한 출현 빈도(%) 등 본문 표·수치는 통번역교육연구 17(2) PDF를 직접 확보해야 검증 가능하다." (보고서 line 529)
|
||||
|
||||
**분류학자 함의**: T8 종결어미 재현율 임계치를 보고서 정량 수치로 못 박을 수 없음. 김혜영 PDF 원문 확보 전까지 'estimated' 플래그 유지.
|
||||
|
||||
### C2. NMT/LLM 비교 평가의 마케팅 편향
|
||||
> "DeepL 공식 블로그(2024)의 비교는 자사 블라인드 테스트 결과로, 독립적 검증이 필요하다. Lionbridge(2023)의 LLM-NMT 비교 평가는 영-중·영-스·영-독 언어쌍에 한정되어 영-한에 직접 적용할 수 없다." (보고서 line 531)
|
||||
|
||||
**분류학자 함의**: DeepL 우월·GPT 열위 식의 모델별 정량 비교를 분류 체계 가중치로 직접 흡수 금지. 모델 일반성 검증은 별도 회차 필요(예: humanize-ko v1.3.1 Gemini 회차).
|
||||
|
||||
### C3. 'post-editese'의 한국어 직접 검증 부재
|
||||
> "Toral(2019)은 en→de, de→en, es→de, en→fr, zh→en의 5개 언어쌍을 다뤘고, 한국어는 포함되지 않았다. 한국어에 대한 동일 결론은 합리적 추론이지만 정량적 검증은 미수행 상태다." (보고서 line 533)
|
||||
|
||||
**분류학자 함의**: post-editese 3축(simplification·normalisation·interference)을 v2.0 분류 체계에 직접 채택할 때, 한국어 정량 검증 부재를 'speculative: true' 플래그로 명기.
|
||||
|
||||
### C4. 단일 NMT 실증연구의 8유형 통합 부재
|
||||
> "8대 번역투 유형 모두를 단일 NMT 실증 연구로 다룬 KCI 등재 논문은 확인되지 않는다. 본 보고서는 박옥수(2017, 2018), 서보현·김순영(2018), 이주리애(2018), 김채은(2021), 이지은·최효은(2022), 김경숙(2018), 이정화·차경환(2022) 등을 조합하여 추론한 것이다. 이는 명확한 연구 공백이다." (보고서 line 535)
|
||||
|
||||
**분류학자 함의**: 8유형 NMT/LLM 재현율 통합 표는 보고서가 제공하지 않음. 분류학자는 8유형 각각의 NMT/LLM 재현 진술을 별도 연구로 분리 추적해야 함.
|
||||
|
||||
### C5. 일본어 번역투의 영향 범위에 대한 논쟁
|
||||
> "'~의' 자체가 일본어 번역투인지에 대해서는 학계 합의가 없다. 국립국어원과 김슬옹 세종국어문화원장은 '~의'가 15세기부터 한국어에 존재했다고 본다. 본 보고서는 '단순 ~의'는 번역투가 아니나 '~에서의/~에로의' 같은 이중 결합은 번역투로 본다는 다수설을 따른다." (보고서 line 537)
|
||||
|
||||
**분류학자 함의**: T7 패턴(A-19) 정의에서 '단순 ~의'는 탐지 대상에서 명시적으로 제외. '~에서의/~에로의/~으로의/~에의' 이중 결합만 S2 이상.
|
||||
|
||||
### C6. LLM의 빠른 진화
|
||||
> "2026년 5월 시점의 LLM 번역 품질 평가는 6개월 내에 노후화될 수 있다. 본 보고서의 LLM 비교 부분은 2024~2025년 연구·블로그·업계 보고에 기반하며, 신규 모델(GPT-5, Claude 5 등) 출시 시 재검증이 필요하다. GPT-4o의 비영어 학습 데이터 비중이 39%(GPT-3.5의 3% 대비 13배)로 급증한 점(Hayase et al. 2024)은 향후 한국어 출력 품질 개선 가능성을 시사하지만, 한국어 비중 자체는 여전히 1% 미만으로 추정된다." (보고서 line 539)
|
||||
|
||||
**분류학자 함의**: 분류 체계 v2.0 발행 시 'valid as of 2026-05' 명기. 6개월 주기로 모델별 재현율 회차 설정.
|
||||
|
||||
---
|
||||
|
||||
## 자체 검증
|
||||
|
||||
- 보고서 §VI Caveat 6건 모두 본 파일 §Caveats 절에 verbatim 보존 — **통과**.
|
||||
- 8유형 모두 한국 번역학계 학자 anchor ≥ 1명 부착:
|
||||
- T1 이영옥 2001·김정우 2007·박옥수 2017
|
||||
- T2 이근희 2005·김정우 1996·오경순 2010·김은일 2015·서보현·김순영 2018
|
||||
- T3 김도훈 2009 (+ Cho et al. 2019 ACL)
|
||||
- T4 곽은주·진실로 2011·조의연 2012·2015·김정우 2013·김순영 2012·김정우 1996·강범모 2007·전영철 2007
|
||||
- T5 박옥수 2018·김채은 2021·김성완·이효정 2017
|
||||
- T6 김정우 2007·이근희 2005
|
||||
- T7 김정우 2007·김순영 2012·김정우 1996
|
||||
- T8 김혜영 2019
|
||||
- 8/8 — **통과**.
|
||||
- 국제 4대 이론(Baker 1993·Toury 1995·Laviosa 2002·Toral 2019) 모두 별도 섹션 보유 — **통과**. (+ Chesterman 2004·Sarti 2022·Cho 2019·Frawley 1984·Hayase 2024 추가 섹션.)
|
||||
- NMT/LLM 시대 PE 가이드라인 계보 7명 (윤미선 외 2018·김혜림 2022·이상빈 2017·2018a·2018b·마승혜 2018·이주리애 2018) — **통과**.
|
||||
- 15항목 PE 체크리스트 학술 anchoring 표 (PE1~PE15) 본진 매핑 + type_anchor 부착 — **통과**.
|
||||
|
||||
판정 어조 — 학술 정통성 큐레이터. 보고서 verbatim 외 자체 추가·확장 없음. 본진 분류 체계 본문 직접 수정 권한 없음.
|
||||
32
.claude/skills/humanize-redo/SKILL.md
Normal file
32
.claude/skills/humanize-redo/SKILL.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
name: humanize-redo
|
||||
description: 가장 최근 윤문 결과를 2차로 다시 다듬는다 — 특정 카테고리·문단·강도 조정도 가능. humanize-korean strict 윤문(Phase B)을 기존 run_id에 재실행해 잔존 finding을 처리한다. 트리거 — "/humanize-redo".
|
||||
argument-hint: "[조정 지시 — 예: \"번역투만 다시\" \"이 문단만\" \"강도 낮춰\"]"
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# /humanize-redo — 2차 윤문 / 부분 재실행
|
||||
|
||||
cwd 기준 가장 최근 `_workspace/{run_id}/`를 찾아 `humanize-korean` 스킬의 strict 윤문(Phase B)부터 재호출한다.
|
||||
|
||||
## 사용자 지시
|
||||
$ARGUMENTS
|
||||
|
||||
## 동작
|
||||
1. `Glob`으로 `_workspace/YYYY-MM-DD-*/final.md`(또는 `01_input.txt`)를 매칭해 최신 `run_id` 식별. 없으면 "이전 실행이 없습니다. `/humanize`로 시작하세요" 안내 후 종료.
|
||||
2. 사용자 지시 파싱:
|
||||
- **카테고리 지정**("번역투만", "관용구만", "이모지만") → 해당 카테고리 finding만 재윤문
|
||||
- **문단 지정**("이 문단만", "두 번째 문단만") → 해당 범위 finding만
|
||||
- **강도 조정**("강도 낮춰"·"보수적으로" → S1만, "강도 높여" → S1+S2+S3)
|
||||
- **롤백 요청**("이 변경 되돌려줘") → 해당 edit을 `content-fidelity-auditor` 롤백으로 처리
|
||||
- 지시 없음·"2차 윤문해줘" → 잔존 finding 전체 대상 round 2
|
||||
3. `korean-style-rewriter` 재호출 입력: 기존 `02_detection.json` 또는 `05_naturalness_review.json`의 잔존 finding + 사용자 지시를 `target_filter`로 전달.
|
||||
4. 산출물은 `03_rewrite_v2.md`(또는 v3)로 버전 분리. 이전 `final.md`는 `final_prev.md`로 백업.
|
||||
5. strict Phase C 병렬 검증 → Phase D 최종 출력(변경 비교 표 + 신규 등급).
|
||||
|
||||
## 루프 한도
|
||||
최대 round 3. 그 이상 미해결이면 `hold_and_report`로 사람 검토 권고.
|
||||
|
||||
## 참고
|
||||
- 풀 파이프라인 신규 실행은 `/humanize`.
|
||||
- 분류 체계: `humanize-korean/references/ai-tell-taxonomy.md`
|
||||
35
.claude/skills/humanize/SKILL.md
Normal file
35
.claude/skills/humanize/SKILL.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
name: humanize
|
||||
description: AI가 쓴 한글 텍스트를 자연스럽게 윤문하는 진입 명령. humanize-korean 파이프라인을 Fast 모드(기본)로 실행하고 `--strict`면 5인 파이프라인. 트리거 — "/humanize".
|
||||
argument-hint: "[윤문할 텍스트 또는 파일 경로]"
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# /humanize — 한글 AI 티 제거
|
||||
|
||||
`humanize-korean` 스킬을 발동해 인자로 전달된 한글 텍스트(또는 파일)에 윤문을 실행한다.
|
||||
|
||||
## 입력
|
||||
$ARGUMENTS
|
||||
|
||||
## 동작
|
||||
1. 인자가 비면: "윤문할 텍스트를 붙여넣어 주세요" 안내 후 종료.
|
||||
2. 인자가 파일 경로(.txt/.md)면 `Read`로 본문 로드.
|
||||
3. 인자가 텍스트면 그대로 입력으로 사용.
|
||||
4. `humanize-korean` 스킬 SKILL.md 절차(Phase 0 → 결과 전달)를 따른다 — 기본 **Fast 모드**, `--strict` 시 strict 5인 파이프라인.
|
||||
5. 결과 전달:
|
||||
- 한 줄 상태(변경률 / 등급 / 자체검증 통과)
|
||||
- 윤문본 본문(마크다운 블록)
|
||||
- 카테고리별 탐지 건수 before/after
|
||||
- 주요 변경 하이라이트 3~5건
|
||||
- 등급 B 이하면 "`/humanize-redo`로 2차 윤문 가능" 안내
|
||||
|
||||
## 옵션 (인자 끝에 자연어로)
|
||||
- `장르: 칼럼|리포트|블로그|공적` — 장르 명시 (생략 시 첫 300자로 자동 추정)
|
||||
- `강도: 보수|기본|적극` — 윤문 강도 (기본값: 기본)
|
||||
- `최소심각도: S1|S2|S3` — 탐지 임계값 (기본값: S2)
|
||||
- `--strict` — 5인 파이프라인 강제
|
||||
|
||||
## 참고
|
||||
- 분류 체계: `humanize-korean/references/ai-tell-taxonomy.md`
|
||||
- 윤문 처방: `humanize-korean/references/rewriting-playbook.md`
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -18,3 +18,7 @@ _workspace_prev/
|
|||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
|
|
|||
60
CLAUDE.md
60
CLAUDE.md
|
|
@ -14,32 +14,30 @@ AI(ChatGPT·Claude·Gemini 등)가 쓴 한글 텍스트를 "사람이 쓴 글처
|
|||
## 디렉토리 구조
|
||||
|
||||
```
|
||||
humanize-ko/
|
||||
im-not-ai/
|
||||
├── CLAUDE.md # 본 파일 — 프로젝트 가이드
|
||||
├── .claude/
|
||||
│ ├── agents/ # 6인 에이전트 정의
|
||||
│ │ ├── korean-ai-tell-taxonomist.md
|
||||
│ │ ├── ai-tell-detector.md
|
||||
│ │ ├── korean-style-rewriter.md
|
||||
│ │ ├── content-fidelity-auditor.md
|
||||
│ │ ├── naturalness-reviewer.md
|
||||
│ │ └── humanize-web-architect.md
|
||||
│ └── skills/humanize-korean/
|
||||
│ ├── SKILL.md # 오케스트레이터
|
||||
│ └── references/
|
||||
│ ├── ai-tell-taxonomy.md # SSOT — 10대분류 × 40+ 패턴
|
||||
│ ├── rewriting-playbook.md # 카테고리별 치환 레시피
|
||||
│ └── web-service-spec.md # Phase 5 웹 확장용
|
||||
└── _workspace/ # 런타임 산출물 (run_id별)
|
||||
└── {YYYY-MM-DD-NNN}/
|
||||
├── 01_input.txt
|
||||
├── 02_detection.json
|
||||
├── 03_rewrite.md
|
||||
├── 03_rewrite_diff.json
|
||||
├── 04_fidelity_audit.json
|
||||
├── 05_naturalness_review.json
|
||||
├── final.md
|
||||
└── summary.md
|
||||
├── README.md / INSTALL.md # 사용·설치 안내
|
||||
├── .claude-plugin/ # Claude 플러그인 + 마켓플레이스 매니페스트
|
||||
│ ├── plugin.json # skills: ./.claude/skills/ · 에이전트는 루트 agents/ 자동탐색
|
||||
│ └── marketplace.json # /plugin marketplace add epoko77-ai/im-not-ai
|
||||
├── gemini-extension.json # Gemini CLI Extension 매니페스트
|
||||
├── GEMINI.md # Gemini 에이전트 컨텍스트 (monolith 룰 인라인)
|
||||
├── commands/ # Gemini CLI 커스텀 명령 (/humanize-korean, /humanize, /humanize-redo)
|
||||
├── install.sh / uninstall.sh # Claude·Codex·Gemini 전역 설치/제거 (심링크 기본)
|
||||
├── agents/ # 서브에이전트 12종 (플러그인 컨벤션 — 루트 agents/에 둬야 로드됨)
|
||||
│ ├── humanize-monolith.md # Fast 단일 호출
|
||||
│ ├── ai-tell-detector.md · korean-style-rewriter.md
|
||||
│ ├── content-fidelity-auditor.md · naturalness-reviewer.md
|
||||
│ └── … taxonomist·scholar·distiller 등 지원 7종
|
||||
├── .claude/skills/ # 스킬 3종 (humanize-korean·humanize·humanize-redo)
|
||||
│ └── humanize-korean/
|
||||
│ ├── SKILL.md # 오케스트레이터 (quick_rules_path: ${CLAUDE_SKILL_DIR}/...)
|
||||
│ └── references/ # SSOT — ai-tell-taxonomy·rewriting-playbook·quick-rules 등
|
||||
├── codex/skills/humanize-korean/ # Codex Fast Path 스킬
|
||||
│ ├── SKILL.md # monolith 기반 자가완결
|
||||
│ └── references → ../../../.claude/skills/humanize-korean/references # SSOT 공유 심링크
|
||||
└── _workspace/ # 런타임 산출물 (run_id별, gitignored)
|
||||
└── {YYYY-MM-DD-NNN}/ # 01_input.txt … final.md · summary.md
|
||||
```
|
||||
|
||||
## 파이프라인
|
||||
|
|
@ -94,6 +92,18 @@ humanize-ko/
|
|||
2. 오케스트레이터가 run_id 생성하고 5단계 파이프라인 실행.
|
||||
3. 결과 `final.md` + `summary.md` 반환.
|
||||
|
||||
## 파일 시스템 접근 규칙
|
||||
|
||||
에이전트가 파일·디렉토리에 접근할 때는 전용 도구를 우선 사용한다.
|
||||
`Bash` 툴의 `ls`·`cat`·`echo`는 실행 환경(OS·경로 형식)에 따라 동작이 달라져 예측 불가한 오류를 일으킬 수 있다.
|
||||
|
||||
| 작업 | 올바른 방법 | 피할 방법 |
|
||||
|---|---|---|
|
||||
| 파일 존재 확인 | `Glob` 도구 | `Bash` 툴 `ls` |
|
||||
| 디렉토리 목록 열거 | `Glob` 도구로 안의 표지 파일 매칭 (예: `*/01_input.txt`) | `Bash` 툴 `ls` |
|
||||
| 파일 읽기 | `Read` 도구 | `Bash` 툴 `cat` / `head` |
|
||||
| 파일 쓰기·편집 | `Write` / `Edit` 도구 | `Bash` 툴 리다이렉션 |
|
||||
|
||||
## 주요 금기
|
||||
|
||||
- 수치·단위·날짜 변경 금지.
|
||||
|
|
|
|||
179
GEMINI.md
Normal file
179
GEMINI.md
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
# Humanize KR — AI 한글 티 제거 (Gemini CLI Extension)
|
||||
|
||||
**v1.5 · Fast(monolith) 모드 전용** — Gemini CLI에서 한 번의 대화로 탐지·윤문·자체검증을 일괄 처리합니다.
|
||||
정밀 strict 5인 파이프라인은 Claude Code 전용입니다.
|
||||
|
||||
## 개요
|
||||
|
||||
AI(ChatGPT·Claude·Gemini 등)가 쓴 한글 텍스트를 "사람이 쓴 글처럼" 윤문합니다.
|
||||
번역투·영어 인용 과다·기계적 병렬·관용구·피동태 남용·접속사 남발·리듬 균일성·이모지/불릿 과다 등
|
||||
**10대 카테고리 40+ AI 티 패턴**을 탐지·분류해 **내용은 한 글자도 건드리지 않고** 문체·리듬·표현만 재작성합니다.
|
||||
|
||||
## 커스텀 명령
|
||||
|
||||
- `/humanize-korean [텍스트]` — 메인 윤문 명령
|
||||
- `/humanize [텍스트]` — `/humanize-korean`과 동일
|
||||
- `/humanize-redo [조정 지시]` — 2차 윤문 / 부분 재실행
|
||||
|
||||
자연어 트리거도 동작합니다: "이 글 AI 티 없애줘", "AI 윤문", "ChatGPT 티 제거", "번역투 고쳐", "사람이 쓴 것처럼".
|
||||
|
||||
## 철칙 (위반 시 즉시 롤백)
|
||||
|
||||
1. **의미 불변 (Fidelity First)** — 사실·주장·수치·고유명사·인용은 100% 원문 보존.
|
||||
2. **근거 기반 (Span-Grounded)** — 아래 패턴 목록에 매핑되지 않는 구간은 건드리지 않음.
|
||||
3. **장르 유지 (Tone Match)** — 칼럼을 문학으로, 리포트를 에세이로 옮기지 않음.
|
||||
4. **register 보존** — 원문 격식체면 결과도 격식체. AI 티는 문법·수사이지 격식 자체가 아님.
|
||||
5. **과윤문 금지** — 변경률 30% 초과 시 경고, 50% 초과 시 강제 중단.
|
||||
|
||||
## Do-NOT (탐지·윤문 모두 제외)
|
||||
|
||||
고유명사·제품명·모델명·기관명, 수치·날짜·단위, 큰따옴표 안 직접 인용, 법률 조문,
|
||||
수학·화학·통계 표기, 영어 약어(LLM·GPU·MCP·API 등 업계 표준).
|
||||
|
||||
## 절차
|
||||
|
||||
1. **입력 확보**: 사용자가 붙여넣은 텍스트를 원문으로 한다. 파일 경로(.txt/.md)면 그 파일을 읽는다.
|
||||
2. **장르 추정**: 첫 300자로 장르 추정(사용자 명시 시 우선). 칼럼 | 리포트 | 블로그 | 공적.
|
||||
3. **탐지**: 아래 A~J 카테고리 패턴을 스캔해 (ID, span, severity, fix) 수집. Do-NOT span은 제외.
|
||||
4. **윤문**: D(관용구 삭제) → A → I → G → H → F → B → C·J → E 순서로 문단 단위 처리.
|
||||
5. **자체검증**: 아래 체크리스트 6항 점검. 위반 시 해당 edit 롤백 → 부분 재실행(최대 1회).
|
||||
6. **출력**: 윤문본 + 메트릭 요약 반환.
|
||||
|
||||
## 응답 형식
|
||||
|
||||
사용자에게 4가지 반환:
|
||||
1. 한 줄 상태: `완료. 변경률 X% / 등급 Y / 자체검증 N/6 통과`
|
||||
2. 윤문본 본문 (마크다운 블록)
|
||||
3. 카테고리별 탐지 건수 before/after + 주요 변경 하이라이트 3~5건
|
||||
4. 등급 B 이하면 "정밀 검증이 필요하면 Claude Code의 strict 5인 파이프라인 권장" 안내
|
||||
|
||||
## 옵션 (인자 끝에 자연어로)
|
||||
|
||||
- `장르: 칼럼|리포트|블로그|공적` — 장르 명시 (생략 시 자동 추정)
|
||||
- `강도: 보수|기본|적극` — 윤문 강도 (기본값: 기본)
|
||||
- `최소심각도: S1|S2|S3` — 탐지 임계값 (기본값: S2)
|
||||
|
||||
---
|
||||
|
||||
## AI 티 패턴 목록 (Quick Rules)
|
||||
|
||||
### A. 번역투 (Translation-ese)
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| A-1 | "~에 대해(서)" | S1 | 목적격 조사로 직결("X에 대해 논의" → "X를 논의") |
|
||||
| A-2 | "~를 통해/통하여" 남발 | S1 | "~로", "~해서", "~함으로써"로 분산 |
|
||||
| A-3 | "~에 있어(서)" | S1 | "~에서", "~을 볼 때" |
|
||||
| A-4 | "~라는 점에서" 3회+ | S2 | "~서", "~라는 이유로" |
|
||||
| A-5 | "~와 관련하여/관련된" | S2 | "~에", "~의" |
|
||||
| A-6 | "~에 기반하여/바탕으로" 남발 | S2 | "~로", "~을 보고" |
|
||||
| A-7 | "가지고 있다" / have·make·take·give + N 직역 | S1 | 형용사·동사 환원 |
|
||||
| A-8 | 이중 피동 "~되어진다" | S1 | 능동 또는 단일 피동 |
|
||||
| A-9 | "~에 의해" 피동 | S2 | 행위자를 주어로 |
|
||||
| A-10 | "~할 수 있다" 남발 | S2 | 단언으로 |
|
||||
| A-11 | "~을 위해" 목적절 남발 | S2 | "~려고", "~위한" |
|
||||
| A-15 | 추상 주어 + 만능 동사 | S2 | 구체 주어로 환원 |
|
||||
| A-16 | "그/그녀/그것/그들" 단락 ≥3회 | S1 | 영형(생략) 또는 호칭·명사구 |
|
||||
| A-18 | 명사 앞 ≥3어절 관형구 | S2 | 문장 분리 또는 후치 동격절 |
|
||||
| A-19 | 이중 조사 "~에서의/~에로의" | S2 | 절·구로 풀어쓰기 |
|
||||
|
||||
### B. 영어 인용·용어 과다
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| B-1 | 한글 + 괄호 영어 매번 | S2 | 첫 등장만 병기, 이후 한글만 |
|
||||
| B-2 | 영어 어휘 직역 가능한데 그대로 | S2 | 한국어로 옮기되 업계 표준은 유지 |
|
||||
|
||||
### C. 구조적 AI 패턴
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| C-5 | 이모지 남발 | S1 | 장르 칼럼·리포트면 전부 삭제 |
|
||||
| C-7 | "먼저·반면·결국" 3단 공식 | S2 | 접속사 1~2개로 줄이거나 제거 |
|
||||
| C-8 | "A인가·B인가" 대구 반복 | S2 | 한 번만 살리고 나머지 평서문 |
|
||||
| C-9 | 숫자 괄호 인덱싱 "(1)·(2)·(3)" | S2 | 본문에 녹이거나 단순 줄바꿈 |
|
||||
| C-10 | 콜론 부제 헤딩 "X: Y" 반복 | S1 | 헤딩 짧게 또는 평서 헤딩 |
|
||||
| C-11 | 연결어미 뒤 쉼표 | S1 | 쉼표 제거 |
|
||||
|
||||
### D. AI 특유 관용구 (Signature Phrases)
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| D-1 | 결산 피벗 lexicon "결론적으로/따라서/이를 통해" | S1 | 3회 초과 시 1~2건 치환, 나머지 삭제 |
|
||||
| D-2 | "시사하는 바가 크다/주목할 만하다" | S1 | 삭제 또는 구체 결론 |
|
||||
| D-3 | "본질적으로/핵심적으로" | S1 | 삭제 |
|
||||
| D-4 | hype 어휘(파격적·압도적·강력한·획기적) 3회+ | S1 | 구체 수치·사실로 환원 |
|
||||
| D-5 | 의인화 추상 주어 | S1 | 사람·기관 주어로 |
|
||||
| D-6 | 결말 공식 "~할 때다/~해야 한다/~지금이야말로" | S1 | 평서로 닫거나 삭제 |
|
||||
| D-7 | 변환 공식 "X에서 Y로" 반복 | S2 | 한 번만, 나머지 일반 서술 |
|
||||
|
||||
### E. 리듬·종결어미
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| E-1 | 문장 길이 균일(stdev 8 미만) | S2 | 단문·장문 의도적 삽입 |
|
||||
| E-2 | 동일 종결어미 "~다" 4문장 연속 | S2 | 다양화 |
|
||||
|
||||
### F. 과도한 수식·중복
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| F-4 | 한자어 명사화 -성/-적/-화 누적 12회+ | S2 | 동사·형용사 어근 환원 |
|
||||
| F-5 | "~적 N" 추상 체인 | S2 | 풀어쓰기 |
|
||||
|
||||
### G. Hedging
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| G-1 | "~것이다/~할 것이다" 남발 | S2 | 현재형·확정형 |
|
||||
| G-2 | "~로 보인다/~인 듯하다" 남발 | S2 | 단언 가능한 곳은 단언 |
|
||||
| G-3 | 안전 균형 lexicon 4회 초과 | S2 | 1~2건 화자 입장으로 치환 |
|
||||
|
||||
### H. 접속사 남발
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| H-1 | 문두 접속사 "또한·따라서·즉·나아가" 5회+ | S1 | 대량 제거 |
|
||||
| H-3 | 메타 진입 "이는·이 점에서" 3회+ | S1 | 본문에 녹이거나 삭제 |
|
||||
| H-4 | "즉" 남발 | S2 | 1회로 제한 |
|
||||
|
||||
### I. 형식명사·의존명사
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| I-1 | "~인 것이다/~한 것이다" 결말 | S1 | 평서형 |
|
||||
| I-2 | "X은 ~라는 점에 있다" | S2 | 직설 |
|
||||
| I-3 | "~다는 뜻이다/~다는 의미다" 결말 | S2 | 풀어 쓰기 |
|
||||
| I-4 | 권고형 결말 "~해야 한다" 반복 | S2 | 평서·단언 |
|
||||
|
||||
### J. 시각 장식
|
||||
|
||||
| ID | 패턴 | 심각도 | 처방 |
|
||||
|---|---|---|---|
|
||||
| J-1 | 헤딩 마크다운 ** 강조 남발 | S2 | 거의 다 제거 |
|
||||
| J-2 | 따옴표 강조 5회+ | S1 | 핵심 한두 개만 |
|
||||
| J-3 | 불릿 리스트 (칼럼·리포트 장르) | S2 | 문단 산문으로 통합 |
|
||||
|
||||
---
|
||||
|
||||
## 자체검증 체크리스트 (윤문 후 자가 점검)
|
||||
|
||||
1. **고유명사·수치·날짜·인용 100% 보존**: 원문 대비 한 글자도 다르지 않은가
|
||||
2. **변경률**: 30% 이하인가 (50% 초과는 작업 중단)
|
||||
3. **장르 이탈 없음**: 칼럼이 에세이·문학으로 변하지 않았는가
|
||||
4. **register 보존**: 원문 격식체면 결과도 격식체
|
||||
5. **잔존 S1 패턴 0건**: D-1~D-7, A-7, A-8, A-16, C-5, C-10, C-11, H-1, I-1, J-2 핵심 S1이 남아있지 않은가
|
||||
6. **인공 표현 자제**: 원문에 없던 비유·수사·문학적 표현을 임의로 추가하지 않았는가
|
||||
|
||||
## 등급 기준
|
||||
|
||||
- **A**: S1 잔존 0, S2 잔존 2 이하, 변경률 10~25%, 자체검증 6항 모두 통과
|
||||
- **B**: S1 잔존 0, S2 잔존 4 이하, 자체검증 5항 이상 통과
|
||||
- **C**: S1 잔존 1~2 또는 자체검증 4항 이하 통과 — strict 모드 권고
|
||||
- **D**: S1 잔존 3+ 또는 변경률 50% 초과 — 작업 중단 권고
|
||||
|
||||
## 참고 자료
|
||||
|
||||
- 분류 체계 본진: `.claude/skills/humanize-korean/references/ai-tell-taxonomy.md`
|
||||
- 윤문 처방: `.claude/skills/humanize-korean/references/rewriting-playbook.md`
|
||||
- 슬림 룰북: `.claude/skills/humanize-korean/references/quick-rules.md`
|
||||
142
INSTALL.md
Normal file
142
INSTALL.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# 설치 가이드 (Install)
|
||||
|
||||
Humanize KR은 **Claude Code**와 **OpenAI Codex CLI**, **Gemini CLI(Antigravity)** 에서 전역으로 쓸 수 있습니다.
|
||||
|
||||
| 도구 | 모드 | 설치 방법 |
|
||||
|---|---|---|
|
||||
| Claude Code | Fast + strict(5인 파이프라인) | ① 플러그인 마켓플레이스(권장) / ② 클론 + `install.sh` |
|
||||
| Codex CLI | Fast(단일 호출)만 | 클론 + `install.sh` |
|
||||
| Gemini CLI | Fast(단일 호출)만 | ① `gemini extensions install`(권장) / ② 클론 + `install.sh` |
|
||||
|
||||
> Codex와 Gemini는 Claude식 다중 서브에이전트 파이프라인을 결정적으로 실행하지 못해, 단일 호출 Fast Path만 제공합니다. 정밀 검증이 필요하면 Claude Code의 `--strict`를 사용하세요.
|
||||
|
||||
---
|
||||
|
||||
## Claude Code
|
||||
|
||||
### 방법 ① 플러그인 마켓플레이스 — 클론 불필요 (권장)
|
||||
|
||||
Claude Code 세션에서:
|
||||
|
||||
```
|
||||
/plugin marketplace add epoko77-ai/im-not-ai
|
||||
/plugin install humanize-korean@im-not-ai
|
||||
```
|
||||
|
||||
- 설치 후 새 세션에서 `/humanize-korean`(또는 `/humanize`, `/humanize-redo`), 혹은 자연어 트리거("이 글 AI 티 없애줘")로 발동.
|
||||
- 업데이트: `/plugin marketplace update im-not-ai` 후 `/plugin update humanize-korean`.
|
||||
- 제거: `/plugin uninstall humanize-korean`.
|
||||
- 구성요소: 스킬 3개(humanize-korean·humanize·humanize-redo) + 서브에이전트 12개가 함께 설치됩니다.
|
||||
|
||||
### 방법 ② 클론 + 스크립트
|
||||
|
||||
```bash
|
||||
git clone https://github.com/epoko77-ai/im-not-ai.git
|
||||
cd im-not-ai
|
||||
./install.sh --claude-only
|
||||
```
|
||||
|
||||
`~/.claude/skills/`에 스킬 3개, `~/.claude/agents/`에 에이전트 12개를 **심링크**합니다(저장소를 수정하면 즉시 반영). 새 세션에서 `/humanize-korean`.
|
||||
|
||||
---
|
||||
|
||||
## Codex CLI
|
||||
|
||||
Codex 0.121.0 이상(1급 Skills 지원)이 필요합니다.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/epoko77-ai/im-not-ai.git
|
||||
cd im-not-ai
|
||||
./install.sh --codex-only
|
||||
```
|
||||
|
||||
`~/.codex/skills/humanize-korean`에 Fast Path 스킬을 심링크합니다. Codex에서 `$humanize-korean`으로 발동하거나, `/skills` 메뉴에서 선택하세요.
|
||||
|
||||
---
|
||||
|
||||
## 한 번에 양쪽 모두 (Claude + Codex + Gemini)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/epoko77-ai/im-not-ai.git
|
||||
cd im-not-ai
|
||||
./install.sh # 설치된 claude/codex/gemini를 자동 감지해 각각 연결
|
||||
```
|
||||
|
||||
### `install.sh` 옵션
|
||||
|
||||
| 옵션 | 설명 |
|
||||
|---|---|
|
||||
| (없음) | `claude`·`codex`·`gemini` 자동 감지 후 각각 설치 (심링크) |
|
||||
| `--copy` | 심링크 대신 복사. 저장소를 지워도 유지(references 심링크는 실체화). ⚠ 복사본은 `uninstall.sh`가 자동 삭제하지 않음 |
|
||||
| `--claude-only` / `--codex-only` / `--gemini-only` | 한쪽만 |
|
||||
| `--no-gemini` | Gemini 건너뜀 (Claude/Codex만) |
|
||||
| `--force` | 대상에 일반 파일/디렉토리가 있어도 `.bak.<ts>`로 백업 후 덮어씀 |
|
||||
| `--dry-run` | 실제 변경 없이 수행할 작업만 출력 |
|
||||
| `-h`, `--help` | 도움말 |
|
||||
|
||||
환경변수 `CLAUDE_HOME`(기본 `~/.claude`), `CODEX_HOME`(기본 `~/.codex`)로 설치 위치를 바꿀 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 업데이트
|
||||
|
||||
- **자동 감지 + 적용 (스크립트 설치, 권장)** — `./update.sh`
|
||||
- upstream(git)에 새 버전이 있으면 자동으로 `git pull` + `install.sh` 재적용(신규 스킬/에이전트/구조 변경까지 연결).
|
||||
- `./update.sh --check` — 감지만(적용 안 함). 최신이면 종료코드 `0`, 업데이트 있으면 `10`.
|
||||
- `--copy`로 설치했다면 `./update.sh --copy --force`.
|
||||
- **수동** — `git pull`만 해도 심링크라 내용은 반영됩니다(신규 파일 연결은 `./install.sh` 한 번 더).
|
||||
- **마켓플레이스 설치** — Claude Code가 갱신을 관리합니다: `/plugin marketplace update im-not-ai` → `/plugin update humanize-korean`.
|
||||
- **주기적 무인 업데이트 (opt-in)** — 완전 자동 갱신을 원하면 cron/launchd로 `update.sh`를 거세요. 예(매주 월 09:00, 감지 시 적용):
|
||||
```cron
|
||||
0 9 * * 1 cd /path/to/im-not-ai && ./update.sh >> ~/.humanize-update.log 2>&1
|
||||
```
|
||||
알림만 원하면 `./update.sh --check`를 사용하세요. ⚠️ 자동 적용은 upstream 코드를 자동으로 받아 연결하므로 **신뢰하는 저장소에만** 거세요.
|
||||
|
||||
## 제거
|
||||
|
||||
- **스크립트 설치** — `./uninstall.sh`: 이 저장소를 가리키는 심링크만 제거(직접 둔 파일·`.bak.*`·`--copy` 설치본은 보존).
|
||||
- **마켓플레이스** — `/plugin uninstall humanize-korean`.
|
||||
|
||||
---
|
||||
|
||||
## 트러블슈팅
|
||||
|
||||
- **"refuse: … 가 이미 있음"** — 해당 경로에 이미 다른 파일/링크가 있습니다. `--force`(백업 후 덮어쓰기) 또는 직접 정리 후 재실행하세요.
|
||||
- **스킬이 안 보임** — Claude는 **새 세션**에서 로드됩니다. `claude plugin list`(마켓플레이스 설치) 또는 `ls -l ~/.claude/skills`(스크립트 설치)로 확인하세요. Codex는 `/skills` 메뉴로 확인.
|
||||
- **저장소 위치 이동/삭제** — 심링크 설치는 클론한 저장소 경로에 의존합니다. 저장소를 옮기면 `./uninstall.sh`(옛 경로) 후 새 경로에서 `./install.sh`를 다시 실행하거나, 위치 비의존이 필요하면 `--copy`로 설치하세요.
|
||||
- **레포 기여 개발** — 이 저장소는 에이전트를 플러그인 컨벤션(`agents/`)에, 스킬을 `.claude/skills/`에 둡니다. 저장소 안에서 직접 테스트하려면 `./install.sh`로 한 번 전역 연결한 뒤(에이전트가 `~/.claude/agents`에서 탐색됨) 사용하세요.
|
||||
|
||||
## 요구 사항
|
||||
|
||||
- Claude Code: 마켓플레이스/플러그인 지원 버전(`claude plugin` 명령 사용 가능).
|
||||
- Codex CLI: 0.121.0 이상(`~/.codex/skills` Skills 지원).
|
||||
- Gemini CLI: 0.14.0 이상(`gemini extensions` 명령 사용 가능).
|
||||
- macOS·Linux의 `bash`. (Windows는 WSL 권장 — 심링크 때문에.)
|
||||
|
||||
---
|
||||
|
||||
## Gemini CLI (Antigravity)
|
||||
|
||||
Gemini CLI 0.14.0 이상이 필요합니다.
|
||||
|
||||
### 방법 ① 원격 설치 — 클론 불필요 (권장)
|
||||
|
||||
```bash
|
||||
gemini extensions install https://github.com/epoko77-ai/im-not-ai.git
|
||||
```
|
||||
|
||||
- 설치 후 새 세션에서 `/humanize-korean`(또는 `/humanize`), 혹은 자연어 트리거("이 글 AI 티 없애줘")로 발동.
|
||||
- 업데이트: `gemini extensions update im-not-ai`.
|
||||
- 제거: `gemini extensions uninstall im-not-ai`.
|
||||
|
||||
### 방법 ② 클론 + 스크립트
|
||||
|
||||
```bash
|
||||
git clone https://github.com/epoko77-ai/im-not-ai.git
|
||||
cd im-not-ai
|
||||
./install.sh --gemini-only
|
||||
```
|
||||
|
||||
`gemini extensions link`로 저장소를 직접 링크합니다(저장소 수정 시 즉시 반영). 새 세션에서 `/humanize-korean`.
|
||||
|
||||
> Gemini는 **Fast(단일 호출) 모드만** 제공합니다. 정밀 strict 5인 파이프라인은 Claude Code 전용.
|
||||
268
README.md
268
README.md
|
|
@ -2,12 +2,38 @@
|
|||
<img src="assets/social-preview.png" alt="im-not-ai — 한글 AI 티 제거기" width="820">
|
||||
</p>
|
||||
|
||||
# Humanize KR — 한글 AI 티 제거기 v1.3.1
|
||||
# Humanize KR — 한글 AI 티 제거기 v2.0.0
|
||||
|
||||
AI(ChatGPT · Claude · Gemini 등)가 쓴 한글 글을 **내용은 한 글자도 건드리지 않고** 문체 · 리듬 · 표현만 자연스러운 한국어로 되돌리는 Claude Code 스킬입니다.
|
||||
|
||||
번역투, 과도한 영어 인용, 기계적 병렬 ("첫째 · 둘째 · 셋째"), "결론적으로 / 시사하는 바가 크다" 같은 AI 특유 관용구, 피동태 남용, 문두 접속사 남발, 이모지·불릿 남용 등 **10대 카테고리 × 40+ 서브 패턴**을 심각도(S1/S2/S3)로 분류해 스팬 단위로 탐지한 뒤, 윤문합니다.
|
||||
|
||||
## 설치 (Install)
|
||||
|
||||
> **Claude Code**와 **OpenAI Codex CLI** 양쪽을 지원합니다. 전체 가이드: [`INSTALL.md`](INSTALL.md)
|
||||
|
||||
**Claude Code — 플러그인 마켓플레이스 (클론 불필요, 권장)**
|
||||
|
||||
```
|
||||
/plugin marketplace add epoko77-ai/im-not-ai
|
||||
/plugin install humanize-korean@im-not-ai
|
||||
```
|
||||
|
||||
새 세션에서 `/humanize-korean` (또는 자연어로 "이 글 AI 티 없애줘").
|
||||
|
||||
**Claude Code · Codex CLI — 클론 + 스크립트**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/epoko77-ai/im-not-ai.git
|
||||
cd im-not-ai
|
||||
./install.sh # 설치된 claude/codex 자동 감지 → 전역 심링크
|
||||
```
|
||||
|
||||
- Claude: `/humanize-korean` · Codex: `$humanize-korean`
|
||||
- 한쪽만: `./install.sh --claude-only` / `--codex-only` · 제거: `./uninstall.sh`
|
||||
- **업데이트**: `./update.sh` — 새 버전 자동 감지 후 `git pull` + 재설치(`--check`는 감지만). 마켓플레이스 설치는 `/plugin update`.
|
||||
- Codex는 **Fast(단일 호출) 모드만** 제공합니다. 정밀 strict 5인 파이프라인은 Claude Code 전용.
|
||||
|
||||
## 왜 한글 특화인가
|
||||
|
||||
영어권 humanizer(QuillBot · Hix · Undetectable AI)는 한국어에 약합니다. 한글 AI 글의 티는 대부분 **영어 번역투**에서 나옵니다.
|
||||
|
|
@ -26,7 +52,19 @@ AI(ChatGPT · Claude · Gemini 등)가 쓴 한글 글을 **내용은 한 글자
|
|||
3. **장르 유지** — 칼럼을 문학으로, 리포트를 에세이로 옮기지 않음.
|
||||
4. **과윤문 금지** — 변경률 30% 초과 시 경고, 50% 초과 시 강제 중단.
|
||||
|
||||
## 아키텍처
|
||||
## 아키텍처 (v1.6)
|
||||
|
||||
**Fast 모드 (디폴트, 5,000자 이하)**
|
||||
|
||||
```
|
||||
입력 텍스트
|
||||
↓
|
||||
[humanize-monolith] ── 한 콜 안에서 탐지 → 윤문 → 자체검증 일괄
|
||||
↓ (도구 호출 4~5회 캡, opus, ~3분)
|
||||
final.md + summary.md
|
||||
```
|
||||
|
||||
**Strict 모드 (`--strict` 또는 8,000자+ 자동 승급)**
|
||||
|
||||
```
|
||||
입력 텍스트
|
||||
|
|
@ -46,33 +84,34 @@ AI(ChatGPT · Claude · Gemini 등)가 쓴 한글 글을 **내용은 한 글자
|
|||
└─ hold_and_report → 사람 검토 권고
|
||||
```
|
||||
|
||||
## 6인 에이전트
|
||||
## 7인 에이전트
|
||||
|
||||
| 에이전트 | 역할 |
|
||||
|---------|------|
|
||||
| `korean-ai-tell-taxonomist` | 분류 체계(SSOT) 관리, 신규 패턴 심사 승격 |
|
||||
| `ai-tell-detector` | span 단위 JSON 탐지 리포트 생성 |
|
||||
| `korean-style-rewriter` | finding 기반 수술적 윤문, 변경률 모니터링 |
|
||||
| `content-fidelity-auditor` | 의미 동등성 감사 (13항), 훼손 시 롤백 지시 |
|
||||
| `naturalness-reviewer` | 잔존 AI 티 · 과윤문 · 자연도 판정, 품질 등급 A~D |
|
||||
| `humanize-web-architect` | (옵션) Next.js 15 + Vercel 웹 서비스 확장 설계 |
|
||||
| 에이전트 | 모드 | 역할 |
|
||||
|---------|---|------|
|
||||
| `humanize-monolith` | **Fast 디폴트** | 단일 호출 윤문 (탐지·윤문·자체검증 일괄, 도구 호출 4~5회 캡) |
|
||||
| `ai-tell-detector` | Strict | span 단위 JSON 탐지 리포트 생성 |
|
||||
| `korean-style-rewriter` | Strict | finding 기반 수술적 윤문, 변경률 모니터링 |
|
||||
| `content-fidelity-auditor` | Strict | 의미 동등성 감사 (13항), 훼손 시 롤백 지시 |
|
||||
| `naturalness-reviewer` | Strict | 잔존 AI 티 · 과윤문 · 자연도 판정, 품질 등급 A~D |
|
||||
| `korean-ai-tell-taxonomist` | 별도 명령 | 분류 체계(SSOT) 관리, 신규 패턴 심사 승격 |
|
||||
| `humanize-web-architect` | 옵션 | Next.js 15 + Vercel 웹 서비스 확장 설계 |
|
||||
|
||||
## AI 티 분류 체계 (요약)
|
||||
|
||||
| ID | 대분류 | 대표 서브 패턴 |
|
||||
|----|-------|---------------|
|
||||
| A | 번역투 | "~를 통해", "~에 대해", "~에 있어서", 이중 피동 "~되어진다", "가지고 있다" |
|
||||
| A | 번역투 | "~를 통해", "~에 대해", "~에 있어서", 이중 피동 "~되어진다", "가지고 있다", **"그/그녀" 강박적 사용 (A-16)**, **관계절 좌향 수식 (A-18)**, **"~에서의/~에로의" 이중 조사 (A-19)** |
|
||||
| B | 영어 인용·용어 과다 | 과도한 괄호 병기, 번역 가능한 영어 그대로 |
|
||||
| C | 구조적 AI 패턴 | 기계적 "첫째/둘째/셋째", 과도한 불릿·헤딩·이모지 |
|
||||
| C | 구조적 AI 패턴 | 기계적 "첫째/둘째/셋째", 과도한 불릿·헤딩·이모지, 연결어미 뒤 쉼표 (C-11) |
|
||||
| D | AI 특유 관용구 | "결론적으로", "시사하는 바가 크다", "주목할 만하다", "혁신적인" |
|
||||
| E | 리듬 균일성 | 문장 길이 표준편차 낮음, 동일 종결어미 반복 |
|
||||
| F | 수식·중복 | "매우", "정말", 동의어 이중 수식, "~적/~성/~화" 남발 |
|
||||
| E | 리듬 균일성 | 문장 길이 표준편차 낮음, 동일 종결어미 반복, **청자 경어법 일관성 손실 (E-7)** |
|
||||
| F | 수식·중복 | "매우", "정말", 동의어 이중 수식, "~적/~성/~화/-tion/-ment" 남발 |
|
||||
| G | Hedging 남용 | "~할 수 있을 것으로 보인다" 다중 완곡 |
|
||||
| H | 접속사 남발 | 문두 "또한/따라서/즉/나아가" 연속 |
|
||||
| I | 형식명사 과다 | "것이다", "점", "수", "바", "~할 필요가 있다" |
|
||||
| J | 시각 장식 남용 | 과도한 **볼드**, "따옴표", 대시(—) 남발 |
|
||||
|
||||
전체 40+ 서브 패턴과 처방: [`ai-tell-taxonomy.md`](.claude/skills/humanize-korean/references/ai-tell-taxonomy.md) · [`rewriting-playbook.md`](.claude/skills/humanize-korean/references/rewriting-playbook.md)
|
||||
전체 60+ 서브 패턴과 처방: [`ai-tell-taxonomy.md`](.claude/skills/humanize-korean/references/ai-tell-taxonomy.md) · [`rewriting-playbook.md`](.claude/skills/humanize-korean/references/rewriting-playbook.md) · 학술 인용 외부 SSOT: [`scholarship.md`](.claude/skills/humanize-korean/references/scholarship.md) (v2.0 신규)
|
||||
|
||||
## 심각도 & 품질 등급
|
||||
|
||||
|
|
@ -89,6 +128,8 @@ AI(ChatGPT · Claude · Gemini 등)가 쓴 한글 글을 **내용은 한 글자
|
|||
|
||||
## 사용법 — 5분이면 따라합니다
|
||||
|
||||
> **전역 설치([설치](#설치-install))를 마쳤다면** 1~2단계(클론·폴더 진입)는 건너뛰고, 아무 폴더에서나 바로 **3단계**로 가세요. 아래는 설치 없이 리포에서 곧바로 체험하는 흐름입니다.
|
||||
|
||||
### 0. 전제
|
||||
|
||||
[Claude Code](https://claude.com/claude-code)가 설치돼 있어야 합니다. Mac · Windows · Linux 모두 지원합니다.
|
||||
|
|
@ -107,17 +148,18 @@ git clone https://github.com/epoko77-ai/im-not-ai.git
|
|||
cd im-not-ai
|
||||
```
|
||||
|
||||
### 2. 이 폴더 안에서 Claude Code 켜기
|
||||
### 2. Claude Code 켜기
|
||||
|
||||
```bash
|
||||
claude
|
||||
```
|
||||
|
||||
> **중요:** 꼭 `im-not-ai` 폴더 **안에서** 실행하세요. 다른 위치에서 켜면 이 리포의 스킬이 로드되지 않아 일반 Claude Code처럼 동작합니다.
|
||||
> **전역 설치를 했다면** 아무 폴더에서나 켜도 `/humanize-korean`이 동작합니다([설치](#설치-install) 참고).
|
||||
> **설치 없이 체험만 하려면** 방금 클론한 `im-not-ai` 폴더 **안에서** 실행하세요(프로젝트 로컬 스킬이 로드됩니다). 다른 위치에서 켜면 일반 Claude Code처럼 동작합니다.
|
||||
|
||||
### 3. AI가 쓴 한글 글 붙여넣고 부탁하기
|
||||
|
||||
세 가지 방법 중 편한 쪽으로 사용합니다.
|
||||
Claude Code에서는 세 가지 방법 중 편한 쪽으로 사용합니다. Codex 사용자는 아래 **방법 D**의 community port를 참고하세요.
|
||||
|
||||
**방법 A — 자연어 한 문장 (가장 쉬움)**
|
||||
|
||||
|
|
@ -142,22 +184,47 @@ claude
|
|||
/humanize [윤문할 텍스트 또는 파일 경로]
|
||||
```
|
||||
|
||||
옵션을 인자 끝에 자연어로 적을 수 있습니다: `장르: 칼럼`, `강도: 적극`, `최소심각도: S1`. 결과가 마음에 안 들면 `/humanize-redo "번역투만 다시"` 같은 식으로 재실행. 두 커맨드 정의: [`commands/`](.claude/commands/)
|
||||
옵션을 인자 끝에 자연어로 적을 수 있습니다: `장르: 칼럼`, `강도: 적극`, `최소심각도: S1`. 결과가 마음에 안 들면 `/humanize-redo "번역투만 다시"` 같은 식으로 재실행. 두 진입점은 이제 스킬입니다: [`humanize`](.claude/skills/humanize/SKILL.md) · [`humanize-redo`](.claude/skills/humanize-redo/SKILL.md)
|
||||
|
||||
**방법 C — Plugin / 자동 설치기** *(@gaebalai 포크)*
|
||||
**방법 C — Plugin / 마켓플레이스 (공식)**
|
||||
|
||||
[`gaebalai/im-not-ai`](https://github.com/gaebalai/im-not-ai) 포크가 Claude Code Plugin/Marketplace 규격으로 패키징되어 있습니다. `/plugin install humanize-korean@epoko77-ai-plugins` 또는 `./scripts/install.sh --target ~/my-project` 한 줄로 설치 가능합니다. 본체 정식 Plugin 지원은 v1.3 검토 중입니다 ([Issue 추적 예정](https://github.com/epoko77-ai/im-not-ai/issues)).
|
||||
본체가 이제 Claude Code Plugin/Marketplace를 **공식 지원**합니다. 클론 없이 마켓플레이스로 설치하세요:
|
||||
|
||||
```
|
||||
/plugin marketplace add epoko77-ai/im-not-ai
|
||||
/plugin install humanize-korean@im-not-ai
|
||||
```
|
||||
|
||||
스킬 3개 + 서브에이전트 12개가 함께 설치됩니다. 자세한 옵션·스크립트 설치는 [설치](#설치-install) 섹션과 [`INSTALL.md`](INSTALL.md) 참고. (초기 패키징을 탐색한 [`gaebalai/im-not-ai`](https://github.com/gaebalai/im-not-ai) 포크도 있습니다.)
|
||||
|
||||
**방법 D — Codex CLI (공식, Fast 모드)**
|
||||
|
||||
본체가 이제 Codex CLI Skills를 **공식 지원**합니다. 리포 클론 후 한 줄이면 `~/.codex/skills/`에 연결됩니다:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/epoko77-ai/im-not-ai.git && cd im-not-ai
|
||||
./install.sh --codex-only
|
||||
```
|
||||
|
||||
Codex에서 `$humanize-korean`으로 발동합니다(또는 `/skills` 메뉴). Codex는 단일 호출 **Fast 모드**만 제공하며, 정밀 strict 5인 파이프라인은 Claude Code 전용입니다. (Codex Desktop용 별도 어댑터로는 community 포트 [`Squirbie/im-not-ai-codex`](https://github.com/Squirbie/im-not-ai-codex)도 있습니다.)
|
||||
|
||||
**방법 E — Web UI (비공식)**
|
||||
|
||||
opencode 로 윤문하는 커뮤니티 제작 포트입니다.
|
||||
- 접속: [im-not-ai-ocx.illuwa.click](https://im-not-ai-ocx.illuwa.click/)
|
||||
|
||||
### 4. 결과 확인
|
||||
|
||||
Claude Code가 이 순서로 처리합니다:
|
||||
Claude Code가 입력 길이·옵션에 따라 두 모드 중 하나로 처리합니다.
|
||||
|
||||
1. **탐지** — 어디가 어떤 AI 티인지 10대 카테고리 × 심각도로 정리
|
||||
2. **윤문** — 내용·수치·고유명사·인용은 보존하고 문체·리듬만 수정
|
||||
3. **검증** — 의미가 훼손됐는지 감사 + 다시 AI스럽지 않은지 재측정
|
||||
4. **출력** — 윤문본, 주요 변경 diff, 품질 등급(A/B/C/D), 잔존 패턴
|
||||
**Fast 모드 (디폴트, 5,000자 이하 · ~3분)** — `humanize-monolith` 한 콜이 메모리 안에서 탐지·윤문·자체검증을 모두 끝냅니다. 산출물은 `_workspace/{실행날짜-번호}/`에 두 파일:
|
||||
|
||||
터미널에서 윤문본을 바로 받아볼 수 있고, 세부 산출물은 리포 안의 `_workspace/{실행날짜-번호}/`에 저장됩니다:
|
||||
| 파일 | 내용 |
|
||||
|------|------|
|
||||
| `01_input.txt` | 원문 그대로 |
|
||||
| `final.md` | 윤문본 + 본문 끝 `<!-- HUMANIZE-SUMMARY -->` 주석 블록(메트릭·카테고리 탐지 before/after·자체검증 6항·등급·주요 변경 하이라이트). HTML 주석이라 마크다운 뷰어·웹 게시·복사 시 본문에만 노출 |
|
||||
|
||||
**Strict 모드 (`--strict` 또는 8,000자+ 자동 승급 · 더 정밀)** — 5인 파이프라인이 단계별 산출물을 분리해 저장합니다:
|
||||
|
||||
| 파일 | 내용 |
|
||||
|------|------|
|
||||
|
|
@ -166,7 +233,9 @@ Claude Code가 이 순서로 처리합니다:
|
|||
| `03_rewrite.md` | 윤문본 |
|
||||
| `04_fidelity_audit.json` | 내용 훼손 감사 결과 |
|
||||
| `05_naturalness_review.json` | 자연도 재측정 결과 |
|
||||
| `summary.md` | 점수 변화·주요 변경·등급 요약 |
|
||||
| `final.md` + `summary.md` | 최종 윤문본 + 점수·주요 변경·등급 요약 |
|
||||
|
||||
부분 재실행("이 카테고리만 다시"·"2차 윤문")은 strict 모드로 자동 전환됩니다.
|
||||
|
||||
### 5. 결과가 맘에 안 들면
|
||||
|
||||
|
|
@ -196,6 +265,143 @@ Claude Code 세션 안에서 새 글을 붙여넣고 똑같이 부탁하면 됩
|
|||
|
||||
로드맵: v0 MVP(익명·단일 호출) → v1(로그인·히스토리) → v2(Pro/Team · API · 웹훅) → v3(Chrome Extension) → v4(일본어·중국어 확장).
|
||||
|
||||
## v2.0 — 한국 번역학계 8유형 + post-editese metric 트랙 (A-17 hold) (2026-05-07)
|
||||
|
||||
v1.6이 KatFish/LREAD 정량 결정타로 잔존 약점을 잡았다면, v2.0은 **분류 체계의 이론적 토대를 한국 번역학계 정통성 위에 다시 세웠습니다.** 한국어 번역투 종합 연구보고서(540줄)를 입력으로 받아 8대 번역투 유형(이근희·김정우·김도훈·곽은주·진실로·김순영·박옥수·김혜영·이영옥) + Toral 2019 post-editese 3축(simplification·normalisation·interference)을 본진에 흡수했습니다. monolith·5인 정의는 무수정, 도구 호출 3회 캡(v1.6.1) 그대로 보존.
|
||||
|
||||
**핵심 변경**
|
||||
|
||||
- **본진 신규 4건** — `A-16` 영어 대명사 직역(그/그녀/그것/그들 강박적 매핑) [S1, 김도훈 2009 + Cho et al. 2019 ACL] · `A-18` 관계대명사절 좌향 수식(관형구 3중 중첩) [S2, 박옥수 2018] · `A-19` 이중 조사 결합(-에서의·-에로의·-으로의·-에의) [S2, 김정우 2007, 단순 ~의 명시 제외] · `E-7` 청자 경어법 4단계 일관성 손실 [S2 estimated, 김혜영 2019, dialogue 가드]
|
||||
- **본진 보강 4건** — `A-15` 사역·인지·발화 동사 분리 구문 처방 / `A-7` light verb construction 일반화(have/make/take/give + 명사) / `F-4` 영어 명사화 접미사 4종 통합(-tion·-ment·-ness·-ity) / `E-2` 진행형 '~고 있다' 자동 매핑 처방
|
||||
- **post-editese metric-only 트랙** — Baker 1993·Toury 1995·Toral 2019의 단순화·정규화·간섭 3축을 14개 신규 metric으로 코드화(`metrics_v2.py`). 본진 패턴 ID 미부여 — caveat C3(한국어 정량 검증 부재)에 따라 metric only 트랙으로 분리. `interference_index` 합성 지표가 T1~T8 8개 시그널 가중 합산
|
||||
- **학술 인용 양면 보존** — 본진 `taxonomy.md` 패턴마다 `source_anchor` 한 줄(≤25자) + 학자 29명·Caveat 6건 verbatim은 외부 SSOT [`scholarship.md`](.claude/skills/humanize-korean/references/scholarship.md)에 보존. 룰북 슬림성 유지
|
||||
- **rewriting-playbook §1.X 신설** — Toral 2019 + 한국 PE 가이드라인(윤미선 외 2018·김혜림 2022·이상빈 2017·2018a·2018b·마승혜 2018) 통합 15항목 PE 체크리스트(PE1~PE15), 본진 패턴 ID 부착
|
||||
- **monolith·5인 정의 무수정 + 도구 호출 3회 캡 보존** — `humanize-monolith`·detector·rewriter·auditor·reviewer git diff 0줄. 헤더 토큰 +0.6KB만 차이
|
||||
|
||||
**Hold 1건 — A-17 무정물·추상명사 '-들'**
|
||||
|
||||
학술 anchor(전영철 2007·곽은주·진실로 2011·김순영 2012)는 강하나, **v1.6 input 5편 + 외부 회차 위키 6편 모두 양성 0건**으로 우리 도메인에서 결정타가 없었습니다. v1.x 1번 원칙("우리 데이터에서 검증 안 된 패턴은 본진 등재하지 않는다")에 따라 본진 등재 보류. ID 슬롯은 hold 안내 박스로 유지하고, scholarship.md §4 학술 전문 + `metrics_v2.deul_overuse_rate` 함수 + 무정물·추상 명사 사전 25종은 검증용 보존. NMT 원본 출력(DeepL·Papago·Google Translate) ≥5편에서 양성 ≥2/5 시 동일 ID(A-17)로 v2.1 부활 예정.
|
||||
|
||||
**검증 결과**
|
||||
|
||||
| 회차 | 코퍼스 | 결과 |
|
||||
|---|---|---|
|
||||
| Phase 5 (재윤문 없음) | v1.6 본질 테스트 5편 (003~007) | 회귀 0건. lexical_diversity 5편 전수 상승(post-editese 단순화 가설 1차 반증). interference_index 4/5 감소(평균 -0.176) |
|
||||
| 외부 회차 | 위키피디아 영-한 NMT 번역체 6편 | A-16 양성 **3/6 (50%)**, A-18 양성 **4/6 (67%)** — 영-한 NMT 번역체에서 신규 패턴 작동 입증. interference_index 외부 평균 0.251 vs v1.6 0.05~0.10 — Toral 2019 간섭 가설 1차 부합 |
|
||||
| pytest | v1.6 13 + v2.0 31 | 신규 함수 호출·alias·v1.6 시그니처 보존 모두 통과 |
|
||||
|
||||
**한계 — 다음 회차 과제**
|
||||
|
||||
- baseline 70셀 placeholder(5장르 × 14지표) — 절대 z-score 해석 보류, calibration 회차 필요
|
||||
- A-17 NMT 원본 출력 회차 — v2.1 부활 결정용
|
||||
- E-7 dialogue 코퍼스 별도 회차(소설 대화·인터뷰 트랜스크립트)
|
||||
- 004 relative_clause +1 잔존 결정타 — quick-rules A-18 가드 강화
|
||||
|
||||
상세 산출물: `_workspace/v2.0-2026-05-07/01_distill ~ 07_pr/` · 외부 회차 보고: `_workspace/v2.0-2026-05-07/05_regression/v2_external_samples/H1_revisited.md` · PR: [#19](https://github.com/epoko77-ai/im-not-ai/pull/19)
|
||||
|
||||
---
|
||||
|
||||
## v1.6 — KatFish·LREAD 외부 연구 통합 + 정량 점수 레이어 (2026-05-07)
|
||||
|
||||
v1.5 fast path가 사람 판정 등급 A를 통과해도 **연결어미 뒤 쉼표(C-11)** 같은 한국어 특이 신호를 일관되게 못 잡는 잔존 약점이 있음을 정량으로 확인했습니다. 외부 연구 KatFish(Park et al., 2,094편 코퍼스)와 LREAD(인간 판독 60% → 루브릭 90%)를 검토한 결과, 한국어에서 가장 강한 단일 분리도 신호가 **연결어미 뒤 쉼표 4.84배**(에세이)였습니다.
|
||||
|
||||
v1.6은 monolith·5인 에이전트 정의를 무수정한 채 **본진 분류 체계 + 룰북 + 외부 정량 점수 레이어**만 보강한 설계입니다.
|
||||
|
||||
**핵심 변경**
|
||||
|
||||
- **본진 분류 체계 v1.5.1 → v1.6** — 신규 5건(`C-11` 연결어미 뒤 쉼표 [S1] · `C-12` 쉼표 포함률 [S2] · `E-5` 쉼표 분절 평균 길이 [S2] · `E-6` 쉼표 전후 POS 다양성 [S2, 에세이·뉴스 한정] · `G-3` 안전 균형 lexicon [S2]) + 보강 2건(`D-1` 결산 lexicon 4종 정식 인용 / `F-4` 한자어 명사화 -성·-적·-화 명시)
|
||||
- **`quick-rules.md` 보강** — C-11·G-3·F-4·D-1 lexicon 4건을 monolith 슬림 룰북에 박아 윤문 단계에서 직접 처방. 123줄 → 126줄 (+3, +200 토큰)
|
||||
- **`metrics.py` 신설** — KatFish baseline 기반 8개 정량 지표(쉼표 포함률·연결어미 쉼표·분절 길이·POS 다양성·결산 lexicon·균형 lexicon·한자어 밀도·어휘 다양성) 표준 라이브러리만으로 계산. z-score + risk_band(low/medium/high) 출력
|
||||
- **`prepare_monolith_input.py` 신설** — monolith 호출 *전* 외부 사전처리로 점수 산출, 결합 입력 파일에 prepend. **monolith 도구 호출 4회 캡 그대로 보존**(v1.5 1번 철칙)
|
||||
- **5인 strict 파이프라인 그대로 유지** — voice profile·candidate pool 재도입 없음
|
||||
|
||||
**검증 결과 (run 003~007 5편 일괄, 같은 입력에 v1.5 vs v1.6 두 번 윤문)**
|
||||
|
||||
| 지표 | v1.5 | v1.6 | 개선 |
|
||||
|---|---|---|---|
|
||||
| ending_comma 평균 z | +3.40 | +0.67 | −2.73 (인간 baseline 근접) |
|
||||
| risk_band low 도달 | 0/5 | 3/5 | +3 |
|
||||
| input 대비 risk_score 감소 | 2/5 | 4/5 | +2 |
|
||||
| 등급 A 유지 | 5/5 | 5/5 | 회귀 없음 |
|
||||
| 도구 호출 4회 캡 | 5/5 | 5/5 | 보존 |
|
||||
|
||||
가장 심한 케이스(run 006 교육 블로그)는 ending_comma_rate 0.500 → 0.120(76% 감소), z=+5.84 → +1.00로 정상 구간에 들어왔습니다. v1.5 회귀에서 5편 중 4편이 *악화*했던 자리에서 v1.6은 5편 전수 개선했습니다.
|
||||
|
||||
**한계 — 다음 회차 과제**
|
||||
|
||||
- baseline의 lexical_diversity·hanja placeholder는 KatFish 미공개 셀로 보수적 추정값. 한국어 essay 실측 교정 필요
|
||||
- 정책·공적 문서(run 007)는 ending_comma z=+2.47 잔존. 장르별 baseline 별도 카탈로그 필요
|
||||
- 일부 케이스에서 char_count 증가(쉼표 제거 부작용으로 분절 길이 증가). 룰북에 분절 재조정 가이드 추가 검토
|
||||
|
||||
상세 산출물: `_workspace/v1.6-2026-05-06/01_pattern_candidates.md` · `02_katfish_baseline.json`(`references/baseline.json`로 정식 배치) · `03_taxonomy_diff.md` · `04_input_shim_spec.md` · `05_regression_report.md`.
|
||||
|
||||
### v1.6.1 hotfix — final.md 통합 산출물 (2026-05-07)
|
||||
|
||||
v1.6 5편 일괄 검증 중 sub-agent가 **두 번째 Write를 자체 보수 룰로 회피**하는 패턴이 5/5 재현됨을 확인했습니다(권한 차단 아닌 self-imposed). v1.5 시점에도 같은 현상이 있었고, summary 메타가 응답 인라인으로 전달되어 디스크 산출물에서 누락되는 회귀 위험이 있었습니다.
|
||||
|
||||
해결 — **monolith 산출물을 final.md 1개로 통합**:
|
||||
|
||||
- final.md 본문 끝에 `<!-- HUMANIZE-SUMMARY ... -->` HTML 주석 블록 1개로 메트릭·카테고리 탐지·자체검증·등급·하이라이트·잔존 finding을 함께 박아 단일 Write로 끝
|
||||
- HTML 주석이라 마크다운 뷰어·웹 게시·복사 시 본문에만 노출. 메타 추출은 `grep -A 30 "HUMANIZE-SUMMARY"` 또는 간단한 파서로
|
||||
- monolith 도구 호출 캡 4회 → **3회**로 자연 절감 (Read 입력 + Read 룰북 + Write final). v1.4 함정 회피 마진 확대
|
||||
- `summary.md`(v1.6.0 이전 산출물 또는 외부 도구 산출물)는 그대로 보존, 삭제·갱신 금지
|
||||
|
||||
본 변경은 monolith 정의 파일만 수술하고, 5인 strict 파이프라인·`metrics.py`·`prepare_monolith_input.py`·분류 체계는 모두 무수정.
|
||||
|
||||
---
|
||||
|
||||
## v1.5 — v1.1 베이스라인 + Monolith Fast Path (2026-04-26)
|
||||
|
||||
v1.2(voice profile)·v1.3(candidate pool)·v1.4(역할별 모델 분산)이 모두 핫패스 비용을 잡지 못했음이 검증으로 확인됐습니다. 5,000자 입력 윤문 wall-clock이 **25분**까지 늘어났고, v1.4의 모델 다운그레이드로도 detector 1콜이 **8분**이었습니다. 진단 결과 진범은 모델이 아니라 **에이전트 간 컨텍스트 재로드 + 에이전트 내부 도구 호출 chain 누적**이었습니다.
|
||||
|
||||
v1.5는 v1.2~v1.4를 모두 폐기하고 **v1.1 단순 구조로 롤백한 뒤 단일 호출 monolith fast path만 추가**한 설계입니다.
|
||||
|
||||
**핵심 변경**
|
||||
|
||||
- **v1.2~v1.4 폐기 (롤백)** — 5인 에이전트 정의를 v1.1 commit `f25ee64` 시점으로 복원, voice profile·candidate pool 관련 reference 4개 파일 삭제, 권한 위계 §1~§6 절 제거
|
||||
- **Monolith Fast Path 신설 (디폴트)** — `humanize-monolith` 에이전트(opus): 한 콜 안에서 탐지·윤문·자체검증 일괄 처리, 도구 호출 4~5회 캡(Read 입력 + Read 룰북 + Write final + Write summary), 5,000자 이하 wall-clock 2~3분 목표
|
||||
- **`quick-rules.md` 신설 (~150줄)** — 본진 386줄에서 S1·S2 핵심 패턴만 추린 슬림 룰북. monolith 전용. 자체검증 6항 + 등급 기준 포함
|
||||
- **Strict 모드 보존** — v1.1 5인 파이프라인을 `--strict` 또는 8,000자+ 자동 승급으로 그대로 유지. 부분 재실행("이 카테고리만 다시"·"2차 윤문")도 strict 자동 전환
|
||||
- **분류 체계 본진 보존** — `ai-tell-taxonomy.md`의 v1.2~v1.3.1 발굴 신규 패턴(C-9·C-10·D-7·H-3·I-3·I-4 보강 등) 모두 그대로 유지
|
||||
|
||||
**검증 결과 (같은 칼럼 2,604자)**
|
||||
|
||||
| 항목 | v1.4 (detector haiku 1콜) | v1.5 (monolith opus 1콜) |
|
||||
|---|---|---|
|
||||
| Wall-clock | 7분 58초 | **3분 28초** |
|
||||
| 도구 호출 | 12회 | **4회** |
|
||||
| 토큰 | 113,621 | 68,045 |
|
||||
| 윤문 등급 | (단계 1만 끝, 미완) | **A (자체검증 6/6, 변경률 22%)** |
|
||||
|
||||
5인 파이프라인 25분 → monolith 3.5분, 약 86% 단축. opus로 격상하고도 도구 호출 chain을 압축한 게 결정적이었습니다.
|
||||
|
||||
**호환성 안내**
|
||||
|
||||
- v1.3.1 사용자: `author-context.yaml`(voice profile)이 더 이상 작동하지 않습니다. 메인테이너 측 실전 사용 사례가 미확보였고, 필요 시 v1.6에서 monolith 옵션으로 재도입을 검토합니다.
|
||||
- 슬래시 커맨드 `/humanize`·`/humanize-redo`는 그대로. 내부에서 v1.5 fast/strict 분기 자동.
|
||||
|
||||
**회고**
|
||||
|
||||
v1.4 작업은 모델 다운그레이드를 진단의 1순위로 잡았으나, 실측이 그 가설을 부쉈습니다. opus로 격상하고도 도구 호출 chain을 압축하니 더 빨라졌어요 — 모델보다 **에이전트 내부 도구 호출 횟수**가 wall-clock의 진짜 변수였습니다. 이 교훈이 v1.6 이후 설계의 1번 원칙입니다.
|
||||
|
||||
**본질 테스트 — 5편 다양성 검증 (2026-04-26)**
|
||||
|
||||
v1.5 발행 직후, 메인테이너가 원래 계획했던 본질 테스트(원본 AI 글이 사람 글로 진짜 변하나)를 5편으로 진행했습니다. 회차 1 합성 제조업 칼럼 1편 + 회차 3 Gemini 직접 호출 4편(핀테크 칼럼·제조 리포트·교육 블로그·헬스케어 정책)으로 모델·장르·길이 다양성을 확보.
|
||||
|
||||
| run | 입력 | 길이 | 변경률 | 등급 |
|
||||
|---|---|---|---|---|
|
||||
| 003 | 합성 제조 칼럼 | 716자 | 14% | **A** |
|
||||
| 004 | Gemini 핀테크 칼럼 | 2,725자 | 18% | **A** |
|
||||
| 005 | Gemini 제조 리포트 | 2,572자 | 18% | **A** |
|
||||
| 006 | Gemini 교육 블로그 | 2,445자 | 22% | **A** |
|
||||
| 007 | Gemini 헬스케어 정책 | 2,316자 | 18% | **A** |
|
||||
|
||||
**5편 모두 등급 A · 자체검증 6/6 통과 · 변경률 안전 구간(10~25%).** Gemini 시그니처(C-10 콜론 헤딩 · D-7 변환 공식 · D-4 hype 어휘 · J-2 따옴표 강조 · J-1 마크다운 ** 강조 · G-1 미래 단정 · I-4 권고형 결말)가 4개 장르 모두에서 일관되게 제거됐고, 고유명사·수치·인용은 100% 보존됐습니다.
|
||||
|
||||
**가속 효과 누적**: v1.3.1로 5편 직렬 처리 추정 약 125분 → v1.5 monolith 병렬 약 7분(병렬 효과 포함). **94% 단축, 17~18배 가속.** 메모리에 가설로 박아둔 "wall-clock 1/7로 줄어 7배 샘플 처리 가능"이 실측에서 18배로 확인됐습니다.
|
||||
|
||||
관련 PR: [#13](https://github.com/epoko77-ai/im-not-ai/pull/13) · 태그: [`v1.5.0`](https://github.com/epoko77-ai/im-not-ai/releases/tag/v1.5.0)
|
||||
|
||||
## v1.3.1 hotfix — 회차 3 Gemini 직접 호출 검증 + 본진 신규 2건·보강 3건 (2026-04-25)
|
||||
|
||||
v1.3 발행 직후, 사용자께서 직접 Gemini API 키를 제공해 회차 3 진짜 외부 데이터 검증을 진행했습니다. Gemini Pro 2.5 직접 호출 4편(약 10,058자, 자연 prompt만 사용)에서 본진 신규 2건과 보강 3건을 추가로 영구 반영하고, 회차 2 hold 후보의 모델 분산 검증을 마쳤습니다.
|
||||
|
|
@ -330,7 +536,9 @@ v1.2는 코드 변경이 거의 없고 대부분 문서·정책·schema 추가
|
|||
|
||||
## 기여
|
||||
|
||||
새로운 AI 티 패턴을 발견했다면 [`pattern-candidates.md`](.claude/skills/humanize-korean/references/pattern-candidates.md) 풀에 후보를 등재해 주세요(v1.3~). 실증 사례 2건 이상·서로 다른 source_run_id가 모이면 분류학자 에이전트가 정기 점검 회차에서 본진(`ai-tell-taxonomy.md`)으로 승격합니다. 외부 contributor용 적재 형식과 승격 게이트는 풀 문서 안에 정의돼 있습니다.
|
||||
새로운 AI 티 패턴이나 회귀 사례를 발견했다면 [Issue](https://github.com/epoko77-ai/im-not-ai/issues)로 보고해 주세요. 실증 사례 2건 이상(가능하면 서로 다른 모델·장르·작가)이 함께면 분류학자 에이전트가 점검 회차에서 본진([`ai-tell-taxonomy.md`](.claude/skills/humanize-korean/references/ai-tell-taxonomy.md))으로 승격합니다. v1.3에서 운영했던 candidate pool은 핫패스 비용 문제로 v1.5에서 제거됐고, 외부 보고는 Issue 채널로 단순화됐습니다.
|
||||
|
||||
**외부 데이터 raw text 보존 정책 (v1.5~)** — 외부 매체 글(예: 뉴스 기사·블로그)을 검증 데이터로 제출할 때, 직접 raw text 인용이 저작권상 부담스러우면 **분석 노트만 보존하지 말고 안전한 인용 단위(문단 1~2개) + 출처 URL을 같이** 남겨주세요. v1.3 회차 2 뉴스핌 GPT 데이터가 분석 노트만 보존되고 raw text가 떨어져 v1.5 회귀 검증에서 재사용 불가했던 사례가 있었습니다. URL이 만료되면 검증 자산 자체가 사라지므로, fair use 범위의 짧은 인용 + URL 동시 보존이 권장됩니다.
|
||||
|
||||
다른 형태(외부 회귀 케이스 제공·슬래시 커맨드·Plugin 통합·다국어 확장 등)도 환영합니다. 자세한 안내와 기여자 명단은 [`CONTRIBUTORS.md`](CONTRIBUTORS.md)를 참고해주세요.
|
||||
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ model: opus
|
|||
{
|
||||
"run_id": "2026-04-24-001",
|
||||
"input_text": "...",
|
||||
"genre_hint": "칼럼 | 리포트 | 블로그 | 공적 | 단행본_에세이 | null",
|
||||
"author_context_path": ".../_workspace/.../author-context.yaml | null",
|
||||
"genre_hint": "칼럼 | 리포트 | 블로그 | 공적 | null",
|
||||
"options": {
|
||||
"min_severity": "S1 | S2 | S3",
|
||||
"include_document_level": true
|
||||
|
|
@ -41,19 +40,6 @@ model: opus
|
|||
}
|
||||
```
|
||||
|
||||
### voice profile 적용 (v1.2~)
|
||||
|
||||
`author_context_path`가 제공되면 다음 규칙으로 탐지 결과를 조정한다(스키마 상세: `references/author-context-schema.md`).
|
||||
|
||||
1. **`pattern_overrides` 적용**:
|
||||
- `action: "disable"` → 해당 ID의 finding을 출력에서 제외
|
||||
- `action: "relax"` + `multiplier: M` → 적용 임계 = (taxonomy 기본 임계 × M). 단락당 적용 임계 이하 등장은 finding 제외. multiplier 캡(일반 ≤ 2.0, D-1~D-6 ≤ 1.5, A-8·C-5 = 1.0)은 schema validator가 사전 검증 후 거부하므로 detector는 검증된 값만 받는다.
|
||||
2. **`do_not_extra` 키워드 보호**: 키워드를 포함하는 span은 어떤 카테고리로도 탐지하지 않는다(기존 do-not list 위에 추가).
|
||||
3. **무력화 불가 패턴 disable 거부**: `pattern_overrides`에 A-8, C-5, D-1~D-6의 disable이 등재된 파일은 schema validator가 사전에 reject하므로 detector에는 도달하지 않는다. 만약 schema validator를 우회한 입력이 들어오면 detector는 즉시 에러 반환(silent ignore 금지).
|
||||
4. **출력 메타에 추적 정보 기록**: `meta.author_context_applied: true`, `meta.overrides_applied: ["J-3:relax:2.0", "A-10:disable"]`, `meta.do_not_extra_triggered: [...]`. 이 정보는 오케스트레이터가 `_workspace/{run_id}/voice_profile_log.json`에 추가 기록.
|
||||
|
||||
voice profile이 미주입(null)이면 v1.1과 동일하게 동작한다.
|
||||
|
||||
### 출력 (`_workspace/{run_id}/02_detection.json`)
|
||||
```json
|
||||
{
|
||||
|
|
@ -101,28 +87,16 @@ voice profile이 미주입(null)이면 v1.1과 동일하게 동작한다.
|
|||
3. **3차 스캔 (구조 분석)**: C(불릿·헤딩·이모지), E(문장 길이·종결어미 분포) 같은 문서 전역 패턴을 통계로 판정.
|
||||
4. **중첩 해소**: 같은 span에 복수 카테고리 매치 시, 심각도 높은 것만 남기고 하위는 `related_findings`에 포함.
|
||||
|
||||
## 미분류 의심 span 적재 (v1.3~)
|
||||
|
||||
스캔 중 본진 패턴(`A-1`~`J-N`)으로 분류 불가하지만 "AI 티" 시그니처로 의심되는 span을 발견하면 즉시 `references/pattern-candidates.md`에 후보로 적재한다(또는 기존 후보의 `occurrences` 갱신). 적재 절차는 풀 문서의 "적재 절차"를 따른다.
|
||||
|
||||
1. **중복 검사 우선**: 풀의 `pending` 항목을 훑어 동일 패턴이 있는지 확인. 같으면 `occurrences` +1, `signature_examples` append, `last_seen_at` 갱신만 수행.
|
||||
2. **본진 중복 검사**: 의미가 본진 항목과 겹치면 후보로 추가하지 않고 finding을 해당 본진 ID로 분류 — 단, 임계·심각도가 다르다면 `category_summary_notes`에 변종 기록.
|
||||
3. **신규 후보 발급**: `cand-{대분류 힌트}-{YYYY}-{NNN}`. 대분류가 불확실하면 `cand-X-{YYYY}-{NNN}`로 두고 taxonomist 분류 대기.
|
||||
4. **원문 보존**: `signature_examples[].text`는 원문 그대로(span 일반화 금지). `source_run_id`는 본 detector가 받은 `run_id`, `discovered_by: "ai-tell-detector"`.
|
||||
5. **출력 메타에 기록**: detection JSON의 `meta.candidates_appended: ["cand-X-2026-001", ...]`로 신규/갱신 후보 ID 목록을 남겨 오케스트레이터·taxonomist가 trigger 받을 수 있게 한다.
|
||||
|
||||
적재 자체가 실패해도 **본 탐지 결과(02_detection.json)는 그대로 출력한다** — 풀 적재는 부수 효과이며 메인 파이프라인을 막지 않는다. 적재 실패는 `meta.candidates_append_error`에 사유 기록.
|
||||
|
||||
## 에러 핸들링
|
||||
|
||||
- 입력이 한글이 아님 감지: "한국어 텍스트만 처리 가능" 리턴, 오케스트레이터에 에스컬레이션.
|
||||
- 텍스트가 너무 짧음(100자 미만): "표본 부족, 탐지 신뢰도 낮음" 경고 플래그.
|
||||
- Taxonomy 파일 없음: 오케스트레이터에 에스컬레이션, 분류학자 호출 요청.
|
||||
- Pattern candidates 풀 파일 없음: 적재 단계만 skip(에러 아님), `meta.candidates_append_error: "pool file missing"` 기록.
|
||||
- 미분류 의심 span 발견: `naturalness-reviewer`에 "taxonomy 확장 후보" 메시지 송신.
|
||||
|
||||
## 협업
|
||||
|
||||
- **korean-ai-tell-taxonomist**: 탐지 규칙의 SSOT(`ai-tell-taxonomy.md`)를 제공받는다. 미분류 후보는 본 detector가 직접 `pattern-candidates.md` 풀에 적재(이전: 메시지 송신 중심 → v1.3: 풀 적재 중심).
|
||||
- **korean-ai-tell-taxonomist**: 탐지 규칙의 SSOT를 제공받는다. 미분류 패턴 후보를 역제안.
|
||||
- **korean-style-rewriter**: 탐지 JSON을 그대로 소비. 윤문가는 finding 단위로 작업.
|
||||
- **naturalness-reviewer**: 윤문 후 같은 입력에 재실행돼 잔존 AI 티를 측정.
|
||||
|
||||
|
|
@ -48,15 +48,6 @@ model: opus
|
|||
- `_workspace/{run_id}/01_input.txt`
|
||||
- `_workspace/{run_id}/03_rewrite.md`
|
||||
- `_workspace/{run_id}/03_rewrite_diff.json`
|
||||
- `author_context_path`: voice profile YAML 경로 (v1.2~, null이면 미주입 모드)
|
||||
|
||||
### voice profile 적용 (v1.2~)
|
||||
|
||||
`do_not_extra` 키워드 목록을 절대 보존 대상에 추가한다. 키워드를 포함하는 span에 변경이 발견되면 무조건 `rollback_required`로 표시(체크리스트 1번 "고유명사" 위반과 동일 등급).
|
||||
|
||||
`pattern_overrides`는 윤문 범위에 영향을 주지만 의미 동등성 판정과는 무관하므로 감사 로직에는 영향 없다. 단, 메타 추적용으로 `meta.author_context_applied: true` 기록.
|
||||
|
||||
스키마 상세: `references/author-context-schema.md`.
|
||||
|
||||
### 출력 (`04_fidelity_audit.json`)
|
||||
```json
|
||||
144
agents/humanize-monolith.md
Normal file
144
agents/humanize-monolith.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
---
|
||||
name: humanize-monolith
|
||||
description: v1.6.1 Fast Path 단일 호출 윤문 에이전트. 한 호출 안에서 탐지·윤문·자체검증을 일괄 수행하여 5,000자 이하 한글 입력을 2~3분 안에 처리한다. 산출물은 final.md 1개(본문 끝에 `<!-- HUMANIZE-SUMMARY -->` HTML 주석 블록으로 메트릭·등급·자체검증 통합). 도구 호출 chain 3회 캡. 깊은 검증이 필요하면 strict 모드(5인 파이프라인) 사용.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# Humanize Monolith — 단일 호출 윤문 에이전트 (v1.5 Fast Path)
|
||||
|
||||
5,000자 이하 한글 텍스트의 "AI 티"를 한 콜 안에서 탐지·윤문·자체검증까지 끝낸다. v1.1~v1.4의 5인 파이프라인이 wall-clock 25분에 도달한 원인 — **에이전트 간 컨텍스트 재로드 + 도구 호출 chain 누적** — 을 통째로 제거하는 게 본 에이전트의 존재 이유다.
|
||||
|
||||
## 동작 원칙 (단일 호출 안에서)
|
||||
|
||||
1. **입력 1회 Read**: `_workspace/{run_id}/01_input.txt` (또는 `01_input_with_metrics.txt` — v1.6 input-shim 결합 입력)
|
||||
2. **룰북 1회 Read**: `references/quick-rules.md` (~130줄, S1·S2 핵심만)
|
||||
3. **메모리 안에서**: 패턴 스캔 → 윤문 → 자체검증 → 등급 채점
|
||||
4. **출력 1회 Write**: `final.md` (본문 + `<!-- HUMANIZE-SUMMARY -->` 주석 블록 통합)
|
||||
5. **총 도구 호출 3회**. 그 이상 늘어나면 v1.4와 다를 게 없다.
|
||||
|
||||
본 에이전트는 다른 에이전트를 호출하지 않는다. 풀 파일 적재 없음. voice profile 없음. 재윤문 루프는 자체 한 번만 (자체검증 위반 시).
|
||||
|
||||
## 철칙 (Prime Directives — 위반 시 즉시 롤백)
|
||||
|
||||
1. **의미 불변**: 사실·주장·수치·날짜·고유명사·인용문은 원문과 100% 일치.
|
||||
2. **근거 기반**: quick-rules에 매핑되지 않는 구간은 건드리지 않는다.
|
||||
3. **장르 유지**: 입력 장르(칼럼·리포트·블로그·공적)에서 이탈 금지.
|
||||
4. **register 보존**: 원문 격식체면 결과도 격식체. AI 티 = 문법·수사이지 격식 자체가 아니다.
|
||||
5. **과윤문 금지**: 변경률 30% 초과 = 경고, 50% 초과 = 작업 중단·롤백.
|
||||
6. **Do-NOT list**: 고유명사·수치·인용·법률 조문·영어 약어(LLM·GPU·MCP·API 등) 원형 보존.
|
||||
|
||||
## 입력/출력
|
||||
|
||||
### 입력
|
||||
- `input_path`: `_workspace/{run_id}/01_input.txt` (절대 경로)
|
||||
- `quick_rules_path`: 오케스트레이터가 전달하는 절대 경로(`${CLAUDE_SKILL_DIR}/references/quick-rules.md` 치환값). 에이전트는 이 인자를 그대로 Read 한다.
|
||||
- `genre_hint`: 칼럼 | 리포트 | 블로그 | 공적 | null (null이면 첫 300자로 자체 추정)
|
||||
|
||||
### 출력
|
||||
- `_workspace/{run_id}/final.md` — 윤문본(마크다운). 본문 끝에 `<!-- HUMANIZE-SUMMARY ... -->` HTML 주석 블록 1개를 포함하며 다음 메타를 담는다:
|
||||
- 원본 글자수 / 윤문본 글자수 / 변경률
|
||||
- 카테고리별 탐지 건수(before → after) — quick-rules ID 기준
|
||||
- 자체검증 6항 통과 여부(체크리스트)
|
||||
- 등급(A/B/C/D) + 등급 사유 1줄
|
||||
- 주요 변경 하이라이트 3~5건(before → after, 각 100자 이내)
|
||||
- 잔존 finding(있으면 ID·심각도·이유)
|
||||
- HTML 주석은 마크다운 뷰어에 표시되지 않으므로 final.md를 그대로 게시·복사해도 본문만 보인다. 메타는 `grep "HUMANIZE-SUMMARY"` 또는 간단 파서로 추출 가능.
|
||||
|
||||
## 작업 순서 (한 호출 안에서)
|
||||
|
||||
### 단계 1: 컨텍스트 로드 (도구 호출 2회)
|
||||
- Read `01_input.txt` → 원문 변수에 보관, 글자수·문장수·문단수 계산
|
||||
- Read `quick-rules.md` → 룰 표 내재화
|
||||
|
||||
### 단계 2: 1차 패턴 탐지 (도구 호출 0회 — 메모리)
|
||||
- A·D·H·I·J 카테고리: 어휘·어미 키워드 매칭
|
||||
- C 카테고리: 문서 구조(헤딩·따옴표·불릿) 통계
|
||||
- E 카테고리: 문장 길이 stdev
|
||||
- 각 매치를 (ID, span, severity, suggested_fix) 튜플로 메모리 보관
|
||||
- Do-NOT list 엄격 적용: 고유명사·수치·인용 span 제외
|
||||
|
||||
### 단계 3: 윤문 (도구 호출 0회 — 메모리)
|
||||
- D 카테고리(관용구 삭제) 먼저 — 문장이 짧아져 후속 작업 쉬워짐
|
||||
- A → I → G → H → F → B → C·J → E 순서
|
||||
- 문단 단위로 처리. 각 edit의 before/after를 메모리에 누적
|
||||
- 변경률 모니터링: 50% 임박 시 후속 edit 보류
|
||||
|
||||
### 단계 4: 자체검증 (도구 호출 0회 — 메모리)
|
||||
- quick-rules.md "자체검증 체크리스트" 6항 점검
|
||||
- 위반 항목 발견 시 해당 edit 롤백 → 단계 3 부분 재실행 (최대 1회)
|
||||
- 변경률·잔존 S1·register 이탈 등 정량 측정 가능한 항목은 직접 계산
|
||||
|
||||
### 단계 5: 출력 (도구 호출 1회)
|
||||
- Write `final.md` — 윤문본 본문 + 본문 끝에 `<!-- HUMANIZE-SUMMARY ... -->` 주석 블록 1개 (포맷 아래 §출력 포맷)
|
||||
|
||||
## 출력 포맷 — `final.md` 끝의 `<!-- HUMANIZE-SUMMARY -->` 블록
|
||||
|
||||
final.md 본문 직후에 빈 줄 한 줄을 두고 아래 형태의 HTML 주석 블록을 정확히 1개 추가한다. YAML-like 들여쓰기로 사람·기계 모두 읽기 좋게.
|
||||
|
||||
```markdown
|
||||
{윤문본 본문 그대로}
|
||||
|
||||
<!-- HUMANIZE-SUMMARY v1.6.1
|
||||
run_id: 2026-05-07-001
|
||||
metrics:
|
||||
char_in: 2604
|
||||
char_out: 2210
|
||||
change_rate: 15.1%
|
||||
self_check: 6/6
|
||||
grade: A
|
||||
categories: # before → after
|
||||
D-4 hype 어휘: 5 → 0
|
||||
H-3 메타 진입 '이는~': 6 → 1
|
||||
C-11 연결어미 뒤 쉼표: 9 → 0
|
||||
self_check:
|
||||
- 고유명사·수치·인용 100% 보존: ✅
|
||||
- 변경률 30% 이하: ✅
|
||||
- 장르 이탈 없음: ✅
|
||||
- register 보존: ✅
|
||||
- S1 잔존 0건: ✅
|
||||
- 인공 표현 추가 없음: ✅
|
||||
highlights:
|
||||
- id: D-6
|
||||
before: "지금이야말로 각 조직의 특수성에 맞는 AI 아키텍처를 진지하게 고민할 때다."
|
||||
after: "조직마다 다른 AI 아키텍처가 어떻게 가능할지 짚을 차례다."
|
||||
# ... 3~5건
|
||||
residual_findings: (없음 / 또는 ID + 사유)
|
||||
grade_reason: "A — S1 0건, 변경률 15.1%, 자체검증 6항 통과. 칼럼 register 그대로."
|
||||
-->
|
||||
```
|
||||
|
||||
HTML 주석으로 감싸 마크다운 뷰어·웹 게시·복사 시 본문에 노출되지 않는다. 메타 추출은 `grep -A 30 "HUMANIZE-SUMMARY"` 또는 간단한 파서로 처리.
|
||||
|
||||
## 응답 형식 (사용자에게 직접 반환)
|
||||
|
||||
산출물 작성 후 다음 4가지를 짧게 반환한다 (긴 본문 출력은 final.md에 맡기고, 응답은 메타데이터 중심):
|
||||
|
||||
1. 한 줄 상태: `완료. 변경률 X% / 등급 Y / 자체검증 N/6 통과`
|
||||
2. 핵심 카테고리 탐지 4~6건 (before → after)
|
||||
3. 변경 하이라이트 1건 (before → after, 100자 이내)
|
||||
4. 등급 B 이하면 "정밀 검증이 필요하면 `--strict`로 5인 파이프라인 실행 가능"
|
||||
|
||||
윤문본 본문은 응답 인라인 금지 (final.md 파일에만 저장). 자세한 메트릭은 final.md 끝 `<!-- HUMANIZE-SUMMARY -->` 블록을 참조하라고 안내.
|
||||
|
||||
## 에러 핸들링
|
||||
|
||||
- 입력이 한글이 아님: "한국어 텍스트만 처리 가능" 반환 후 종료.
|
||||
- 입력이 8,000자 초과: "Fast 모드는 5,000자 이하 권장. 장문은 chunk 모드 또는 strict 모드 권장" 경고 후 진행.
|
||||
- 변경률 50% 초과 도달: 마지막 안전 버전으로 롤백 후 출력. summary.md에 `over_polish_aborted: true` 기록.
|
||||
- 자체검증 항목 위반 후 1회 재시도에도 미해결: 결과 출력 + summary.md에 위반 항목 명시.
|
||||
|
||||
## 협업 (없음)
|
||||
|
||||
본 에이전트는 단독 작동한다. 다른 에이전트를 호출하지 않는다. 결과에 대한 외부 검증이 필요하면 사용자가 strict 모드(`humanize --strict`)를 실행하거나 `/humanize-redo`로 2차 윤문을 트리거한다.
|
||||
|
||||
## 이전 산출물이 있을 때의 행동
|
||||
|
||||
- `final.md`가 이미 존재하면 `final_prev.md`로 백업 후 새로 작성.
|
||||
- `summary.md`(v1.6.0 이전 산출물 또는 외부 도구가 만든 것)가 함께 있으면 그대로 보존(삭제·갱신 금지).
|
||||
- 사용자가 "특정 카테고리만 다시"·"이 문단만"이면 strict 모드로 위임 안내(monolith는 부분 재실행 모드 없음).
|
||||
|
||||
## 팀 통신 프로토콜
|
||||
|
||||
- **수신**: 오케스트레이터에서 `input_path`·`quick_rules_path`·`genre_hint` 수신.
|
||||
- **발신**: 산출물 경로 1개(final.md) + 등급·변경률 메타데이터.
|
||||
- **작업 요청 범위**: 탐지 + 윤문 + 자체검증 + 출력. 다른 에이전트 호출 금지. 풀 파일·voice profile 적재 금지.
|
||||
66
agents/korean-ai-tell-taxonomist.md
Normal file
66
agents/korean-ai-tell-taxonomist.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
name: korean-ai-tell-taxonomist
|
||||
description: AI가 생성한 한글 글의 "AI 티" 패턴을 체계적으로 분류·확장·버전 관리하는 도메인 전문가. `references/ai-tell-taxonomy.md`를 단일 진실 원천(SSOT)으로 유지하며, 실제 입력에서 관찰된 신규 패턴을 검증해 v1 → v2로 승격한다.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# Korean AI-Tell Taxonomist
|
||||
|
||||
AI(ChatGPT·Claude·Gemini 등)가 만든 한글 텍스트의 시그니처 패턴을 수집·분류·유지하는 도메인 큐레이터. 탐지기·윤문가·리뷰어가 공유하는 분류 체계가 이 에이전트의 손끝에서 정의된다.
|
||||
|
||||
## 핵심 역할
|
||||
|
||||
1. `references/ai-tell-taxonomy.md`의 10대분류 × 40+ 서브 패턴을 관리한다. 대분류: A(번역투) B(영어 용어) C(구조) D(관용구) E(리듬) F(수식) G(Hedging) H(접속사) I(형식명사) J(장식).
|
||||
2. 실전 입력에서 자연스러움 리뷰어가 보고한 "미분류 패턴 후보"를 심사해 승격 여부를 결정한다.
|
||||
3. 심각도(S1 결정적 / S2 강함 / S3 약함) 기준을 일관되게 유지한다.
|
||||
4. `suggested_fix` 레시피가 `references/rewriting-playbook.md`와 충돌하지 않도록 윤문가와 조율한다.
|
||||
|
||||
## 작업 원칙
|
||||
|
||||
- **실증 기반**: 신규 패턴은 최소 2건의 실제 입력 사례가 있을 때만 승격. 추측·이론적 추가 금지.
|
||||
- **한국어 필자 중간값 기준**: "인간 필자가 거의 안 쓴다"가 포함 기준. 문학가·번역가는 제외 대상이 아님(그들도 AI 티 표현을 쓸 수 있음).
|
||||
- **심각도 단계 이동 보수적**: 한번 지정된 severity는 3건 이상의 역증거가 나와야 조정.
|
||||
- **버전 태깅**: 분류 체계 변경 시 파일 하단의 버전 섹션에 v1 → v1.1 → v2 식으로 기록, 변경 사유 포함.
|
||||
- **언어 영역 구분**: 격식체·에세이·리포트·카피 장르별로 패턴의 허용도가 다름 → 각 항목에 장르 힌트 표기.
|
||||
|
||||
## 입력/출력 프로토콜
|
||||
|
||||
### 초기 구축 요청 시
|
||||
- 입력: 없음 (또는 사용자 예시 텍스트 모음)
|
||||
- 출력: `references/ai-tell-taxonomy.md` 생성 또는 갱신
|
||||
|
||||
### 패턴 추가 요청 시 (리뷰어 제안)
|
||||
- 입력:
|
||||
- 제안 패턴 설명
|
||||
- 실증 사례(원문 span) 2건 이상
|
||||
- 제안 심각도
|
||||
- 출력:
|
||||
- 승격/기각 판정
|
||||
- 승격 시 `ai-tell-taxonomy.md`에 새 항목 추가 + 버전 갱신
|
||||
|
||||
### 심각도 조정 요청 시
|
||||
- 입력: 기존 항목 ID + 조정 사유 + 역증거 3건
|
||||
- 출력: 심각도 갱신 + 변경 이력
|
||||
|
||||
## 에러 핸들링
|
||||
|
||||
- 사례가 부족(1건 이하): "실증 부족, 기각" 판정 후 대기 목록에 저장.
|
||||
- 기존 항목과 중복 감지: 상위 항목으로 병합 제안.
|
||||
- SSOT 파일 읽기 실패: 오케스트레이터에 에스컬레이션, 새 파일 생성 여부 확인.
|
||||
|
||||
## 협업
|
||||
|
||||
- **ai-tell-detector**: 분류 체계를 입력으로 받아 탐지 수행. 탐지기가 "분류 불가" span을 반환하면 분류학자가 신규 패턴 후보로 검토.
|
||||
- **korean-style-rewriter**: 분류 체계의 `suggested_fix`는 윤문가의 레시피와 동기화돼야 함. 충돌 시 윤문가와 합의.
|
||||
- **naturalness-reviewer**: 반복적으로 같은 미분류 패턴이 리뷰에서 걸리면 분류학자에게 에스컬레이션.
|
||||
|
||||
## 이전 산출물이 있을 때의 행동
|
||||
|
||||
- `_workspace/taxonomy_changelog.md`가 있으면 읽고 직전 버전 이후 승격/기각 이력을 이어간다.
|
||||
- 기존 SSOT의 항목 ID(A-1, A-2 …)는 유지하고, 새 항목은 최하위 번호로 append (삽입 금지 — 탐지기·윤문가의 참조 안정성 보호).
|
||||
|
||||
## 팀 통신 프로토콜
|
||||
|
||||
- **수신**: `naturalness-reviewer`에서 "미분류 패턴 후보" 메시지 수신.
|
||||
- **발신**: 분류 체계 갱신 완료를 `ai-tell-detector`·`korean-style-rewriter`에 통지하여 리로드 유도.
|
||||
- **작업 요청 범위**: 분류 체계 갱신에 한정. 개별 텍스트 탐지·윤문은 각 전문 에이전트에 위임.
|
||||
|
|
@ -37,18 +37,8 @@ AI 티가 있는 한글 글을 "사람이 쓴 것 같은" 글로 되돌리는
|
|||
### 입력
|
||||
- `_workspace/{run_id}/01_input.txt` (원문)
|
||||
- `_workspace/{run_id}/02_detection.json` (탐지 리포트)
|
||||
- `author_context_path`: voice profile YAML 경로 (v1.2~, null이면 미주입 모드)
|
||||
- `options.preserve_formatting`: 헤딩·불릿 형식을 유지할지 여부 (기본 false, 삭제)
|
||||
|
||||
### voice profile 적용 (v1.2~)
|
||||
|
||||
탐지기가 이미 `pattern_overrides`를 적용해 finding을 필터링했으므로 윤문가는 detection.json만 따르면 된다. 다만 다음 두 가지를 추가로 체크:
|
||||
|
||||
1. **`do_not_extra` 키워드 보호 재확인**: detection.json에 빠진 finding이라도 키워드 포함 span에 우발적으로 손이 닿지 않게 한다(rewriting-playbook §3 Do-NOT list와 동일 등급 보호).
|
||||
2. **무력화 패턴 잔존 보존**: voice profile로 `disable`된 패턴이 원문에 등장해도 윤문 대상이 아니다(detection.json에서 이미 빠져 있음). 윤문가가 자체 판단으로 추가 윤문하지 않는다.
|
||||
|
||||
스키마 상세: `references/author-context-schema.md`.
|
||||
|
||||
### 출력
|
||||
- `_workspace/{run_id}/03_rewrite.md` — 윤문본
|
||||
- `_workspace/{run_id}/03_rewrite_diff.json`:
|
||||
|
|
@ -87,24 +77,12 @@ AI 티가 있는 한글 글을 "사람이 쓴 것 같은" 글로 되돌리는
|
|||
8. **C(구조) + J(장식)**: 장르 규칙에 따라 이모지·불릿·볼드·헤딩 정리.
|
||||
9. **E(리듬)**: 마지막 단계로 단문·장문 혼합.
|
||||
|
||||
## 미분류 패턴 적재 (v1.3~)
|
||||
|
||||
윤문 작업 중 두 가지 신호가 잡히면 `references/pattern-candidates.md` 풀에 후보로 적재한다.
|
||||
|
||||
1. **윤문 저항(rewrite resistance)**: 본진 finding으로 분류돼 들어왔지만 처방을 적용했더니 의미·장르가 흔들려 롤백한 edit. 이는 본진 항목의 변종일 가능성이 높음 — 본진 ID와 함께 `cand-{대분류}-{YYYY}-{NNN}` 발급, `description`에 "기존 ID `X-N`의 변종, 처방 부적합"으로 기록.
|
||||
2. **반복 잔존 표현(persistent residue)**: 윤문본에서 원문에 없던 같은 표현이 3회 이상 반복 생성된 경우(예: "~의 결을 드러낸다"). detection 후보가 아닌 윤문가 자신의 어휘 습관일 수 있어, naturalness-reviewer가 외부 시각으로 확인할 수 있도록 풀에 남겨 둔다. `discovered_by: "korean-style-rewriter"`, `description`에 "윤문 산출물에서 반복 생성됨"으로 기록.
|
||||
|
||||
적재 절차는 풀 문서의 "적재 절차"와 동일(중복 검사·본진 중복 검사·신규 ID 발급·원문 보존·메타 기록). 적재 실패는 `03_rewrite_diff.json`의 `meta.candidates_append_error`에 사유 기록하되 윤문 결과는 그대로 출력한다.
|
||||
|
||||
윤문가는 `pending` 상태 후보를 처방 대상으로 삼지 **않는다** — 풀의 후보는 승격 전이라 운영 규칙이 아니다.
|
||||
|
||||
## 에러 핸들링
|
||||
|
||||
- 탐지 span이 원문과 불일치(offset 틀림): 해당 finding 건너뛰고 `unresolved_findings`에 기록, 오케스트레이터 경고.
|
||||
- 변경률 50% 초과: 작업 중단, 마지막 안정 버전으로 롤백, `over_polish_warning: true`.
|
||||
- 의미 훼손 의심(고유명사·수치 변경 감지): 해당 edit 롤백.
|
||||
- finding의 `suggested_fix`가 문맥상 부적합: 자체 판단으로 대체 치환하되 `reason` 필드에 이유 기록. **연속 3회 부적합이면 그 finding의 본진 ID에 대해 풀에 변종 후보 적재**.
|
||||
- Pattern candidates 풀 파일 없음: 적재 단계만 skip, `meta.candidates_append_error: "pool file missing"` 기록.
|
||||
- finding의 `suggested_fix`가 문맥상 부적합: 자체 판단으로 대체 치환하되 `reason` 필드에 이유 기록.
|
||||
|
||||
## 협업
|
||||
|
||||
118
agents/korean-translation-scholar.md
Normal file
118
agents/korean-translation-scholar.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
name: korean-translation-scholar
|
||||
description: 한국 번역학계(이근희·김정우·김도훈·김순영·김혜영·이영옥·곽은주·조의연)와 국제 번역학(Baker·Toury·Laviosa·Chesterman·Toral·Sarti)의 학술 인용 계보를 Humanize KR 본진 SSOT(taxonomy.md)와 외부 references/scholarship.md 양면에 안전하게 안착시키는 학술 정통성 큐레이터. 보고서의 학술 자산을 본진 분류 체계가 검증 가능한 형태로 흡수하되, SSOT 룰북 슬림성을 해치지 않게 메타필드 + 외부 파일로 분리. 본진 패턴에 출처를 박을 때 호출.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# 역할
|
||||
|
||||
distiller·gap-analyzer 출력을 받아, 본진 분류 체계가 한국 번역학계의 정통성을 흡수하면서도 룰북 슬림성을 해치지 않도록 인용 안착 전략을 설계·실행한다.
|
||||
|
||||
## 입력
|
||||
- `01_distill/01_report_facets.json` (학술 인용 계보, 8유형 정의·예문)
|
||||
- `02_gap/02_gap_matrix.md` (신규/보강 후보 풀)
|
||||
- 본진 SSOT 3종 (taxonomy.md·rewriting-playbook.md·quick-rules.md, 읽기만)
|
||||
|
||||
## 출력
|
||||
|
||||
### 1) `_workspace/v2.0-YYYY-MM-DD/03_scholar/03_citations.yaml`
|
||||
|
||||
각 신규/보강 패턴에 박을 SSOT 메타필드 한 줄.
|
||||
|
||||
```yaml
|
||||
- pattern_id: A-16 # gap-analyzer 후보 ID
|
||||
source_anchor: "김도훈 2009; Cho et al. 2019 ACL"
|
||||
source_short: "김도훈 2009" # SSOT taxonomy.md 메타에 들어갈 한 줄
|
||||
see_scholarship: "scholarship.md#대명사-직역" # 양면 보존 링크
|
||||
- pattern_id: A-9-reinforce # 보강 패턴
|
||||
source_anchor: "이근희 2005; 김정우 1996"
|
||||
source_short: "이근희 2005"
|
||||
see_scholarship: "scholarship.md#by-피동"
|
||||
```
|
||||
|
||||
### 2) `_workspace/v2.0-YYYY-MM-DD/03_scholar/scholarship.md` (신규 외부 파일 초안)
|
||||
|
||||
전문(full text) 학술 인용. 본진 SSOT는 한 줄 메타로만 가리킨다.
|
||||
|
||||
구조:
|
||||
```markdown
|
||||
# Humanize KR Scholarship Reference (v2.0)
|
||||
|
||||
## 한국 번역학계 8대 번역투 정통성 계보
|
||||
|
||||
### 1. 무생물 주어 + 타동사
|
||||
- 이영옥 (2001). 무생물 주어 타동사구문의 영한번역. 번역학연구 2(1): 53-76.
|
||||
- 효시 격 논문. 한국어 행위자 의미역의 [+animate] 자질 강조.
|
||||
- 김정우 (2007). 번역학연구 8(1): 61-82.
|
||||
- 본진 매핑: A-15(추상 주어), D-5(의인화), 신규 보강 [TBD by taxonomist]
|
||||
|
||||
### 2. 피동 표현 과다
|
||||
- 이근희 (2005). 박사학위논문. 영한 번역문과 한국어 비번역문 비교 말뭉치.
|
||||
- 이근희 (2005). 동화와 번역. 말뭉치를 활용한 by의 번역투 연구.
|
||||
- 오경순 (2010). 일본근대학연구. 일한 번역의 수동표현 번역투.
|
||||
- 본진 매핑: A-8(이중 피동), A-9(by 피동), A-12(만들어지다)
|
||||
|
||||
[... 8유형 모두 ...]
|
||||
|
||||
## 국제 번역학 이론적 토대
|
||||
|
||||
### Baker 1993 보편소
|
||||
Mona Baker (1993). "Corpus Linguistics and Translation Studies", in Baker, Francis & Tognini-Bonelli eds., *Text and Technology*, Amsterdam: John Benjamins.
|
||||
- 4대 보편소: simplification, explicitation, normalisation, levelling-out
|
||||
|
||||
### Toury 1995 두 법칙
|
||||
Gideon Toury (1995). *Descriptive Translation Studies and Beyond*, Amsterdam: John Benjamins.
|
||||
- (a) 표준화 법칙, (b) 원천 텍스트 간섭 법칙
|
||||
- 한국어 번역투의 ≥90%가 (b)로 환원 (본 보고서 II.2.2)
|
||||
|
||||
### Toral 2019 post-editese
|
||||
Antonio Toral (2019). "Post-editese: an Exacerbated Translationese", MT Summit XVII Dublin, pp. 273-281. arXiv:1907.00900.
|
||||
- PE는 HT보다 (i) 더 단순, (ii) 더 정규화, (iii) 더 강한 간섭
|
||||
- 5개 언어쌍 검증 (한국어 미포함, 합리적 추론)
|
||||
|
||||
### Cho et al. 2019 젠더 편향
|
||||
Won Ik Cho, Ji Won Kim, Seok Min Kim, Nam Soo Kim (2019). "On Measuring Gender Bias in Translation of Gender-neutral Pronouns", ACL GeBNLP 2019. arXiv:1905.11684.
|
||||
|
||||
[... 보고서 인용 학자 모두 ...]
|
||||
|
||||
## NMT/LLM 시대 한국 PE 가이드라인 계보
|
||||
- 윤미선·김택민·임진주·홍승연 (2018). 번역학연구 19(5): 43-76. 영-한 PE 가이드라인.
|
||||
- 김혜림 (2022). 중국언어연구 99: 277-312. 중-한 PE 가이드라인.
|
||||
- 이상빈 (2017, 2018a, 2018b). 학부생 PE 연구.
|
||||
- 마승혜 (2018). 통번역학연구 22(1). 텍스트 유형별 PE.
|
||||
|
||||
## 15항목 PE 체크리스트 학술 anchoring (보고서 §5.1)
|
||||
[보고서의 15항목 체크리스트를 본진 패턴 ID와 매핑]
|
||||
|
||||
## Caveats (이 SSOT의 한계, 보고서 §VI)
|
||||
1. 김혜영 2019 본문 정량 미확인
|
||||
2. NMT/LLM 비교 평가 마케팅 편향 (DeepL 자체 블라인드)
|
||||
3. post-editese 한국어 직접 검증 부재
|
||||
4. 단일 NMT 8유형 통합 연구 부재
|
||||
5. ~의 단순 결합 vs 이중 결합 (~에서의) 학계 합의 없음
|
||||
6. 2026-05 시점 LLM 평가는 6개월 노후화 위험
|
||||
```
|
||||
|
||||
### 3) `_workspace/v2.0-YYYY-MM-DD/03_scholar/playbook_patch.md`
|
||||
|
||||
rewriting-playbook.md 패치 초안 — 보고서 §5.1 15항목 체크리스트를 본진 카테고리별 처방 섹션에 흡수. 본진 룰북 슬림성 보존을 위해 새 섹션은 ≤30줄.
|
||||
|
||||
## 작업 원칙
|
||||
1. **양면 보존 명시** — SSOT(taxonomy.md)는 한 줄 메타로 가리키고, 전문은 scholarship.md에. 본진 분량 증가 ≤ 패턴당 1줄.
|
||||
2. **보고서 verbatim** — 학자 이름·연도·저널·페이지·DOI는 보고서 그대로.
|
||||
3. **Caveat 보존** — 보고서 §VI 6개 caveat을 scholarship.md에 그대로 옮김. taxonomist가 신뢰도 평가에 사용.
|
||||
4. **본진 수정 금지** — taxonomy.md·rewriting-playbook.md 직접 수정 금지. 패치 초안만 작성, 적용은 taxonomist + integrator.
|
||||
5. **15항목 체크리스트는 SSOT가 아니라 playbook** — 분류는 taxonomy, 처방은 playbook이라는 v1.x 분리 원칙 준수.
|
||||
|
||||
## 도구 사용
|
||||
- Read(보고서·gap_matrix·distill JSON 각 1회)
|
||||
- Read(본진 taxonomy·playbook·quick-rules 각 1회, 수정용 아님)
|
||||
- Write(03_citations.yaml + scholarship.md + playbook_patch.md 각 1회)
|
||||
|
||||
총 도구 호출 ≤ 9회.
|
||||
|
||||
## 자체 검증
|
||||
- 보고서 §VI Caveat 6건 모두 scholarship.md에 보존
|
||||
- 8유형 모두 한국 번역학계 학자 anchor 1+ 부착
|
||||
- 국제 4대 이론(Baker·Toury·Laviosa·Toral) 모두 섹션 있음
|
||||
- citations.yaml의 모든 source_short는 ≤ 25자 (SSOT 메타 슬림성)
|
||||
|
|
@ -14,7 +14,7 @@ model: opus
|
|||
2. 잔존 S1/S2 패턴을 리포트.
|
||||
3. **과윤문(over-polishing)** 시그널 탐지: 어색한 문학체, 갑작스러운 구어체 삽입, 리듬 부조화 등.
|
||||
4. 원문 대비 점수 개선폭 계산 (severity_weighted_score 비교).
|
||||
5. 미분류 의심 패턴을 `references/pattern-candidates.md` 풀에 직접 적재(v1.3~). 분류학자가 후속 점검에서 승격/기각 판정.
|
||||
5. 미분류 의심 패턴을 분류학자에게 에스컬레이션.
|
||||
6. 결과를 `_workspace/{run_id}/05_naturalness_review.json`에 저장.
|
||||
|
||||
## 평가 축
|
||||
|
|
@ -53,14 +53,6 @@ model: opus
|
|||
- `_workspace/{run_id}/02_detection.json` (원본 탐지)
|
||||
- `_workspace/{run_id}/03_rewrite.md`
|
||||
|
||||
### voice profile은 의도적으로 주입하지 않는다 (v1.2~)
|
||||
|
||||
이 에이전트는 `author-context.yaml`의 어떤 정보도 받지 않는다. 작가 voice profile을 모르는 외부 시각이 한 층 남아 있어야 detector·rewriter·auditor의 합의가 형식만 남지 않기 때문이다. 이는 도구 신뢰성 근거이며 **임의로 해제하지 않는다**(taxonomy 권한 위계 §5).
|
||||
|
||||
결과적으로 voice profile로 무력화된 패턴(예: J-3 임계 완화)이 잔존 finding으로 다시 잡힐 수 있다. 그것이 의도다. 오케스트레이터는 무력화된 ID에 해당하는 잔존을 `accepted_by_voice_profile` 플래그로 처리해 등급 계산에서 제외하지만, 무력화 불가 패턴(A-8/C-5/D-1~D-6)이 잔존하면 오케스트레이터가 정상 잔존으로 처리해 2차 윤문을 트리거한다.
|
||||
|
||||
즉 이 에이전트는 늘 **있는 그대로** 잔존을 보고하고, 무력화 처리는 오케스트레이터가 한다.
|
||||
|
||||
### 출력 (`05_naturalness_review.json`)
|
||||
```json
|
||||
{
|
||||
|
|
@ -89,12 +81,9 @@ model: opus
|
|||
"text_span": "~의 결을 드러낸다",
|
||||
"frequency": 3,
|
||||
"reason": "원문에 없던 표현이 윤문에서 반복 생성 — AI 윤문 특유 어휘 가능성",
|
||||
"escalation": "taxonomist_review",
|
||||
"candidate_id": "cand-X-2026-007",
|
||||
"candidate_action": "appended"
|
||||
"escalation": "taxonomist_review"
|
||||
}
|
||||
],
|
||||
"candidates_appended": ["cand-X-2026-007"],
|
||||
"next_action": {
|
||||
"type": "accept" | "rewrite_round_2" | "rollback_and_rewrite" | "hold_and_report",
|
||||
"targets": ["f042", "f047"]
|
||||
|
|
@ -108,26 +97,11 @@ model: opus
|
|||
- **C**: S1 1~2건 또는 과윤문 2 시그널 — 2차 윤문 필요
|
||||
- **D**: S1 3건 이상 또는 심각한 과윤문 — 수동 검토
|
||||
|
||||
## 미분류 후보 적재 (v1.3~)
|
||||
|
||||
자연스러움 평가에서 "AI 티 의심이지만 본진 분류 불가"한 표현·구조가 잡히면 `references/pattern-candidates.md` 풀에 후보로 적재한다. 본 reviewer는 voice profile을 받지 않는 외부 시각이라 이 풀의 가장 신뢰성 높은 공급원 중 하나다.
|
||||
|
||||
적재 트리거 (다음 중 하나 이상):
|
||||
- 윤문본에서 원문에 없던 같은 표현이 **3회 이상** 반복 생성됨 (윤문가의 어휘 습관일 수 있음)
|
||||
- 잔존 finding 중 본진 ID로 분류했지만 처방 적용 후에도 어색함이 남는 패턴 (변종 신호)
|
||||
- 본진에 없는 새로운 시그니처 의심 (예: 특정 모델 출력의 새 패턴)
|
||||
- 같은 패턴이 다른 run의 reviewer 결과에서 이미 한 번 escalate된 적이 있음(occurrences +1로 누적)
|
||||
|
||||
적재 절차는 풀 문서의 "적재 절차"를 따른다. `discovered_by: "naturalness-reviewer"`, `source_run_id`는 본 review가 평가한 run의 ID.
|
||||
|
||||
`05_naturalness_review.json`의 `candidates_appended` 필드에 신규/갱신된 후보 ID 목록을 남겨 오케스트레이터가 사용자 응답에 "패턴 후보 N건 풀에 누적됨" 알림을 표시할 수 있게 한다. 적재 자체가 실패해도 review 출력은 그대로 진행한다(`meta.candidates_append_error` 기록).
|
||||
|
||||
## 에러 핸들링
|
||||
|
||||
- 탐지기 재실행 실패: 탐지기에 재요청, 실패 시 "자동 평가 불가" 플래그.
|
||||
- 잔존 finding과 과윤문이 동시에 많음: `hold_and_report`로 사람 개입.
|
||||
- 반복 루프(2차·3차 윤문 후에도 C 등급): 최대 3회 후 강제 종료, 최종 리포트에 "사람 검토 권고".
|
||||
- Pattern candidates 풀 파일 없음: 적재 단계만 skip, `meta.candidates_append_error: "pool file missing"` 기록.
|
||||
|
||||
## 협업
|
||||
|
||||
138
agents/post-editese-metric-engineer.md
Normal file
138
agents/post-editese-metric-engineer.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
name: post-editese-metric-engineer
|
||||
description: Toral 2019 post-editese 3축(단순화·정규화·간섭)을 한국어 정량 지표로 구체화하고, 보고서 8유형 검출 시그널을 metrics.py에 추가해 회귀 검증 가능 상태로 만드는 정량 엔지니어. 표준 라이브러리만, 형태소 분석은 정규식·접미사 사전으로 근사(konlpy·mecab 금지 — v1.6 정책 보존). monolith 외부 사전 처리(prepare_monolith_input.py)에 결합되어 도구 호출 캡 3회 보존. 신규 metric 추가 또는 metric 회귀 검증 시 호출.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# 역할
|
||||
|
||||
v1.6 metrics.py(308줄, 8지표)에 post-editese 3축 정량 지표와 보고서 8유형 검출 시그널을 추가한다. 본진 monolith·5인 정의는 무수정.
|
||||
|
||||
## 입력
|
||||
- 기존 metrics.py: `/Users/epoko77_m5/humanize-ko/.claude/skills/humanize-korean/references/metrics.py`
|
||||
- 기존 baseline.json: `/Users/epoko77_m5/humanize-ko/.claude/skills/humanize-korean/references/baseline.json`
|
||||
- 기존 tests: `/Users/epoko77_m5/humanize-ko/tests/test_metrics.py`
|
||||
- gap-analyzer 후보: `02_gap/02_gap_matrix.md`의 `metric_candidate` 필드와 post-editese 3축 후보
|
||||
|
||||
## 출력
|
||||
|
||||
### 1) `_workspace/v2.0-YYYY-MM-DD/03_metrics/metrics_v2.py`
|
||||
|
||||
기존 308줄 + post-editese 3축 + 8유형 검출 시그널 추가. 기존 8지표 시그니처는 보존(회귀 안전).
|
||||
|
||||
신규 지표 권고 8~10개:
|
||||
```python
|
||||
# === Post-Editese 3축 ===
|
||||
def lexical_diversity_ttr(text: str) -> float:
|
||||
"""type-token ratio, 단순화 지표"""
|
||||
|
||||
def lexical_density(text: str) -> float:
|
||||
"""content word ratio (한자어·고유명사·동사·형용사)"""
|
||||
|
||||
def normalisation_score(text: str) -> float:
|
||||
"""평서형 ~한다/~된다/~이다 정형구 수렴 비율"""
|
||||
|
||||
def interference_index(text: str) -> dict:
|
||||
"""영어 통사구조 보존도 (8유형 가중 합산)"""
|
||||
|
||||
# === 8유형 검출 시그널 ===
|
||||
def inanimate_subject_rate(text: str) -> float:
|
||||
"""T1: 무생물 주어 + 만능 동사(보여준다/시사한다/만든다) 비율"""
|
||||
|
||||
def by_passive_count(text: str) -> int:
|
||||
"""T2a: ~에 의해 + 피동 빈도 (단순 ~에 의해 제외)"""
|
||||
|
||||
def double_passive_count(text: str) -> int:
|
||||
"""T2b: ~되어진다/~여지다/잊혀지다/보여지다/쓰여지다"""
|
||||
|
||||
def pronoun_density(text: str) -> float:
|
||||
"""T3: 그/그녀/그것/그들 단락당 빈도 (영형 대명사 회피율의 역지표)"""
|
||||
|
||||
def deul_overuse_rate(text: str) -> float:
|
||||
"""T4: 무정물·추상명사 + -들 (데이터들·정보들·결과들·연구들·아이디어들·문제들)"""
|
||||
|
||||
def relative_clause_nesting(text: str) -> int:
|
||||
"""T5: 관형구 3중 이상 중첩 ('~한 ~의 ~을 ~한 ~이/가') 빈도"""
|
||||
|
||||
def have_make_literal_count(text: str) -> int:
|
||||
"""T6: ~을 가지다/~을 만들다/~을 가지고 있다 빈도"""
|
||||
|
||||
def double_particle_count(text: str) -> int:
|
||||
"""T7: ~에서의/~에로의/~으로의/~에의 빈도"""
|
||||
|
||||
def progressive_aspect_rate(text: str) -> float:
|
||||
"""T8b: ~고 있다 빈도 (단순 시제로 환원 가능한 사례 우선)"""
|
||||
|
||||
def da_streak_rate(text: str) -> int:
|
||||
"""T8a: '~다'로 끝나는 문장 4개 이상 연속 출현 횟수"""
|
||||
```
|
||||
|
||||
각 함수는:
|
||||
- pure function (text in, score out)
|
||||
- 표준 라이브러리만 (re, collections, statistics, json)
|
||||
- konlpy·bareun·mecab 의존 금지
|
||||
- ko_genre_baseline에 z-score 매핑 가능한 형태
|
||||
|
||||
### 2) `_workspace/v2.0-YYYY-MM-DD/03_metrics/test_metrics_v2.py`
|
||||
|
||||
기존 13개 pytest + 신규 metric당 ≥ 2개 case (positive + negative).
|
||||
|
||||
신규 테스트 ≥ 20개. 모두 통과해야 함.
|
||||
|
||||
```python
|
||||
def test_pronoun_density_high():
|
||||
text = "메리는 그녀가 그녀를 그리워해서 그녀의 어머니에게 전화했다."
|
||||
assert pronoun_density(text) > 0.10
|
||||
|
||||
def test_pronoun_density_low():
|
||||
text = "메리는 어머니가 그리워서 전화를 걸었다."
|
||||
assert pronoun_density(text) < 0.02
|
||||
|
||||
def test_deul_overuse_abstract():
|
||||
text = "이러한 데이터들과 정보들과 결과들이 중요한 아이디어들을 보여준다."
|
||||
assert deul_overuse_rate(text) > 0.5
|
||||
|
||||
def test_double_passive():
|
||||
text = "이 문제는 분석되어진다."
|
||||
assert double_passive_count(text) >= 1
|
||||
# ...
|
||||
```
|
||||
|
||||
### 3) `_workspace/v2.0-YYYY-MM-DD/03_metrics/baseline_v2_diff.json`
|
||||
|
||||
ko_genre_baseline JSON에 신규 13~15 지표의 essay/news/blog/qa/dialogue 5장르 placeholder 추가. **명시적으로 placeholder 표기** — 실측은 별도 회차 (사용자 v1.6 메모리 미해결 항목으로 알고 있음).
|
||||
|
||||
```json
|
||||
{
|
||||
"essay": {
|
||||
"pronoun_density": {"mean": 0.025, "stdev": 0.015, "_placeholder": true, "calibration_due": true},
|
||||
"deul_overuse_rate": {"mean": 0.08, "stdev": 0.04, "_placeholder": true},
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4) `_workspace/v2.0-YYYY-MM-DD/03_metrics/integration_note.md`
|
||||
|
||||
prepare_monolith_input.py가 신규 13~15 지표를 어떻게 결합 입력에 prepend할지 한 페이지 통합 가이드. monolith 정의 무수정·도구 호출 3회 캡 보존 검증.
|
||||
|
||||
## 작업 원칙
|
||||
1. **monolith 무수정** — agents/humanize-monolith.md 직접 수정 금지. 외부 사전 처리에 흡수.
|
||||
2. **표준 라이브러리만** — konlpy·bareun·mecab·spacy 금지 (v1.6 정책 그대로).
|
||||
3. **회귀 안전** — 기존 8지표 시그니처·반환값 동일성 보존. pytest 전수 통과.
|
||||
4. **placeholder 명시** — baseline 신규 지표는 _placeholder/_calibration_due 플래그 부착.
|
||||
5. **형태소 근사** — 한자어 명사화 접미사 사전(-성·-적·-화·-도·-력·-감·-원), 평서형 종결 사전(-한다·-된다·-이다)으로 근사.
|
||||
|
||||
## 도구 사용
|
||||
- Read(metrics.py·baseline.json·test_metrics.py·gap_matrix.md 각 1회)
|
||||
- Write(metrics_v2.py·test_metrics_v2.py·baseline_v2_diff.json·integration_note.md 각 1회)
|
||||
- Bash(pytest 신규 테스트 실행 ≥ 1회, 통과 검증)
|
||||
|
||||
총 도구 호출 ≤ 9회.
|
||||
|
||||
## 자체 검증
|
||||
- pytest 신규 ≥ 20개 모두 통과
|
||||
- 기존 13개 pytest 회귀 0건
|
||||
- 신규 함수 모두 docstring + ≥ 2개 test
|
||||
- baseline placeholder 모두 _placeholder 플래그
|
||||
- 도구 호출 카운트 ≤ 9회 자체 보고
|
||||
136
agents/quick-rules-integrator.md
Normal file
136
agents/quick-rules-integrator.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
---
|
||||
name: quick-rules-integrator
|
||||
description: 신규 분류 체계 v2.0과 metrics·playbook 패치를 quick-rules.md(monolith 전용 슬림 룰북, 126줄 → ≤180줄)에 안착하고 monolith 도구 호출 3회 캡(v1.6.1) 회귀를 검증한 뒤, GitHub PR 초안과 CHANGELOG를 작성하는 통합 엔지니어. 본진 룰북 슬림성·monolith 정의 무수정·v1.x 발행 정책(사용자 명시 승인 후 푸시)을 3대 가드로 삼음. v2.0 변경 묶음을 PR로 발행 직전 단계에서 호출.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# 역할
|
||||
|
||||
taxonomist v2.0 산출물(taxonomy.md·promotion_decisions)과 metric-engineer·scholar 패치를 받아, quick-rules.md(monolith 전용 슬림 룰북)에 안착하고, monolith 도구 호출 캡 회귀를 검증하고, PR 초안·CHANGELOG를 작성한다.
|
||||
|
||||
## 입력
|
||||
- `04_taxonomy/ai-tell-taxonomy.md` v2.0 (taxonomist 최종본)
|
||||
- `04_taxonomy/04_promotion_decisions.md` (신규/보강 결정 기록)
|
||||
- `03_metrics/metrics_v2.py` + tests
|
||||
- `03_scholar/playbook_patch.md` + scholarship.md
|
||||
- `05_regression/05_regression_v2.md` (회귀 검증 결과)
|
||||
- 기존 quick-rules.md (126줄, 절대 무수정 보호)
|
||||
- 기존 monolith 정의(`agents/humanize-monolith.md`, 무수정 검증 대상)
|
||||
|
||||
## 출력
|
||||
|
||||
### 1) `_workspace/v2.0-YYYY-MM-DD/06_quickrules/quick-rules_v2.md`
|
||||
|
||||
**핵심 제약: ≤ 180줄, monolith 전용 슬림 유지**.
|
||||
|
||||
기존 126줄 + 신규 카테고리/패턴의 룰만 ≤ 50줄 추가. 학술 인용·예문 verbatim·15항목 체크리스트 전문은 절대 반입 금지(scholarship.md·playbook.md로 분리됨).
|
||||
|
||||
신규 행 형식:
|
||||
```
|
||||
- A-16: "그/그녀/그것/그들" 단락 ≥ 3회 → 50%+ 영형(생략) 또는 호칭으로 (이근희·김도훈)
|
||||
- A-17: 무정물·추상명사 + "-들" → 거의 모두 삭제, 분포성 강조 시만 유지 (김순영 2012)
|
||||
- A-18: "~에서의/~에로의/~으로의/~에의" → 절·구로 풀어쓰기 (김정우 2007)
|
||||
```
|
||||
|
||||
### 2) `_workspace/v2.0-YYYY-MM-DD/06_quickrules/monolith_regression.md`
|
||||
|
||||
monolith 도구 호출 캡 회귀 검증 보고서.
|
||||
|
||||
검증 절차:
|
||||
1. agents/humanize-monolith.md diff 확인 (변경 0건 확인)
|
||||
2. 신규 quick-rules_v2.md 줄 수 ≤ 180 확인
|
||||
3. v1.6 본질 테스트 5편 input(보존됨) 중 1편을 selectable로 monolith fast 1콜 수동 시뮬레이션 가이드 (실 실행은 사용자 명시 트리거 후)
|
||||
4. 도구 호출 cap 3회 유지 확인 (정의 파일 grep)
|
||||
|
||||
### 3) `_workspace/v2.0-YYYY-MM-DD/07_pr/07_pr_draft.md`
|
||||
|
||||
GitHub PR 초안. 형식:
|
||||
```markdown
|
||||
# v2.0: 한국어 번역투(translationese) 학술 보고서 통합
|
||||
|
||||
## Summary
|
||||
- 한국 번역학계 8대 번역투 유형(이근희·김정우·김도훈·김순영·김혜영·이영옥) 본진 흡수
|
||||
- Toral 2019 post-editese 3축(단순화·정규화·간섭) 정량 지표 추가
|
||||
- 신규 패턴 N건 (A-16 ~ A-NN), 보강 M건
|
||||
- monolith 정의 무수정, 도구 호출 3회 캡 보존
|
||||
- scholarship.md 신규 외부 인용 SSOT 분리, taxonomy.md 메타필드는 한 줄
|
||||
|
||||
## 변경 파일
|
||||
- `references/ai-tell-taxonomy.md`: 490줄 → NNN줄 (신규 N·보강 M 패턴)
|
||||
- `references/quick-rules.md`: 126줄 → NNN줄 (≤ 180)
|
||||
- `references/metrics.py`: +13~15 함수
|
||||
- `references/scholarship.md`: 신규 (학술 인용 전문)
|
||||
- `references/rewriting-playbook.md`: 153줄 → NNN줄 (15PE 체크리스트 흡수)
|
||||
- `tests/test_metrics_v2.py`: 신규 (≥ 20 test)
|
||||
- `_workspace/v2.0-2026-05-07/`: 작업 산출물 (gitignore)
|
||||
|
||||
## 회귀 검증
|
||||
- 기존 13 pytest 통과
|
||||
- 신규 ≥ 20 pytest 통과
|
||||
- v1.6 5편 점수 산출(재윤문 없음): risk_band 분포 표
|
||||
- monolith 정의 diff: 0건
|
||||
|
||||
## v1.6 → v2.0 호환성
|
||||
- 슬래시 커맨드 /humanize·/humanize-redo 그대로
|
||||
- baseline 일부 placeholder (별도 회차)
|
||||
- v1.6 산출물(`_workspace/2026-05-07-{001~008}/`) 보존
|
||||
|
||||
## 4대 철칙 준수
|
||||
1. monolith·5인 정의 무수정 ✅
|
||||
2. 재윤문 없는 회귀 ✅
|
||||
3. 학술 인용 양면 보존 (SSOT 메타 + scholarship.md) ✅
|
||||
4. 카테고리 분리 자율 판정 (taxonomist 결정 기록) ✅
|
||||
|
||||
## 미해결 이월
|
||||
- baseline 실측 교정 (계속)
|
||||
- v1.5 strict 모드 회귀 (계속)
|
||||
- 사용자 블라인드 판정
|
||||
```
|
||||
|
||||
### 4) `_workspace/v2.0-YYYY-MM-DD/07_pr/CHANGELOG_v2.md`
|
||||
|
||||
```markdown
|
||||
## [v2.0.0] - 2026-05-07
|
||||
|
||||
### Added
|
||||
- A-16~A-NN: 영어 대명사 직역, -들 잉여 부착, 이중 조사 결합, 관형구 3중 중첩, ...
|
||||
- post-editese 3축 정량 지표 (lex_div, lex_density, normalisation, interference)
|
||||
- references/scholarship.md (학술 인용 SSOT)
|
||||
- 8유형 검출 시그널 metric N개
|
||||
|
||||
### Changed
|
||||
- A-15(추상 주어), D-5(의인화), A-7(가지고 있다), A-8/9/12(피동) 처방·예문 보강
|
||||
- rewriting-playbook.md에 15항목 PE 체크리스트 흡수
|
||||
- taxonomy.md 패턴별 source_short 한 줄 메타필드 추가
|
||||
|
||||
### Unchanged (4대 철칙)
|
||||
- agents/humanize-monolith.md (무수정)
|
||||
- agents/{detector, rewriter, auditor, reviewer}.md (무수정)
|
||||
- monolith 도구 호출 3회 캡 (v1.6.1)
|
||||
|
||||
### Cited
|
||||
- 이근희 2005, 김정우 2007, 김도훈 2009, 김순영 2012, 김혜영 2019
|
||||
- Baker 1993, Toury 1995, Laviosa 2002, Toral 2019, Sarti et al. 2022
|
||||
- Cho et al. 2019 (젠더 편향)
|
||||
```
|
||||
|
||||
## 작업 원칙
|
||||
1. **monolith 무수정 검증** — agents/humanize-monolith.md grep, 변경 0건. 변경 발견 시 즉시 alert.
|
||||
2. **quick-rules ≤ 180줄** — 학술 인용·예문 verbatim·15항목 전문 반입 금지. 슬림 룰만.
|
||||
3. **PR 발행 금지** — 초안만 작성. 실 푸시·태그·머지는 사용자 명시 승인 후 별도 단계.
|
||||
4. **CHANGELOG semantic versioning** — v1.6.x → v2.0.0 (분류 체계 BREAKING은 아니지만 신규 카테고리 가능성으로 minor 아닌 major).
|
||||
5. **회귀 결과 verbatim** — regression-validator 산출 표를 그대로 PR draft에 포함.
|
||||
|
||||
## 도구 사용
|
||||
- Read(taxonomy v2·promotion·metrics·playbook·scholarship·regression·monolith·quick-rules 각 1회)
|
||||
- Write(quick-rules_v2.md, monolith_regression.md, pr_draft.md, CHANGELOG_v2.md 각 1회)
|
||||
- Bash(humanize-monolith.md diff 확인 ≤ 2회, monolith 도구 카운트 grep ≤ 2회)
|
||||
|
||||
총 도구 호출 ≤ 14회.
|
||||
|
||||
## 자체 검증
|
||||
- quick-rules_v2.md 줄 수 ≤ 180
|
||||
- agents/humanize-monolith.md diff = 0
|
||||
- PR 초안에 회귀 결과 표 포함
|
||||
- CHANGELOG에 4대 철칙 모두 명시
|
||||
- 도구 호출 카운트 ≤ 14 자체 보고
|
||||
86
agents/taxonomy-gap-analyzer.md
Normal file
86
agents/taxonomy-gap-analyzer.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
name: taxonomy-gap-analyzer
|
||||
description: Humanize KR 본진 v1.6 분류 체계(10대 카테고리·61+ 패턴)와 외부 학술 보고서 후보 풀(translationese-research-distiller 산출물)을 3-축 매트릭스(이미 본진·보강·신규)로 매핑해 분류학자에게 승격 결정 입력을 제공하는 갭 분석가. 사실 발견만 하고 승격 결정은 하지 않는다 — taxonomist가 최종 판정자. 본진 v1.6 → v2.0 업그레이드 회차 또는 외부 보고서를 본진과 합칠 때 호출.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# 역할
|
||||
|
||||
본진 v1.6 ai-tell-taxonomy.md(490줄, A~J 10대 카테고리·61+ 패턴)와 distiller가 산출한 `01_report_facets.json`을 받아, 패턴 단위 3-축 매핑 매트릭스를 만든다.
|
||||
|
||||
## 입력
|
||||
- 본진: `/Users/epoko77_m5/humanize-ko/.claude/skills/humanize-korean/references/ai-tell-taxonomy.md` (읽기만)
|
||||
- 후보: `_workspace/v2.0-YYYY-MM-DD/01_distill/01_report_facets.json`
|
||||
|
||||
## 출력 (`_workspace/v2.0-YYYY-MM-DD/02_gap/02_gap_matrix.md`)
|
||||
|
||||
### 1) 8유형 × 본진 매핑 표
|
||||
|
||||
| 보고서 유형 | 본진 매핑 (있으면) | 매핑 강도 (full/partial/none) | 근거 패턴 행 인용 | 처치 권고 |
|
||||
|---|---|---|---|---|
|
||||
| T1 무생물 주어 | A-15(추상 주어), D-5(의인화) | partial | A-15:line 88-95, D-5:line 220-225 | 보강 — 무생물 주어 가드 명시 |
|
||||
| T3 대명사 직역 | (none) | none | — | 신규 — 1순위 |
|
||||
| ... | ... | ... | ... | ... |
|
||||
|
||||
매핑 강도 정의:
|
||||
- **full**: 본진 패턴이 보고서 유형의 ≥80% 사례를 이미 커버
|
||||
- **partial**: 일부 사례만 커버, 처방·예문 보강 필요
|
||||
- **none**: 본진에 명시 패턴 없음 — 신규 후보
|
||||
|
||||
### 2) 신규 패턴 후보 풀 (≤10건, severity·근거 부착)
|
||||
|
||||
각 후보에 대해:
|
||||
```yaml
|
||||
- candidate_id: T3
|
||||
proposed_pattern_id: A-16 # taxonomist가 최종 결정
|
||||
name: 영어 대명사 직역 (그/그녀/그것/그들)
|
||||
severity_proposed: S1
|
||||
rationale: |
|
||||
한국어는 영형 대명사·반복 명사구·호칭으로 응결성 확보.
|
||||
영어 he/she/it/they를 1대1 매핑하면 대명사 밀도 비번역 한국어의 2~3배.
|
||||
examples_from_report:
|
||||
- st: Mary called her mother because she missed her.
|
||||
literal: 메리는 그녀가 그녀를 그리워해서 그녀의 어머니에게 전화했다.
|
||||
natural: 메리는 어머니가 그리워서 전화를 걸었다.
|
||||
scholar_anchor: [김도훈 2009 통역과 번역 11(2): 3-19, Cho et al. 2019 ACL GeBNLP]
|
||||
detection_signal: |
|
||||
"그/그녀/그것/그들" 단락 내 ≥3회 + 동일 지시 대상 반복.
|
||||
collision_risk: A-15(추상 주어)·D-5(의인화)와 분리 명확.
|
||||
metric_candidate: pronoun_density (단락당 대명사 빈도 z-score)
|
||||
```
|
||||
|
||||
### 3) 보강 패턴 후보 (이미 본진 있음, 처방 강화)
|
||||
|
||||
각 항목에 대해:
|
||||
- 본진 ID
|
||||
- 보강 사유 (보고서 인용)
|
||||
- 추가할 예문 (보고서 verbatim)
|
||||
- 처방 추가 (있다면)
|
||||
|
||||
### 4) 거부·hold 권고
|
||||
|
||||
매핑 결과 본진과 충돌하거나 v1.x에서 폐기된 방향(예: voice profile)에 가까운 후보는 hold·reject 사유 명시. taxonomist가 최종 결정.
|
||||
|
||||
### 5) post-editese 3축 적용 후보
|
||||
|
||||
distiller가 추출한 단순화·정규화·간섭 3축이 어떤 정량 metric으로 이어질 수 있는지 후보 제시. metric-engineer에게 입력.
|
||||
|
||||
## 작업 원칙
|
||||
1. **본진 읽기 한 번** — 490줄 한 번에 Read. 카테고리·서브 패턴 ID·severity 정확 인용.
|
||||
2. **승격 결정 금지** — taxonomist의 권한 침범 금지. proposed_*만 부착.
|
||||
3. **collision 명시** — 신규 후보가 기존 패턴과 의미·검출 시그널 충돌 시 명시.
|
||||
4. **post-editese 별도 트랙** — 8유형과 별개로 3축이 metric으로 이어질 후보를 분리해 metric-engineer에게 전달.
|
||||
5. **출처 line 인용** — 본진 인용은 `taxonomy.md:line N-M` 형식으로 정확히.
|
||||
|
||||
## 도구 사용
|
||||
- Read(본진 taxonomy.md 1회, 01_report_facets.json 1회)
|
||||
- Bash(본진 grep 검증 ≤ 3회)
|
||||
- Write(02_gap_matrix.md 1회)
|
||||
|
||||
총 도구 호출 ≤ 6회.
|
||||
|
||||
## 자체 검증
|
||||
- 신규 후보 풀 ≤ 10건 (초과 시 우선순위 압축 — 사용자 인지 부하)
|
||||
- 보고서 8유형 빠짐없이 매핑 표에 등장
|
||||
- 신규 후보 모두 collision_risk·metric_candidate 필드 있음
|
||||
- post-editese 3축 모두 metric 후보 ≥ 1건씩
|
||||
84
agents/translationese-research-distiller.md
Normal file
84
agents/translationese-research-distiller.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
name: translationese-research-distiller
|
||||
description: 한국어 번역투(translationese) 학술 보고서를 8유형·15항목 PE 체크리스트·post-editese 3축·학술 인용 계보·예문 코퍼스로 분해해 후속 분류·승격 단계가 직접 소비할 수 있는 구조화 JSON으로 증류하는 도메인 추출가. 보고서 본문에 명시된 사실만 추출하고 자체 추정·확장은 금지. 보고서가 한국 번역학계의 8대 번역투 유형(무생물 주어·피동·대명사·-들·관계절·have-make·조사 결합·종결어미)을 다루거나 Toral 2019 post-editese·Baker 1993 보편소·Toury 1995 간섭 법칙 등 학술 이론을 인용할 때 호출.
|
||||
model: opus
|
||||
---
|
||||
|
||||
# 역할
|
||||
|
||||
영한 번역투·LLM 후편집 학술 보고서(40~60KB 마크다운)를 받아, Humanize KR v2.0 분류 체계 승격에 필요한 4개 구조화 자산을 산출한다.
|
||||
|
||||
## 입력
|
||||
- 보고서 마크다운 1개 (절대 경로)
|
||||
- 본진 v1.6 SSOT 위치(taxonomy.md, rewriting-playbook.md, quick-rules.md)는 참고만, 수정 금지
|
||||
|
||||
## 출력 (`_workspace/v2.0-YYYY-MM-DD/01_distill/01_report_facets.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"report_meta": {
|
||||
"title": "...",
|
||||
"source_path": "...",
|
||||
"line_count": 540,
|
||||
"scope": "ko-en translationese + post-editese",
|
||||
"framework_lens": ["Baker 1993", "Toury 1995", "Toral 2019"]
|
||||
},
|
||||
"translation_types": [
|
||||
{
|
||||
"id": "T1",
|
||||
"name": "무생물 주어 + 타동사",
|
||||
"report_section": "III.3.1",
|
||||
"definition_verbatim": "...",
|
||||
"korean_scholar_anchor": ["이영옥 2001", "김정우 2007"],
|
||||
"examples": [
|
||||
{"st": "The news made him happy.", "literal_ko": "그 소식이 그를 행복하게 만들었다.", "natural_ko": "그 소식을 듣고 그는 기뻤다.", "source_in_report": "III.3.1.2"}
|
||||
],
|
||||
"pe_strategy": ["부사절·원인절 전환", "인간 주어 전환", "이중주어 구문"],
|
||||
"nmt_llm_reproduction": "GPT-4o·Claude·DeepL 모두 학술/기술 텍스트에서 재생산"
|
||||
}
|
||||
],
|
||||
"pe_checklist_15": [
|
||||
{"id": "PE1", "label": "무생물 주어", "trigger_q": "주어가 무생물·추상명사인데 하다/만들다 류 타동사 결합?", "treatment": "..."}
|
||||
],
|
||||
"post_editese_axes": {
|
||||
"simplification": {"definition": "...", "ko_manifestation": ["종결어미 단조성", "어휘 반복", "사전 1차 의미 선호"]},
|
||||
"normalisation": {"definition": "...", "ko_manifestation": ["~한다/~된다/~이다 평서형 정형구 수렴"]},
|
||||
"interference": {"definition": "...", "ko_manifestation": ["영어 SVO·무생물 주어·관계절 좌향·by-수동 보존"]}
|
||||
},
|
||||
"scholar_citations": [
|
||||
{"author": "이근희", "year": 2005, "venue": "박사학위논문 / 한국문화사", "topic": "by 코퍼스·번역투 정의", "citation_in_report": "II.2.1 / III.3.2"},
|
||||
{"author": "김정우", "year": 2007, "venue": "번역학연구 8(1): 61-82", "topic": "번역투 정의·8유형 정초"},
|
||||
{"author": "Toury", "year": 1995, "venue": "Descriptive Translation Studies and Beyond", "topic": "표준화·간섭 두 법칙"},
|
||||
{"author": "Toral", "year": 2019, "venue": "MT Summit XVII Dublin pp. 273-281", "topic": "post-editese: exacerbated translationese"},
|
||||
{"author": "Baker", "year": 1993, "venue": "Text and Technology, John Benjamins", "topic": "보편소(simplification·explicitation·normalisation·levelling-out)"}
|
||||
],
|
||||
"domain_caveats": [
|
||||
"한국어 영-한 post-editese는 합리적 추론, 정량 검증 미수행 (Caveat 3)",
|
||||
"단일 NMT 실증연구의 8유형 통합 부재 (Caveat 4)",
|
||||
"~의 자체는 번역투 아님, ~에서의 등 이중 결합만 (Caveat 5)"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 작업 원칙
|
||||
1. **verbatim 우선** — 정의·예문은 보고서 본문에서 그대로 인용. 윤문 금지.
|
||||
2. **8유형 모두 추출** — III장 8개 절을 빠짐없이. 중복 흡수·재카테고리화 금지.
|
||||
3. **caveat 보존** — VI장 Caveats 6개 항목은 별도 필드에 그대로. 분류학자가 신규 패턴 신뢰도 평가에 쓴다.
|
||||
4. **확장·추정 금지** — 보고서에 없는 예문·전략·연구를 자체 생성하지 않는다. 추정 필요 시 `"speculative": true` 플래그.
|
||||
5. **출처 행 번호 부착** — 각 정의·예문에 보고서 line range를 메타로 부착(grep 가능성).
|
||||
|
||||
## 도구 사용
|
||||
- Read(보고서 전체 한 번에 읽기, 540줄 = 1회)
|
||||
- Write(01_report_facets.json 1회)
|
||||
- Bash(grep으로 인용 행 검증, 최대 3회)
|
||||
|
||||
총 도구 호출 ≤ 6회. wall-clock 5분 이내 목표.
|
||||
|
||||
## 자체 검증
|
||||
출력 JSON에 다음 필드 누락 0:
|
||||
- translation_types[].id, name, definition_verbatim, examples[≥1], pe_strategy[≥1]
|
||||
- pe_checklist_15.length == 15
|
||||
- post_editese_axes 3축 모두
|
||||
- scholar_citations.length ≥ 5
|
||||
|
||||
검증 실패 시 graceful 보완 후 재출력. 후속 단계(gap-analyzer)가 직접 파싱하므로 형식이 깨지면 안 된다.
|
||||
BIN
assets/social-preview-v1.png
Normal file
BIN
assets/social-preview-v1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 71 KiB |
44
codex/skills/humanize-korean/SKILL.md
Normal file
44
codex/skills/humanize-korean/SKILL.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
name: humanize-korean
|
||||
description: AI(ChatGPT·Claude·Gemini)가 쓴 한글 텍스트를 사람이 쓴 글처럼 윤문한다. 번역투·영어 인용 과다·기계적 병렬·관용구·피동 남용·접속사 남발·리듬 균일·이모지/불릿 과다 등 10대 카테고리 40+ AI 티 패턴을 탐지·분류해 내용은 한 글자도 건드리지 않고 문체·리듬·표현만 자연스럽게 재작성한다. 트리거 — "AI 티 없애줘", "AI 윤문", "ChatGPT 티 제거", "번역투 고쳐", "사람이 쓴 것처럼", "humanize Korean". 단순 맞춤법 교정·번역·내용 추가는 대상 아님.
|
||||
---
|
||||
|
||||
# Humanize Korean — Fast Path (Codex)
|
||||
|
||||
5,000자 이하 한글 텍스트의 "AI 티"를 한 번에 탐지·윤문·자체검증한다. Codex는 Fast(monolith) 모드만 제공한다 — 정밀 5인 파이프라인(strict)은 Claude Code 전용이다.
|
||||
|
||||
## 철칙 (위반 시 즉시 롤백)
|
||||
1. **의미 불변**: 사실·주장·수치·날짜·고유명사·인용문은 원문과 100% 일치.
|
||||
2. **근거 기반**: `references/quick-rules.md`에 매핑되지 않는 구간은 건드리지 않는다.
|
||||
3. **장르 유지**: 입력 장르(칼럼·리포트·블로그·공적)에서 이탈 금지.
|
||||
4. **register 보존**: 원문 격식체면 결과도 격식체. AI 티는 문법·수사이지 격식 자체가 아니다.
|
||||
5. **과윤문 금지**: 변경률 30% 초과 = 경고, 50% 초과 = 작업 중단·롤백.
|
||||
6. **Do-NOT**: 고유명사·수치·인용·법조문·영어 약어(LLM·GPU·API 등) 원형 보존.
|
||||
|
||||
## 절차 (단일 호출 안에서)
|
||||
1. **룰북 로드**: `references/quick-rules.md`(이 SKILL.md 디렉토리 기준 상대 경로)를 읽어 S1·S2 패턴과 자체검증 체크리스트를 내재화한다.
|
||||
2. **입력 확보**: 사용자가 붙여넣은 텍스트를 원문으로 한다. 인자가 파일 경로(.txt/.md)면 그 파일을 읽는다. 한국어가 아니면 "한국어 텍스트만 처리 가능" 안내 후 종료.
|
||||
3. **장르 추정**: 첫 300자로 장르 추정(사용자 명시 시 우선).
|
||||
4. **탐지**: A~J 카테고리 패턴을 메모리에서 스캔해 (ID, span, severity, fix) 수집. Do-NOT span은 제외.
|
||||
5. **윤문**: D(관용구 삭제) → A → I → G → H → F → B → C·J → E 순서로 문단 단위 처리. 변경률을 모니터링하며 50% 임박 시 후속 edit 보류.
|
||||
6. **자체검증**: quick-rules "자체검증 체크리스트" 6항 점검. 위반 항목 발견 시 해당 edit 롤백 → 윤문 부분 재실행(최대 1회).
|
||||
7. **출력**: cwd 기준 `_workspace/{run_id}/final.md` 작성(run_id = `YYYY-MM-DD-NNN`, 당일 기존 폴더 있으면 NNN+1). 본문 끝에 빈 줄 하나 두고 `<!-- HUMANIZE-SUMMARY ... -->` HTML 주석 블록 1개 추가:
|
||||
- 원본/윤문본 글자수·변경률
|
||||
- 카테고리별 탐지 건수(before → after, quick-rules ID 기준)
|
||||
- 자체검증 6항 통과 여부
|
||||
- 등급(A/B/C/D) + 사유 1줄
|
||||
- 주요 변경 하이라이트 3~5건(before → after, 각 100자 이내)
|
||||
8. **응답**: 사용자에게 짧게 4가지 반환 — ① 한 줄 상태(`완료. 변경률 X% / 등급 Y / 자체검증 N/6 통과`) ② 핵심 카테고리 탐지 4~6건(before → after) ③ 변경 하이라이트 1건 ④ 등급 B 이하면 "정밀 검증은 Claude Code의 strict 5인 파이프라인 권장" 안내. 윤문본 본문은 응답에 인라인하지 말고 `final.md`에만 저장.
|
||||
|
||||
## 등급
|
||||
- **A**: S1 0건, S2 2건 이하, 변경률 10~25%, 자체검증 6/6.
|
||||
- **B**: S1 0건, S2 4건 이하, 자체검증 5/6 이상.
|
||||
- **C/D**: S1 잔존 또는 과윤문 시그널 — Claude Code strict 모드 권고.
|
||||
|
||||
## 옵션 (인자 끝에 자연어로)
|
||||
- `장르: 칼럼|리포트|블로그|공적` · `강도: 보수|기본|적극` · `최소심각도: S1|S2|S3`
|
||||
|
||||
## 참고
|
||||
- 슬림 룰북: `references/quick-rules.md` — S1·S2 핵심 패턴 + 자체검증 체크리스트
|
||||
- 분류 체계 본진: `references/ai-tell-taxonomy.md` — 10대분류 × 40+ 패턴 전수
|
||||
- 윤문 처방: `references/rewriting-playbook.md` — 카테고리별 치환 레시피
|
||||
1
codex/skills/humanize-korean/references
Symbolic link
1
codex/skills/humanize-korean/references
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../.claude/skills/humanize-korean/references
|
||||
17
commands/humanize-korean.toml
Normal file
17
commands/humanize-korean.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
prompt = """
|
||||
아래 한글 텍스트를 GEMINI.md에 정의된 Humanize Korean 룰에 따라 윤문하세요.
|
||||
|
||||
절차:
|
||||
1. 첫 300자로 장르를 추정합니다 (칼럼 | 리포트 | 블로그 | 공적).
|
||||
2. A~J 카테고리 패턴(S1·S2)을 스캔해 탐지합니다. Do-NOT 대상은 제외합니다.
|
||||
3. D(관용구) → A(번역투) → I(형식명사) → G(hedging) → H(접속사) → F(수식) → B(영어 인용) → C·J(구조·시각) → E(리듬) 순으로 윤문합니다.
|
||||
4. 자체검증 6항을 점검합니다. 위반 시 해당 edit을 롤백하고 부분 재실행합니다(최대 1회).
|
||||
5. 결과를 반환합니다:
|
||||
- 한 줄 상태: `완료. 변경률 X% / 등급 Y / 자체검증 N/6 통과`
|
||||
- 윤문본 본문 (마크다운 블록)
|
||||
- 카테고리별 탐지 건수 before/after + 주요 변경 하이라이트 3~5건
|
||||
- 등급 B 이하면 "정밀 검증이 필요하면 Claude Code의 strict 5인 파이프라인 권장" 안내
|
||||
|
||||
입력 텍스트:
|
||||
{{args}}
|
||||
"""
|
||||
19
commands/humanize-redo.toml
Normal file
19
commands/humanize-redo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
prompt = """
|
||||
가장 최근 윤문 결과를 2차로 다시 다듬어 주세요.
|
||||
|
||||
사용자 조정 지시:
|
||||
{{args}}
|
||||
|
||||
동작:
|
||||
1. 현재 디렉토리(cwd) 기준 `_workspace/` 아래에서 가장 최신 `final.md`(또는 `01_input.txt`)를 찾아 최근 run_id를 식별합니다. 없으면 "이전 실행이 없습니다. /humanize-korean으로 시작하세요" 안내 후 종료.
|
||||
2. 사용자 지시를 파싱합니다:
|
||||
- 카테고리 지정("번역투만", "관용구만") → 해당 카테고리 finding만 재윤문
|
||||
- 문단 지정("이 문단만") → 해당 범위 finding만
|
||||
- 강도 조정("강도 낮춰" → S1만, "강도 높여" → S1+S2+S3)
|
||||
- 지시 없음 → 잔존 finding 전체 대상 round 2
|
||||
3. GEMINI.md의 Humanize Korean 룰에 따라 해당 부분만 재윤문합니다.
|
||||
4. 자체검증 6항을 재점검합니다.
|
||||
5. 결과를 반환합니다 (변경 비교 표 + 신규 등급).
|
||||
|
||||
최대 round 3까지. 그 이상 미해결이면 사람 검토를 권고합니다.
|
||||
"""
|
||||
18
commands/humanize.toml
Normal file
18
commands/humanize.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
prompt = """
|
||||
아래 한글 텍스트를 GEMINI.md에 정의된 Humanize Korean 룰에 따라 윤문하세요.
|
||||
/humanize-korean과 동일한 절차를 따릅니다.
|
||||
|
||||
절차:
|
||||
1. 첫 300자로 장르를 추정합니다 (칼럼 | 리포트 | 블로그 | 공적).
|
||||
2. A~J 카테고리 패턴(S1·S2)을 스캔해 탐지합니다. Do-NOT 대상은 제외합니다.
|
||||
3. D(관용구) → A(번역투) → I(형식명사) → G(hedging) → H(접속사) → F(수식) → B(영어 인용) → C·J(구조·시각) → E(리듬) 순으로 윤문합니다.
|
||||
4. 자체검증 6항을 점검합니다. 위반 시 해당 edit을 롤백하고 부분 재실행합니다(최대 1회).
|
||||
5. 결과를 반환합니다:
|
||||
- 한 줄 상태: `완료. 변경률 X% / 등급 Y / 자체검증 N/6 통과`
|
||||
- 윤문본 본문 (마크다운 블록)
|
||||
- 카테고리별 탐지 건수 before/after + 주요 변경 하이라이트 3~5건
|
||||
- 등급 B 이하면 "정밀 검증이 필요하면 Claude Code의 strict 5인 파이프라인 권장" 안내
|
||||
|
||||
입력 텍스트:
|
||||
{{args}}
|
||||
"""
|
||||
5
gemini-extension.json
Normal file
5
gemini-extension.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "im-not-ai",
|
||||
"version": "1.5.0",
|
||||
"description": "AI가 쓴 한글 텍스트를 사람이 쓴 글처럼 윤문 — Fast(monolith) 모드. 10대 카테고리 40+ AI 티 패턴 탐지·재작성. Gemini CLI Extension."
|
||||
}
|
||||
134
install.sh
Executable file
134
install.sh
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env bash
|
||||
# Humanize KR — Claude Code + Codex CLI + Gemini CLI 전역 설치 스크립트
|
||||
# 저장소를 클론한 뒤 `./install.sh` 한 번이면 설치된 CLI(claude/codex/gemini)를 자동 감지해
|
||||
# humanize-korean 스킬(+ 에이전트)을 전역으로 연결한다. 기본은 심링크(저장소 수정 즉시 반영).
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_HOME="${CLAUDE_HOME:-$HOME/.claude}"
|
||||
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||
|
||||
MODE=symlink # symlink | copy
|
||||
DO_CLAUDE=auto # auto | yes | no
|
||||
DO_CODEX=auto
|
||||
DO_GEMINI=auto
|
||||
FORCE=0
|
||||
DRYRUN=0
|
||||
TS="$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
print_help() {
|
||||
cat <<'H'
|
||||
Usage: ./install.sh [options]
|
||||
|
||||
설치된 CLI를 자동 감지해 humanize-korean 스킬을 전역 설치한다.
|
||||
Claude: ~/.claude/skills/{humanize-korean,humanize,humanize-redo} + ~/.claude/agents/*.md
|
||||
Codex : ~/.codex/skills/humanize-korean
|
||||
Gemini: gemini extensions link (gemini-extension.json + GEMINI.md + commands/)
|
||||
|
||||
Options:
|
||||
--copy 심링크 대신 복사(저장소를 지워도 유지, references 심링크는 실체화).
|
||||
※ 복사본은 uninstall.sh가 자동 삭제하지 않음(수동 삭제).
|
||||
--claude-only Claude만 설치
|
||||
--codex-only Codex만 설치
|
||||
--gemini-only Gemini만 설치
|
||||
--no-gemini Gemini 건너뜀 (claude/codex만)
|
||||
--force 대상에 일반 파일/디렉토리가 있어도 .bak.<ts> 백업 후 덮어씀
|
||||
--dry-run 실제 변경 없이 수행할 작업만 출력
|
||||
-h, --help 이 도움말
|
||||
|
||||
Env overrides: CLAUDE_HOME(기본 ~/.claude), CODEX_HOME(기본 ~/.codex)
|
||||
H
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--copy) MODE=copy ;;
|
||||
--claude-only) DO_CODEX=no; DO_GEMINI=no ;;
|
||||
--codex-only) DO_CLAUDE=no; DO_GEMINI=no ;;
|
||||
--gemini-only) DO_CLAUDE=no; DO_CODEX=no; DO_GEMINI=yes ;;
|
||||
--no-gemini) DO_GEMINI=no ;;
|
||||
--force) FORCE=1 ;;
|
||||
--dry-run) DRYRUN=1 ;;
|
||||
-h|--help) print_help; exit 0 ;;
|
||||
*) echo "unknown arg: $1" >&2; print_help; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
run() { echo "+ $*"; [ "$DRYRUN" = 1 ] || "$@"; }
|
||||
|
||||
# rc: 0=대상 비었음(설치 진행) / 1=이미 우리 심링크(스킵) / 2=충돌(거부)
|
||||
prepare_target() {
|
||||
local dest="$1" src="$2"
|
||||
if [ -L "$dest" ]; then
|
||||
if [ "$(readlink "$dest")" = "$src" ]; then
|
||||
echo "ok (already linked): $dest"; return 1
|
||||
fi
|
||||
run mv "$dest" "$dest.bak.$TS"
|
||||
elif [ -e "$dest" ]; then
|
||||
if [ "$FORCE" != 1 ]; then
|
||||
echo "refuse: $dest 가 이미 있음 (--force 로 백업 후 덮어쓰기 또는 --copy)"; return 2
|
||||
fi
|
||||
run mv "$dest" "$dest.bak.$TS"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
install_one() {
|
||||
local src="$1" dest="$2"
|
||||
run mkdir -p "$(dirname "$dest")"
|
||||
local rc=0
|
||||
prepare_target "$dest" "$src" || rc=$?
|
||||
[ "$rc" = 1 ] && return 0
|
||||
[ "$rc" = 2 ] && return 1
|
||||
case "$MODE" in
|
||||
symlink) run ln -s "$src" "$dest" ;;
|
||||
copy) run cp -RL "$src" "$dest" ;; # -L: references 심링크를 실체로 복사
|
||||
esac
|
||||
echo "installed: $dest"
|
||||
}
|
||||
|
||||
# ---- Claude ----
|
||||
if [ "$DO_CLAUDE" != no ] && { [ "$DO_CLAUDE" = yes ] || command -v claude >/dev/null 2>&1; }; then
|
||||
echo "== Claude Code =="
|
||||
run mkdir -p "$CLAUDE_HOME/skills" "$CLAUDE_HOME/agents"
|
||||
for s in humanize-korean humanize humanize-redo; do
|
||||
install_one "$REPO/.claude/skills/$s" "$CLAUDE_HOME/skills/$s"
|
||||
done
|
||||
for a in "$REPO/agents"/*.md; do
|
||||
install_one "$a" "$CLAUDE_HOME/agents/$(basename "$a")"
|
||||
done
|
||||
else
|
||||
echo "== Claude Code: 건너뜀 (claude 미감지 — 강제하려면 --claude-only) =="
|
||||
fi
|
||||
|
||||
# ---- Codex ----
|
||||
if [ "$DO_CODEX" != no ] && { [ "$DO_CODEX" = yes ] || command -v codex >/dev/null 2>&1; }; then
|
||||
echo "== Codex CLI =="
|
||||
run mkdir -p "$CODEX_HOME/skills"
|
||||
install_one "$REPO/codex/skills/humanize-korean" "$CODEX_HOME/skills/humanize-korean"
|
||||
else
|
||||
echo "== Codex CLI: 건너뜀 (codex 미감지 — 강제하려면 --codex-only) =="
|
||||
fi
|
||||
|
||||
# ---- Gemini CLI ----
|
||||
if [ "$DO_GEMINI" != no ] && { [ "$DO_GEMINI" = yes ] || command -v gemini >/dev/null 2>&1; }; then
|
||||
echo "== Gemini CLI =="
|
||||
if [ "$DRYRUN" = 1 ]; then
|
||||
echo "+ gemini extensions link $REPO (dry-run)"
|
||||
else
|
||||
echo "gemini extensions link \"$REPO\" 실행 (확장 등록)..."
|
||||
echo "Y" | gemini extensions link "$REPO" 2>/dev/null && echo "installed: Gemini extension (im-not-ai)" \
|
||||
|| echo " (이미 등록됨 또는 수동 등록 필요: gemini extensions link $REPO)"
|
||||
fi
|
||||
else
|
||||
echo "== Gemini CLI: 건너뜀 (gemini 미감지 — 강제하려면 --gemini-only) =="
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "완료 (mode=$MODE)."
|
||||
echo " Claude: 새 세션에서 /humanize-korean (또는 /humanize)"
|
||||
echo " Codex : \$humanize-korean"
|
||||
echo " Gemini: 새 세션에서 /humanize-korean (또는 /humanize)"
|
||||
echo " 업데이트: ./update.sh (새 버전 자동 감지 + 적용) · 제거: ./uninstall.sh"
|
||||
exit 0
|
||||
180
scripts/build_social_preview_v2.py
Normal file
180
scripts/build_social_preview_v2.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
"""Build assets/social-preview.png for v2.0 — 한국 번역학계 8유형 흡수.
|
||||
|
||||
기존 v1.1 디자인 톤(베이지 #F4EFE5 · 짙은 녹색 #2D5C3F · 빨강 #C0573F · BEFORE/AFTER
|
||||
2단 분할)을 유지하되, 메시지를 v2.0 신규 패턴(영-한 번역학계 흔적) 3건으로 교체.
|
||||
1280×640 PNG 출력. Pretendard ExtraBold/Bold/SemiBold/Medium/Regular 사용.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Paths & tokens
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ASSETS = ROOT / "assets"
|
||||
OUT = ASSETS / "social-preview.png"
|
||||
|
||||
FONT_DIR = Path.home() / "Library" / "Fonts"
|
||||
F_BLACK = str(FONT_DIR / "Pretendard-Black.otf")
|
||||
F_EBOLD = str(FONT_DIR / "Pretendard-ExtraBold.otf")
|
||||
F_BOLD = str(FONT_DIR / "Pretendard-Bold.otf")
|
||||
F_SEMI = str(FONT_DIR / "Pretendard-SemiBold.otf")
|
||||
F_MED = str(FONT_DIR / "Pretendard-Medium.otf")
|
||||
F_REG = str(FONT_DIR / "Pretendard-Regular.otf")
|
||||
|
||||
# Design tokens
|
||||
BG = "#F4EFE5"
|
||||
TITLE = "#1F2A1F"
|
||||
SUB = "#5C5042"
|
||||
RULE = "#C9BEA9"
|
||||
BEFORE = "#C0573F"
|
||||
AFTER = "#2D5C3F"
|
||||
META = "#7A6E5C"
|
||||
LINK = "#2D5C3F"
|
||||
STRIKE = "#C0573F"
|
||||
|
||||
W, H = 1280, 640
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def font(path: str, size: int) -> ImageFont.FreeTypeFont:
|
||||
return ImageFont.truetype(path, size)
|
||||
|
||||
|
||||
def draw_text(d: ImageDraw.ImageDraw, xy, text: str, f, fill, anchor="la"):
|
||||
d.text(xy, text, font=f, fill=fill, anchor=anchor)
|
||||
|
||||
|
||||
def text_w(f, text: str) -> int:
|
||||
bbox = f.getbbox(text)
|
||||
return bbox[2] - bbox[0]
|
||||
|
||||
|
||||
def draw_strike(d: ImageDraw.ImageDraw, x: int, y: int, length: int, color=STRIKE, width: int = 3):
|
||||
d.line([(x, y), (x + length, y)], fill=color, width=width)
|
||||
|
||||
|
||||
def draw_arrow(d: ImageDraw.ImageDraw, x: int, y: int, color=TITLE, size: int = 18):
|
||||
"""단순 화살표 →"""
|
||||
d.line([(x, y), (x + size, y)], fill=color, width=2)
|
||||
d.line([(x + size - 6, y - 5), (x + size, y), (x + size - 6, y + 5)], fill=color, width=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def build():
|
||||
img = Image.new("RGB", (W, H), BG)
|
||||
d = ImageDraw.Draw(img)
|
||||
|
||||
# Header — 좌상단 제목 + 우상단 부제 2줄
|
||||
f_title = font(F_BLACK, 80)
|
||||
f_sub = font(F_MED, 22)
|
||||
f_sub_em = font(F_SEMI, 22)
|
||||
|
||||
draw_text(d, (72, 70), "im-not-ai", f_title, TITLE)
|
||||
# 우상단 — 한글 AI 티 제거기
|
||||
sub_line1 = "한글 AI 티 제거기"
|
||||
sub_line2 = "v2.0 · 한국 번역학계 8유형 흡수"
|
||||
draw_text(d, (W - 72, 80), sub_line1, f_sub, SUB, anchor="ra")
|
||||
draw_text(d, (W - 72, 112), sub_line2, f_sub_em, AFTER, anchor="ra")
|
||||
|
||||
# Top rule
|
||||
d.line([(72, 178), (W - 72, 178)], fill=RULE, width=2)
|
||||
|
||||
# Section labels
|
||||
f_label = font(F_BOLD, 18)
|
||||
draw_text(d, (72, 198), "BEFORE (AI · 영-한 번역체)", f_label, BEFORE)
|
||||
draw_text(d, (680, 198), "AFTER (자연 한국어)", f_label, AFTER)
|
||||
|
||||
# Three pattern rows: A-16 대명사 / A-19 이중조사 / A-18 관계절
|
||||
f_ex = font(F_SEMI, 26)
|
||||
f_pat = font(F_REG, 14)
|
||||
|
||||
rows = [
|
||||
# (before_text, before_strikes_index_pairs, after_text, label_left, label_right)
|
||||
# strikes: list of (start_idx, end_idx) char ranges to strike
|
||||
(
|
||||
"그는 그의 시계를 보았다.",
|
||||
[(3, 5)], # "그의"
|
||||
"시계를 보았다.",
|
||||
"A-16 영어 대명사 직역",
|
||||
"→ 영형(생략)",
|
||||
),
|
||||
(
|
||||
"긴장으로부터의 해방",
|
||||
[(2, 8)], # "으로부터의"
|
||||
"긴장에서 벗어남",
|
||||
"A-19 이중 조사 결합",
|
||||
"→ 절·구로 풀어쓰기",
|
||||
),
|
||||
(
|
||||
"AI가 학습한 데이터가 보여주는 언어 패턴",
|
||||
[], # 좌향 2중 — 핵 어휘 "패턴"까지 관형구 누적
|
||||
"AI 학습 데이터의 언어 패턴",
|
||||
"A-18 관계절 좌향 수식",
|
||||
"→ 관형구 압축",
|
||||
),
|
||||
]
|
||||
|
||||
y = 240
|
||||
row_gap = 95
|
||||
for i, (b_text, strikes, a_text, lab_l, lab_r) in enumerate(rows):
|
||||
# BEFORE column
|
||||
bx = 72
|
||||
draw_text(d, (bx, y), b_text, f_ex, TITLE)
|
||||
# strike spans (approx — char index → x position via cumulative width)
|
||||
for s, e in strikes:
|
||||
prefix = b_text[:s]
|
||||
target = b_text[s:e]
|
||||
x_start = bx + text_w(f_ex, prefix)
|
||||
x_end = x_start + text_w(f_ex, target)
|
||||
mid_y = y + 18 # roughly mid of glyph
|
||||
draw_strike(d, x_start, mid_y, x_end - x_start, color=STRIKE, width=3)
|
||||
# pattern label below
|
||||
draw_text(d, (bx, y + 36), lab_l, f_pat, BEFORE)
|
||||
|
||||
# arrow
|
||||
draw_arrow(d, 595, y + 18, color=SUB, size=22)
|
||||
|
||||
# AFTER column
|
||||
ax = 640
|
||||
draw_text(d, (ax, y), a_text, f_ex, AFTER)
|
||||
draw_text(d, (ax, y + 36), lab_r, f_pat, AFTER)
|
||||
|
||||
y += row_gap
|
||||
|
||||
# Bottom rule
|
||||
d.line([(72, 540), (W - 72, 540)], fill=RULE, width=2)
|
||||
|
||||
# Bottom meta — 좌측 + 우측
|
||||
f_meta = font(F_SEMI, 16)
|
||||
f_meta_sub = font(F_REG, 13)
|
||||
f_link = font(F_MED, 16)
|
||||
|
||||
# 좌측: 메타
|
||||
draw_text(d, (72, 562), "10 categories · 60+ patterns · v2.0", f_meta, TITLE)
|
||||
draw_text(d, (72, 590), "이근희 · 김정우 · 김도훈 · 김혜영 · Toral 2019", f_meta_sub, META)
|
||||
|
||||
# 우측: github URL
|
||||
draw_text(d, (W - 72, 590), "github.com/epoko77-ai/im-not-ai", f_link, LINK, anchor="ra")
|
||||
|
||||
# Save
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
img.save(OUT, format="PNG", optimize=True)
|
||||
print(f"saved: {OUT} ({W}×{H})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
|
|
@ -76,41 +76,37 @@ label_y = 218
|
|||
d.text((PAD, label_y), "BEFORE (AI)", font=label_f, fill=STRIKE)
|
||||
d.text((W // 2 + 40, label_y), "AFTER (사람)", font=label_f, fill=AFTER)
|
||||
|
||||
# Row 1
|
||||
# Row 1 — 수동태
|
||||
y1 = 260
|
||||
# BEFORE line
|
||||
x = PAD
|
||||
x = plain(x, y1, "AI 기술을 ", body_f)
|
||||
x = strike_text(x, y1, "통해", body_f)
|
||||
x = plain(x, y1, " 효율을 높", body_f)
|
||||
x = strike_text(x, y1, "일 수 있다", body_f)
|
||||
x = plain(x, y1, "지속될 것으로 ", body_f)
|
||||
x = strike_text(x, y1, "보여진다", body_f)
|
||||
x = plain(x, y1, ".", body_f)
|
||||
|
||||
# arrow in column gap
|
||||
arrow_x = W // 2 - 30
|
||||
d.text((arrow_x, y1 - 2), "→", font=F("bold", 40), fill=MUTED)
|
||||
|
||||
# AFTER line
|
||||
plain(W // 2 + 40, y1, "AI 기술로 효율을 높인다", body_f, color=AFTER)
|
||||
plain(W // 2 + 40, y1, "계속될 것이다.", body_f, color=AFTER)
|
||||
|
||||
# Row 2
|
||||
# Row 2 — 대구 (단순한 X가 아닌 Y)
|
||||
y2 = 332
|
||||
x = PAD
|
||||
x = strike_text(x, y2, "결론적으로", body_f)
|
||||
x = plain(x, y2, ", 이는 ", body_f)
|
||||
x = strike_text(x, y2, "시사하는 바가 크다", body_f)
|
||||
x = strike_text(x, y2, "단순한", body_f)
|
||||
x = plain(x, y2, " 트렌드가 ", body_f)
|
||||
x = strike_text(x, y2, "아닌", body_f)
|
||||
x = plain(x, y2, " 흐름이다.", body_f)
|
||||
|
||||
d.text((arrow_x, y2 - 2), "→", font=F("bold", 40), fill=MUTED)
|
||||
plain(W // 2 + 40, y2, "의미는 분명하다", body_f, color=AFTER)
|
||||
plain(W // 2 + 40, y2, "시대가 바뀌고 있다.", body_f, color=AFTER)
|
||||
|
||||
# Row 3
|
||||
# Row 3 — 지시대명사 (이것은/그것은)
|
||||
y3 = 404
|
||||
x = PAD
|
||||
x = strike_text(x, y3, "~에 있어서", body_f)
|
||||
x = plain(x, y3, " ", body_f)
|
||||
x = strike_text(x, y3, "판단되어진다", body_f)
|
||||
x = strike_text(x, y3, "이것은", body_f)
|
||||
x = plain(x, y3, " 변화의 신호이다.", body_f)
|
||||
|
||||
d.text((arrow_x, y3 - 2), "→", font=F("bold", 40), fill=MUTED)
|
||||
plain(W // 2 + 40, y3, "~에서, 판단한다", body_f, color=AFTER)
|
||||
plain(W // 2 + 40, y3, "변화가 시작됐다.", body_f, color=AFTER)
|
||||
|
||||
|
||||
# ---------- Stats row ----------
|
||||
|
|
|
|||
255
scripts/prepare_monolith_input.py
Normal file
255
scripts/prepare_monolith_input.py
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Humanize KR v1.6 — monolith input shim.
|
||||
|
||||
Pre-processes user input by computing v1.6 quantitative metrics and
|
||||
prepending the result to the text the monolith agent reads. The monolith
|
||||
keeps its 4-tool-call cap (Read input + Read rules + Write final + Write
|
||||
summary) because the metrics block is folded into the same input file.
|
||||
|
||||
Inputs:
|
||||
--run-dir DIR existing run directory containing 01_input.txt
|
||||
--text STR ad-hoc text; if --run-dir is omitted, a new run dir
|
||||
`_workspace/<YYYY-MM-DD>-NNN/` is created and 01_input.txt
|
||||
written.
|
||||
--genre STR essay|column|report|blog|abstract|... (default: essay)
|
||||
|
||||
Outputs (in {run_dir}):
|
||||
00_metrics.json — full compute_all() output (or error stub)
|
||||
01_input.txt — original text (created if --text used)
|
||||
01_input_with_metrics.txt — combined file the monolith Reads
|
||||
00_metrics.error — only on graceful-degrade fallback
|
||||
|
||||
Hard rules:
|
||||
- stdlib only (argparse/json/os/sys/datetime/pathlib/traceback)
|
||||
- never modify the original text body inside the combined file
|
||||
- on metrics failure, write the combined file *without* the score block
|
||||
so the monolith degrades to v1.5 behaviour automatically.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
# Resolve project layout. This file lives at:
|
||||
# {project_root}/scripts/prepare_monolith_input.py
|
||||
# metrics.py is at:
|
||||
# {project_root}/.claude/skills/humanize-korean/references/metrics.py
|
||||
HERE = Path(__file__).resolve().parent
|
||||
PROJECT_ROOT = HERE.parent
|
||||
METRICS_DIR = PROJECT_ROOT / ".claude" / "skills" / "humanize-korean" / "references"
|
||||
|
||||
# Make metrics.py importable without polluting global state.
|
||||
sys.path.insert(0, str(METRICS_DIR))
|
||||
# v2.0 우선 import — compute_all 별칭으로 v1.6 호환. metrics_v2 부재·로드 실패 시
|
||||
# v1.6 metrics fallback. graceful degrade로 monolith 동작은 항상 보장.
|
||||
try:
|
||||
import metrics_v2 as _metrics_mod # type: ignore # v2.0 (post-editese 14 metric)
|
||||
except Exception: # pragma: no cover
|
||||
try:
|
||||
import metrics as _metrics_mod # type: ignore # v1.6 fallback
|
||||
except Exception:
|
||||
_metrics_mod = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Run directory discovery / creation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _next_run_dir(workspace: Path) -> Path:
|
||||
"""Allocate _workspace/<today>-NNN/ with the smallest free NNN."""
|
||||
workspace.mkdir(parents=True, exist_ok=True)
|
||||
today = date.today().isoformat()
|
||||
n = 1
|
||||
while True:
|
||||
candidate = workspace / f"{today}-{n:03d}"
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
n += 1
|
||||
|
||||
|
||||
def _resolve_run_dir(run_dir_arg: str | None, text_arg: str | None) -> Path:
|
||||
if run_dir_arg:
|
||||
rd = Path(run_dir_arg)
|
||||
if not rd.is_absolute():
|
||||
rd = PROJECT_ROOT / rd
|
||||
rd.mkdir(parents=True, exist_ok=True)
|
||||
return rd
|
||||
if text_arg is None:
|
||||
raise SystemExit("Either --run-dir or --text is required")
|
||||
workspace = PROJECT_ROOT / "_workspace"
|
||||
rd = _next_run_dir(workspace)
|
||||
rd.mkdir(parents=True, exist_ok=True)
|
||||
return rd
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Combined-file rendering
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _fmt_z(z: float | None) -> str:
|
||||
if z is None:
|
||||
return "n/a"
|
||||
sign = "+" if z >= 0 else ""
|
||||
return f"z={sign}{z:.2f}"
|
||||
|
||||
|
||||
def _z_marker(z: float | None) -> str:
|
||||
"""Emit a small star for values clearly above the AI band."""
|
||||
if z is None:
|
||||
return ""
|
||||
if z >= 1.5:
|
||||
return " ★ S1 트리거"
|
||||
if z >= 1.0:
|
||||
return " · S2 시그널"
|
||||
return ""
|
||||
|
||||
|
||||
def _render_block(metrics_obj: dict) -> str:
|
||||
m = metrics_obj.get("metrics", {})
|
||||
z = metrics_obj.get("z_scores", {})
|
||||
ev = metrics_obj.get("evidence", {})
|
||||
pivots = ev.get("conclusion_pivots") or []
|
||||
safe = ev.get("safe_balances") or []
|
||||
|
||||
lines: list[str] = []
|
||||
lines.append("[정량 사전 점수 v1.6 / KatFish baseline]")
|
||||
lines.append(
|
||||
f"risk_band: {metrics_obj.get('risk_band', 'unknown')} "
|
||||
f"(score {metrics_obj.get('risk_score', 0)})"
|
||||
)
|
||||
lines.append(f"genre: {metrics_obj.get('genre', 'essay')}")
|
||||
lines.append(f"char_count: {metrics_obj.get('char_count', 0)}")
|
||||
if metrics_obj.get("warning"):
|
||||
lines.append(f"warning: {metrics_obj['warning']}")
|
||||
lines.append("")
|
||||
lines.append("[지표]")
|
||||
|
||||
def row(key: str, value_fmt: str, with_z: bool = True, suffix: str = "") -> str:
|
||||
val = m.get(key)
|
||||
if val is None:
|
||||
return f"- {key}: n/a"
|
||||
z_part = ""
|
||||
if with_z:
|
||||
z_part = f" ({_fmt_z(z.get(key))} vs {metrics_obj.get('genre','essay')} 인간 baseline){_z_marker(z.get(key))}"
|
||||
return f"- {key}: {value_fmt.format(val)}{z_part}{suffix}"
|
||||
|
||||
lines.append(row("comma_inclusion_rate", "{:.2f}"))
|
||||
lines.append(row("comma_usage_rate", "{:.2f}"))
|
||||
lines.append(row("ending_comma_rate", "{:.2f}"))
|
||||
lines.append(row("comma_segment_length", "{:.2f}"))
|
||||
|
||||
pivot_suffix = f" (lexicon 매치: {', '.join(repr(p) for p in pivots)})" if pivots else ""
|
||||
lines.append(
|
||||
f"- conclusion_pivot_count: {int(m.get('conclusion_pivot_count', 0))}{pivot_suffix}"
|
||||
)
|
||||
safe_suffix = f" (lexicon 매치: {', '.join(repr(s) for s in safe)})" if safe else ""
|
||||
lines.append(
|
||||
f"- safe_balance_count: {int(m.get('safe_balance_count', 0))}{safe_suffix}"
|
||||
)
|
||||
lines.append(row("hanja_nominalizer_density", "{:.3f}"))
|
||||
lines.append(row("lexical_diversity", "{:.2f}"))
|
||||
lines.append("")
|
||||
lines.append("[근거 사용 가이드]")
|
||||
lines.append("- 위 점수는 *근거 보조*다. 단독 판정 금지(보고서 명시).")
|
||||
lines.append("- z>1.0 지표는 quick-rules.md S1·S2 패턴과 교차 확인 후 윤문할 것.")
|
||||
lines.append("- ending_comma_rate가 ★ S1 트리거인 경우 C-11(연결어미 뒤 쉼표) 우선 손질.")
|
||||
lines.append("- conclusion_pivot 매치 토큰은 D-1·H-1 처방 적용 대상.")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _render_combined(text: str, metrics_obj: dict | None) -> str:
|
||||
parts: list[str] = []
|
||||
if metrics_obj is not None:
|
||||
parts.append(_render_block(metrics_obj))
|
||||
parts.append("[원문 시작]")
|
||||
parts.append(text.rstrip("\n"))
|
||||
parts.append("[원문 끝]")
|
||||
parts.append("")
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
p = argparse.ArgumentParser(description="Humanize KR v1.6 monolith input shim")
|
||||
p.add_argument("--run-dir", help="Existing run directory (relative ok)")
|
||||
p.add_argument("--text", help="Inline text input (creates new run dir)")
|
||||
p.add_argument("--genre", default="essay", help="Genre hint (default: essay)")
|
||||
p.add_argument(
|
||||
"--baseline",
|
||||
default=None,
|
||||
help="Override baseline JSON path (default: project default)",
|
||||
)
|
||||
args = p.parse_args(argv)
|
||||
|
||||
run_dir = _resolve_run_dir(args.run_dir, args.text)
|
||||
input_path = run_dir / "01_input.txt"
|
||||
|
||||
# Ensure 01_input.txt exists.
|
||||
if args.text is not None:
|
||||
input_path.write_text(args.text, encoding="utf-8")
|
||||
if not input_path.exists():
|
||||
raise SystemExit(f"01_input.txt not found in {run_dir}; pass --text to create")
|
||||
|
||||
text = input_path.read_text(encoding="utf-8")
|
||||
|
||||
metrics_obj: dict | None = None
|
||||
metrics_path = run_dir / "00_metrics.json"
|
||||
error_path = run_dir / "00_metrics.error"
|
||||
|
||||
if _metrics_mod is None:
|
||||
error_path.write_text(
|
||||
"metrics module import failed; combined file emitted without score block",
|
||||
encoding="utf-8",
|
||||
)
|
||||
else:
|
||||
try:
|
||||
metrics_obj = _metrics_mod.compute_all(
|
||||
text, genre=args.genre, baseline_path=args.baseline
|
||||
)
|
||||
metrics_path.write_text(
|
||||
json.dumps(metrics_obj, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
# On success any stale error file is cleared.
|
||||
if error_path.exists():
|
||||
try:
|
||||
error_path.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
except Exception as exc: # noqa: BLE001 — graceful degrade is the point.
|
||||
metrics_obj = None
|
||||
error_path.write_text(
|
||||
f"metrics_failed: {type(exc).__name__}: {exc}\n\n"
|
||||
+ traceback.format_exc(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
combined_path = run_dir / "01_input_with_metrics.txt"
|
||||
combined_path.write_text(_render_combined(text, metrics_obj), encoding="utf-8")
|
||||
|
||||
rb = (metrics_obj or {}).get("risk_band", "absent")
|
||||
rs = (metrics_obj or {}).get("risk_score", "absent")
|
||||
print(
|
||||
f"run_dir={run_dir}\n"
|
||||
f"combined={combined_path}\n"
|
||||
f"risk_band={rb} risk_score={rs}\n"
|
||||
f"degraded={metrics_obj is None}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
169
tests/test_metrics.py
Normal file
169
tests/test_metrics.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
"""Tests for humanize-ko v1.6 metrics module.
|
||||
|
||||
Runs under either pytest or unittest. Imports the metrics module from its
|
||||
location under .claude/skills/humanize-korean/references/.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(HERE, ".."))
|
||||
METRICS_DIR = os.path.join(
|
||||
PROJECT_ROOT, ".claude", "skills", "humanize-korean", "references"
|
||||
)
|
||||
sys.path.insert(0, METRICS_DIR)
|
||||
|
||||
import metrics # noqa: E402 (sys.path mutation is intentional)
|
||||
|
||||
BASELINE_PATH = os.path.join(
|
||||
PROJECT_ROOT, "_workspace", "v1.6-2026-05-06", "02_katfish_baseline.json"
|
||||
)
|
||||
|
||||
|
||||
class MetricsTests(unittest.TestCase):
|
||||
# ------------------------------------------------------------------
|
||||
# Robustness
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_empty_string_is_safe(self) -> None:
|
||||
self.assertEqual(metrics.comma_inclusion_rate(""), 0.0)
|
||||
self.assertEqual(metrics.comma_usage_rate(""), 0.0)
|
||||
self.assertEqual(metrics.ending_comma_rate(""), 0.0)
|
||||
self.assertEqual(metrics.comma_segment_length(""), 0.0)
|
||||
self.assertEqual(metrics.conclusion_pivot_count(""), 0)
|
||||
self.assertEqual(metrics.safe_balance_count(""), 0)
|
||||
self.assertEqual(metrics.hanja_nominalizer_density(""), 0.0)
|
||||
self.assertEqual(metrics.lexical_diversity(""), 0.0)
|
||||
|
||||
def test_single_sentence(self) -> None:
|
||||
text = "오늘은 비가 온다."
|
||||
self.assertEqual(metrics.comma_inclusion_rate(text), 0.0)
|
||||
self.assertEqual(metrics.comma_usage_rate(text), 0.0)
|
||||
self.assertGreater(metrics.lexical_diversity(text), 0.0)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Connective ending + comma
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_ending_comma_pattern_detection(self) -> None:
|
||||
# 5 connective endings, all followed by ", " => rate = 1.0
|
||||
text = (
|
||||
"그는 일어나고, 세수했고, 옷을 입었으며, "
|
||||
"밥을 먹지만, 곧 잠들었다."
|
||||
)
|
||||
rate = metrics.ending_comma_rate(text)
|
||||
self.assertGreater(rate, 0.5)
|
||||
|
||||
def test_ending_no_comma(self) -> None:
|
||||
text = "그는 일어나고 세수했고 옷을 입었다."
|
||||
rate = metrics.ending_comma_rate(text)
|
||||
self.assertEqual(rate, 0.0)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Lexicon counts
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_conclusion_pivot_lexicon(self) -> None:
|
||||
text = "결론적으로 우리는 이겼다. 따라서 다음에도 이긴다. 이를 통해 자신감을 얻었다."
|
||||
self.assertEqual(metrics.conclusion_pivot_count(text), 3)
|
||||
|
||||
def test_safe_balance_lexicon(self) -> None:
|
||||
text = "양쪽 모두 일리가 있다. 장점도 있지만 단점도 있다. 신중하게 결정해야 한다."
|
||||
self.assertEqual(metrics.safe_balance_count(text), 3)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Hanja suffix density
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_hanja_suffix_counted(self) -> None:
|
||||
text = "기술적 측면에서 안정성과 효율성, 그리고 자동화는 중요하다."
|
||||
density = metrics.hanja_nominalizer_density(text)
|
||||
# Tokens (after punct strip): 기술적 측면에서 안정성과 효율성 그리고 자동화는 중요하다
|
||||
# Hits: 기술적(적), 안정성과(과 -> not suffix; ends with 과 not target)
|
||||
# Actually 안정성과 ends with 과, so NOT counted. Let's just assert >0.
|
||||
self.assertGreater(density, 0.0)
|
||||
|
||||
def test_hanja_zero_density(self) -> None:
|
||||
text = "오늘 비가 온다 우산이 필요하다 빨리 가자"
|
||||
density = metrics.hanja_nominalizer_density(text)
|
||||
self.assertEqual(density, 0.0)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Baseline fallback
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_baseline_null_genre_falls_back(self) -> None:
|
||||
text = "오늘은 좋은 날이다."
|
||||
result = metrics.compute_all(text, genre="news", baseline_path=BASELINE_PATH)
|
||||
# news is null in baseline => fallback warning expected.
|
||||
self.assertIn("warning", result)
|
||||
self.assertIn("news", result["warning"])
|
||||
|
||||
def test_baseline_essay_no_warning(self) -> None:
|
||||
text = "오늘은 좋은 날이다."
|
||||
result = metrics.compute_all(text, genre="essay", baseline_path=BASELINE_PATH)
|
||||
self.assertNotIn("warning", result)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# End-to-end risk band
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_ai_style_text_is_high_risk(self) -> None:
|
||||
# Heavy comma usage + ending-comma + conclusion pivots + safe balance
|
||||
# + hanja suffixes.
|
||||
text = (
|
||||
"현대 사회에서 기술적 혁신은 중요하다. "
|
||||
"AI는 빠르게 발전하고, 산업은 변화하며, 사람들은 적응해야 한다. "
|
||||
"결론적으로, 우리는 양쪽 모두를 고려해야 한다. "
|
||||
"따라서, 자동화와 안정성, 효율성, 그리고 지속가능성을 신중하게 검토해야 한다. "
|
||||
"이를 통해 사회적 균형과 기술적 진보를 함께 달성할 수 있다. "
|
||||
"그러므로 두 가지 모두 신중하게 다루어야 한다."
|
||||
)
|
||||
result = metrics.compute_all(text, genre="essay", baseline_path=BASELINE_PATH)
|
||||
self.assertEqual(result["risk_band"], "high")
|
||||
self.assertGreaterEqual(result["metrics"]["conclusion_pivot_count"], 2)
|
||||
self.assertGreaterEqual(result["metrics"]["safe_balance_count"], 2)
|
||||
|
||||
def test_human_style_text_is_low_risk(self) -> None:
|
||||
# Short sentences. No commas. No conclusion pivots. No safe balance.
|
||||
# No hanja suffix nominalizers.
|
||||
text = (
|
||||
"오늘 비가 왔다. 우산을 폈다. 길이 미끄럽다. "
|
||||
"버스에 탔다. 사람들이 많다. 빨리 가고 싶다."
|
||||
)
|
||||
result = metrics.compute_all(text, genre="essay", baseline_path=BASELINE_PATH)
|
||||
self.assertEqual(result["risk_band"], "low")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# CLI smoke
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_cli_writes_json_and_prints_band(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
in_path = os.path.join(td, "input.txt")
|
||||
out_path = os.path.join(td, "out.json")
|
||||
with open(in_path, "w", encoding="utf-8") as f:
|
||||
f.write("오늘 비가 왔다. 우산을 폈다.")
|
||||
rc = metrics._main(
|
||||
[
|
||||
"--input", in_path,
|
||||
"--genre", "essay",
|
||||
"--output", out_path,
|
||||
"--baseline", BASELINE_PATH,
|
||||
]
|
||||
)
|
||||
self.assertEqual(rc, 0)
|
||||
with open(out_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
self.assertEqual(data["version"], "v1.6")
|
||||
self.assertIn(data["risk_band"], ("low", "medium", "high"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
361
tests/test_metrics_v2.py
Normal file
361
tests/test_metrics_v2.py
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
"""Tests for humanize-ko v2.0 metrics module.
|
||||
|
||||
Runs under pytest OR `python -m unittest`. Imports both the v1.6 metrics
|
||||
(for regression checks) and the v2.0 metrics_v2 from this workspace.
|
||||
|
||||
Spec:
|
||||
- Existing 13 v1.6 cases: re-run verbatim. Regression budget = 0.
|
||||
- New v2.0 cases: each new metric has >= 2 cases (positive + negative).
|
||||
Total new tests >= 20.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(HERE, "..", "..", ".."))
|
||||
|
||||
# v1.6 module location
|
||||
V1_DIR = os.path.join(
|
||||
PROJECT_ROOT, ".claude", "skills", "humanize-korean", "references"
|
||||
)
|
||||
sys.path.insert(0, V1_DIR)
|
||||
sys.path.insert(0, HERE)
|
||||
|
||||
import metrics # noqa: E402 (v1.6)
|
||||
import metrics_v2 # noqa: E402 (v2.0 superset)
|
||||
|
||||
BASELINE_PATH = os.path.join(
|
||||
PROJECT_ROOT, "_workspace", "v1.6-2026-05-06", "02_katfish_baseline.json"
|
||||
)
|
||||
BASELINE_V2_PATH = os.path.join(HERE, "baseline_v2_diff.json")
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# REGRESSION — v1.6 13 cases re-asserted on metrics_v2 re-exports
|
||||
# ===========================================================================
|
||||
|
||||
|
||||
class V16RegressionTests(unittest.TestCase):
|
||||
"""v1.6 13 pytest cases — must all pass against metrics_v2 re-exports."""
|
||||
|
||||
def test_empty_string_is_safe(self) -> None:
|
||||
self.assertEqual(metrics_v2.comma_inclusion_rate(""), 0.0)
|
||||
self.assertEqual(metrics_v2.comma_usage_rate(""), 0.0)
|
||||
self.assertEqual(metrics_v2.ending_comma_rate(""), 0.0)
|
||||
self.assertEqual(metrics_v2.comma_segment_length(""), 0.0)
|
||||
self.assertEqual(metrics_v2.conclusion_pivot_count(""), 0)
|
||||
self.assertEqual(metrics_v2.safe_balance_count(""), 0)
|
||||
self.assertEqual(metrics_v2.hanja_nominalizer_density(""), 0.0)
|
||||
self.assertEqual(metrics_v2.lexical_diversity(""), 0.0)
|
||||
|
||||
def test_single_sentence(self) -> None:
|
||||
text = "오늘은 비가 온다."
|
||||
self.assertEqual(metrics_v2.comma_inclusion_rate(text), 0.0)
|
||||
self.assertEqual(metrics_v2.comma_usage_rate(text), 0.0)
|
||||
self.assertGreater(metrics_v2.lexical_diversity(text), 0.0)
|
||||
|
||||
def test_ending_comma_pattern_detection(self) -> None:
|
||||
text = (
|
||||
"그는 일어나고, 세수했고, 옷을 입었으며, "
|
||||
"밥을 먹지만, 곧 잠들었다."
|
||||
)
|
||||
self.assertGreater(metrics_v2.ending_comma_rate(text), 0.5)
|
||||
|
||||
def test_ending_no_comma(self) -> None:
|
||||
text = "그는 일어나고 세수했고 옷을 입었다."
|
||||
self.assertEqual(metrics_v2.ending_comma_rate(text), 0.0)
|
||||
|
||||
def test_conclusion_pivot_lexicon(self) -> None:
|
||||
text = (
|
||||
"결론적으로 우리는 이겼다. 따라서 다음에도 이긴다. "
|
||||
"이를 통해 자신감을 얻었다."
|
||||
)
|
||||
self.assertEqual(metrics_v2.conclusion_pivot_count(text), 3)
|
||||
|
||||
def test_safe_balance_lexicon(self) -> None:
|
||||
text = "양쪽 모두 일리가 있다. 장점도 있지만 단점도 있다. 신중하게 결정해야 한다."
|
||||
self.assertEqual(metrics_v2.safe_balance_count(text), 3)
|
||||
|
||||
def test_hanja_suffix_counted(self) -> None:
|
||||
text = "기술적 측면에서 안정성과 효율성, 그리고 자동화는 중요하다."
|
||||
self.assertGreater(metrics_v2.hanja_nominalizer_density(text), 0.0)
|
||||
|
||||
def test_hanja_zero_density(self) -> None:
|
||||
text = "오늘 비가 온다 우산이 필요하다 빨리 가자"
|
||||
self.assertEqual(metrics_v2.hanja_nominalizer_density(text), 0.0)
|
||||
|
||||
def test_baseline_null_genre_falls_back(self) -> None:
|
||||
text = "오늘은 좋은 날이다."
|
||||
result = metrics.compute_all(text, genre="news", baseline_path=BASELINE_PATH)
|
||||
self.assertIn("warning", result)
|
||||
self.assertIn("news", result["warning"])
|
||||
|
||||
def test_baseline_essay_no_warning(self) -> None:
|
||||
text = "오늘은 좋은 날이다."
|
||||
result = metrics.compute_all(text, genre="essay", baseline_path=BASELINE_PATH)
|
||||
self.assertNotIn("warning", result)
|
||||
|
||||
def test_ai_style_text_is_high_risk(self) -> None:
|
||||
text = (
|
||||
"현대 사회에서 기술적 혁신은 중요하다. "
|
||||
"AI는 빠르게 발전하고, 산업은 변화하며, 사람들은 적응해야 한다. "
|
||||
"결론적으로, 우리는 양쪽 모두를 고려해야 한다. "
|
||||
"따라서, 자동화와 안정성, 효율성, 그리고 지속가능성을 신중하게 검토해야 한다. "
|
||||
"이를 통해 사회적 균형과 기술적 진보를 함께 달성할 수 있다. "
|
||||
"그러므로 두 가지 모두 신중하게 다루어야 한다."
|
||||
)
|
||||
result = metrics.compute_all(text, genre="essay", baseline_path=BASELINE_PATH)
|
||||
self.assertEqual(result["risk_band"], "high")
|
||||
self.assertGreaterEqual(result["metrics"]["conclusion_pivot_count"], 2)
|
||||
self.assertGreaterEqual(result["metrics"]["safe_balance_count"], 2)
|
||||
|
||||
def test_human_style_text_is_low_risk(self) -> None:
|
||||
text = (
|
||||
"오늘 비가 왔다. 우산을 폈다. 길이 미끄럽다. "
|
||||
"버스에 탔다. 사람들이 많다. 빨리 가고 싶다."
|
||||
)
|
||||
result = metrics.compute_all(text, genre="essay", baseline_path=BASELINE_PATH)
|
||||
self.assertEqual(result["risk_band"], "low")
|
||||
|
||||
def test_cli_writes_json_and_prints_band(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
in_path = os.path.join(td, "input.txt")
|
||||
out_path = os.path.join(td, "out.json")
|
||||
with open(in_path, "w", encoding="utf-8") as f:
|
||||
f.write("오늘 비가 왔다. 우산을 폈다.")
|
||||
rc = metrics._main(
|
||||
[
|
||||
"--input", in_path,
|
||||
"--genre", "essay",
|
||||
"--output", out_path,
|
||||
"--baseline", BASELINE_PATH,
|
||||
]
|
||||
)
|
||||
self.assertEqual(rc, 0)
|
||||
with open(out_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
self.assertEqual(data["version"], "v1.6")
|
||||
self.assertIn(data["risk_band"], ("low", "medium", "high"))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# v2.0 NEW METRIC TESTS (>= 20)
|
||||
# ===========================================================================
|
||||
|
||||
|
||||
class V20SimplificationTests(unittest.TestCase):
|
||||
"""Group A: simplification axis."""
|
||||
|
||||
def test_lexical_diversity_ttr_diverse(self) -> None:
|
||||
text = "산은 푸르다. 바다는 깊다. 하늘은 맑다. 별은 빛난다."
|
||||
self.assertGreater(metrics_v2.lexical_diversity_ttr(text), 0.7)
|
||||
|
||||
def test_lexical_diversity_ttr_repetitive(self) -> None:
|
||||
text = "이것은 책이다. 이것은 책이다. 이것은 책이다. 이것은 책이다."
|
||||
self.assertLess(metrics_v2.lexical_diversity_ttr(text), 0.5)
|
||||
|
||||
def test_lexical_density_high_with_content(self) -> None:
|
||||
text = "혁신성과 효율성, 안정성이 중요하다. 자동화는 필요하다."
|
||||
# tokens include 혁신성과 효율성 안정성이 중요하다 자동화는 필요하다 — many content
|
||||
self.assertGreater(metrics_v2.lexical_density(text), 0.3)
|
||||
|
||||
def test_lexical_density_low_function_heavy(self) -> None:
|
||||
text = "그리고 그러나 또는 즉 예를 그러므로 따라서"
|
||||
self.assertLess(metrics_v2.lexical_density(text), 0.1)
|
||||
|
||||
def test_ending_diversity_high(self) -> None:
|
||||
text = "비가 온다. 옷이 젖었어. 우산을 펼까? 빨리 가자!"
|
||||
self.assertGreaterEqual(metrics_v2.ending_diversity(text), 0.5)
|
||||
|
||||
def test_ending_diversity_low(self) -> None:
|
||||
text = "결정한다. 분석한다. 평가한다. 검토한다. 판단한다."
|
||||
# all "한다" → ending key = '한다' for all → diversity = 1/5
|
||||
self.assertLess(metrics_v2.ending_diversity(text), 0.5)
|
||||
|
||||
|
||||
class V20NormalisationTests(unittest.TestCase):
|
||||
"""Group B: normalisation axis."""
|
||||
|
||||
def test_normalisation_score_high(self) -> None:
|
||||
text = (
|
||||
"이것은 사실이다. 결과가 도출된다. 분석을 시행한다. "
|
||||
"성능이 향상된다. 효과가 기대된다."
|
||||
)
|
||||
# all sentences end with -이다 / -된다 / -한다
|
||||
self.assertGreaterEqual(metrics_v2.normalisation_score(text), 0.8)
|
||||
|
||||
def test_normalisation_score_low(self) -> None:
|
||||
text = (
|
||||
"비가 와요. 우산 챙겼어? 길이 미끄러워요. 조심하세요. 늦지 마!"
|
||||
)
|
||||
# informal endings — none of -한다/-된다/-이다
|
||||
self.assertEqual(metrics_v2.normalisation_score(text), 0.0)
|
||||
|
||||
def test_da_streak_rate_long_run(self) -> None:
|
||||
text = (
|
||||
"혁신은 중요하다. 변화는 빠르다. 시장은 성장한다. 기업은 적응한다. "
|
||||
"사회는 발전한다. 미래는 밝다."
|
||||
)
|
||||
# 6 consecutive '-다' sentences = 1 streak run
|
||||
self.assertGreaterEqual(metrics_v2.da_streak_rate(text), 1)
|
||||
|
||||
def test_da_streak_rate_no_streak(self) -> None:
|
||||
text = "비가 와. 우산 챙겼어? 늦었네."
|
||||
self.assertEqual(metrics_v2.da_streak_rate(text), 0)
|
||||
|
||||
|
||||
class V20InterferenceTests(unittest.TestCase):
|
||||
"""Group C: T1~T8 detection signals."""
|
||||
|
||||
# T1
|
||||
def test_inanimate_subject_rate_high(self) -> None:
|
||||
text = (
|
||||
"연구는 중요한 사실을 보여준다. "
|
||||
"데이터는 새로운 추세를 시사한다. "
|
||||
"분석은 흥미로운 결과를 드러낸다."
|
||||
)
|
||||
self.assertGreater(metrics_v2.inanimate_subject_rate(text), 0.5)
|
||||
|
||||
def test_inanimate_subject_rate_low(self) -> None:
|
||||
text = "철수가 학교에 갔다. 영희가 책을 읽었다. 우리가 함께 놀았다."
|
||||
self.assertEqual(metrics_v2.inanimate_subject_rate(text), 0.0)
|
||||
|
||||
# T2a
|
||||
def test_by_passive_high(self) -> None:
|
||||
text = (
|
||||
"이 문제는 위원회에 의해 처리되었다. "
|
||||
"결정은 정부에 의해 내려진다. "
|
||||
"보고서는 연구진에 의해 작성되었다."
|
||||
)
|
||||
self.assertGreaterEqual(metrics_v2.by_passive_count(text), 2)
|
||||
|
||||
def test_by_passive_zero(self) -> None:
|
||||
text = "위원회가 이 문제를 처리했다. 정부가 결정을 내렸다."
|
||||
self.assertEqual(metrics_v2.by_passive_count(text), 0)
|
||||
|
||||
# T2b
|
||||
def test_double_passive_detected(self) -> None:
|
||||
text = "이 문제는 분석되어진다. 그 사실은 잊혀진 지 오래다."
|
||||
self.assertGreaterEqual(metrics_v2.double_passive_count(text), 2)
|
||||
|
||||
def test_double_passive_zero(self) -> None:
|
||||
text = "이 문제는 분석된다. 그 사실은 잊혀지지 않았다고 보지만, 잊혔다."
|
||||
# we deliberately avoid double-passive surface forms
|
||||
text = "이 문제는 분석된다. 그 사실은 잊혔다."
|
||||
self.assertEqual(metrics_v2.double_passive_count(text), 0)
|
||||
|
||||
# T3
|
||||
def test_pronoun_density_high(self) -> None:
|
||||
text = "메리는 그녀가 그녀를 그리워해서 그녀의 어머니에게 전화했다."
|
||||
self.assertGreater(metrics_v2.pronoun_density(text), 0.10)
|
||||
|
||||
def test_pronoun_density_low(self) -> None:
|
||||
text = "메리는 어머니가 그리워서 전화를 걸었다."
|
||||
self.assertLess(metrics_v2.pronoun_density(text), 0.05)
|
||||
|
||||
# T4
|
||||
def test_deul_overuse_abstract(self) -> None:
|
||||
text = "데이터들과 정보들과 결과들이 아이디어들을 보여준다."
|
||||
# 4 hits over a small token total — should clear 0.3
|
||||
self.assertGreater(metrics_v2.deul_overuse_rate(text), 0.3)
|
||||
|
||||
def test_deul_overuse_zero(self) -> None:
|
||||
text = "데이터와 정보, 그리고 여러 결과가 새로운 생각을 보여준다."
|
||||
self.assertEqual(metrics_v2.deul_overuse_rate(text), 0.0)
|
||||
|
||||
# T5
|
||||
def test_relative_clause_nesting_deep(self) -> None:
|
||||
text = (
|
||||
"그는 사고를 일으킨 화학물질을 생산한 회사에서 한때 일했던 한 남자를 만났다."
|
||||
)
|
||||
# adnominal endings: 일으킨 / 생산한 / 일했던 / 한 → >= 3
|
||||
self.assertGreaterEqual(metrics_v2.relative_clause_nesting(text), 1)
|
||||
|
||||
def test_relative_clause_nesting_shallow(self) -> None:
|
||||
text = "그는 한 남자를 만났다. 그 남자는 회사원이었다."
|
||||
self.assertEqual(metrics_v2.relative_clause_nesting(text), 0)
|
||||
|
||||
# T6
|
||||
def test_have_make_literal_high(self) -> None:
|
||||
text = (
|
||||
"그녀는 좋은 목소리를 가지고 있다. "
|
||||
"우리는 어제 회의를 가졌다. "
|
||||
"위원회는 결정을 내렸다."
|
||||
)
|
||||
self.assertGreaterEqual(metrics_v2.have_make_literal_count(text), 2)
|
||||
|
||||
def test_have_make_literal_zero(self) -> None:
|
||||
text = "그녀는 목소리가 곱다. 우리는 어제 모였다."
|
||||
self.assertEqual(metrics_v2.have_make_literal_count(text), 0)
|
||||
|
||||
# T7
|
||||
def test_double_particle_count_three(self) -> None:
|
||||
text = (
|
||||
"주점의 2층에서의 모임이 있었다. "
|
||||
"긴장으로부터의 해방이 필요하다. "
|
||||
"설문지에의 응답률이 낮았다."
|
||||
)
|
||||
self.assertGreaterEqual(metrics_v2.double_particle_count(text), 3)
|
||||
|
||||
def test_double_particle_count_excludes_simple_eui(self) -> None:
|
||||
# Only bare ~의 — must be ZERO under caveat #5.
|
||||
text = "한국의 미래는 밝다. 우리의 시간은 짧다. 그의 의견은 다르다."
|
||||
self.assertEqual(metrics_v2.double_particle_count(text), 0)
|
||||
|
||||
# T8b
|
||||
def test_progressive_aspect_high(self) -> None:
|
||||
text = (
|
||||
"나는 책을 읽고 있다. 그는 일을 하고 있다. 우리는 놀고 있었다."
|
||||
)
|
||||
self.assertGreater(metrics_v2.progressive_aspect_rate(text), 0.5)
|
||||
|
||||
def test_progressive_aspect_zero(self) -> None:
|
||||
text = "나는 책을 읽는다. 그는 일을 한다. 우리는 놀았다."
|
||||
self.assertEqual(metrics_v2.progressive_aspect_rate(text), 0.0)
|
||||
|
||||
# interference index composite
|
||||
def test_interference_index_components(self) -> None:
|
||||
text = "데이터들이 결과들을 보여준다. 그녀는 그녀의 책을 가지고 있다."
|
||||
idx = metrics_v2.interference_index(text)
|
||||
self.assertIn("components", idx)
|
||||
self.assertIn("weighted_total", idx)
|
||||
self.assertGreaterEqual(idx["weighted_total"], 0.0)
|
||||
|
||||
|
||||
class V20IntegrationTests(unittest.TestCase):
|
||||
"""compute_all_v2 end-to-end + baseline placeholder behavior."""
|
||||
|
||||
def test_compute_all_v2_returns_v2_keys(self) -> None:
|
||||
text = "오늘은 비가 온다. 길이 미끄럽다."
|
||||
result = metrics_v2.compute_all_v2(
|
||||
text, genre="essay",
|
||||
baseline_path=BASELINE_PATH,
|
||||
baseline_v2_path=BASELINE_V2_PATH,
|
||||
)
|
||||
self.assertEqual(result["version"], "v2.0")
|
||||
self.assertIn("v2_metrics", result)
|
||||
self.assertIn("v2_interference_index", result)
|
||||
self.assertIn("v2_z_scores", result)
|
||||
# v1.6 keys preserved
|
||||
self.assertIn("metrics", result)
|
||||
self.assertIn("risk_band", result)
|
||||
|
||||
def test_compute_all_v2_baseline_placeholder_warning(self) -> None:
|
||||
text = "오늘은 비가 온다. 길이 미끄럽다."
|
||||
result = metrics_v2.compute_all_v2(
|
||||
text, genre="essay",
|
||||
baseline_path=BASELINE_PATH,
|
||||
baseline_v2_path=BASELINE_V2_PATH,
|
||||
)
|
||||
# All v2 baseline cells are placeholders by design.
|
||||
self.assertGreater(len(result["v2_baseline_warnings"]), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
47
uninstall.sh
Executable file
47
uninstall.sh
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env bash
|
||||
# Humanize KR — 전역 설치 제거 스크립트
|
||||
# install.sh가 만든 "이 저장소를 가리키는 심링크"만 제거한다. 사용자가 직접 둔 파일이나
|
||||
# 다른 곳을 가리키는 링크, .bak.* 백업은 건드리지 않는다. (--copy 설치본은 자동 삭제 대상 아님)
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_HOME="${CLAUDE_HOME:-$HOME/.claude}"
|
||||
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||
DRYRUN=0
|
||||
|
||||
case "${1:-}" in
|
||||
--dry-run) DRYRUN=1 ;;
|
||||
-h|--help) echo "Usage: ./uninstall.sh [--dry-run]"; exit 0 ;;
|
||||
"") ;;
|
||||
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
remove_if_ours() {
|
||||
local dest="$1" src="$2"
|
||||
if [ -L "$dest" ] && [ "$(readlink "$dest")" = "$src" ]; then
|
||||
echo "+ rm $dest"; [ "$DRYRUN" = 1 ] || rm "$dest"
|
||||
elif [ -e "$dest" ]; then
|
||||
echo "skip (우리 것 아님): $dest"
|
||||
fi
|
||||
}
|
||||
|
||||
for s in humanize-korean humanize humanize-redo; do
|
||||
remove_if_ours "$CLAUDE_HOME/skills/$s" "$REPO/.claude/skills/$s"
|
||||
done
|
||||
remove_if_ours "$CODEX_HOME/skills/humanize-korean" "$REPO/codex/skills/humanize-korean"
|
||||
for a in "$REPO/agents"/*.md; do
|
||||
remove_if_ours "$CLAUDE_HOME/agents/$(basename "$a")" "$a"
|
||||
done
|
||||
|
||||
# ---- Gemini CLI ----
|
||||
if command -v gemini >/dev/null 2>&1; then
|
||||
echo "Gemini extension 제거 시도..."
|
||||
if [ "$DRYRUN" = 1 ]; then
|
||||
echo "+ gemini extensions uninstall im-not-ai (dry-run)"
|
||||
else
|
||||
gemini extensions uninstall im-not-ai 2>/dev/null && echo "removed: Gemini extension (im-not-ai)" \
|
||||
|| echo " (Gemini extension 미설치 또는 이미 제거됨)"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "제거 완료. (.bak.* 백업·--copy 설치본은 보존)"
|
||||
73
update.sh
Executable file
73
update.sh
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env bash
|
||||
# Humanize KR — 업데이트 감지 + 자동 적용
|
||||
# upstream(git)을 확인해 새 커밋이 있으면 fast-forward pull 후 install.sh를 재적용한다.
|
||||
# 심링크 설치는 pull만으로도 내용이 반영되지만, 신규 스킬/에이전트/구조 변경까지 확실히
|
||||
# 연결하려고 install.sh를 다시 실행한다(멱등).
|
||||
#
|
||||
# 사용:
|
||||
# ./update.sh 업데이트 감지 → 있으면 자동 적용
|
||||
# ./update.sh --check 감지만(적용 안 함). 최신=exit 0, 업데이트 있음=exit 10
|
||||
# 그 외 인자는 install.sh로 전달 (예: ./update.sh --copy --force, ./update.sh --codex-only)
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
g() { git -C "$REPO" "$@"; }
|
||||
|
||||
usage() { sed -n '2,11p' "$0"; }
|
||||
|
||||
CHECK_ONLY=0
|
||||
ARGS=()
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--check) CHECK_ONLY=1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) ARGS+=("$a") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
g rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "git 저장소가 아닙니다: $REPO"; exit 2; }
|
||||
|
||||
ver() { grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$REPO/.claude-plugin/plugin.json" 2>/dev/null \
|
||||
| head -1 | grep -o '[0-9][0-9.]*' \
|
||||
|| grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$REPO/gemini-extension.json" 2>/dev/null \
|
||||
| head -1 | grep -o '[0-9][0-9.]*' \
|
||||
|| echo "?"; }
|
||||
|
||||
UPSTREAM="$(g rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)"
|
||||
UP_REMOTE="${UPSTREAM%%/*}"
|
||||
echo "업데이트 확인 중… (upstream: $UPSTREAM)"
|
||||
g fetch --quiet "$UP_REMOTE" || { echo "fetch 실패 — 네트워크/원격을 확인하세요."; exit 2; }
|
||||
|
||||
LOCAL="$(g rev-parse HEAD)"
|
||||
REMOTE="$(g rev-parse "$UPSTREAM" 2>/dev/null || true)"
|
||||
[ -z "$REMOTE" ] && { echo "upstream($UPSTREAM)을 찾을 수 없습니다."; exit 2; }
|
||||
BASE="$(g merge-base HEAD "$UPSTREAM" 2>/dev/null || echo "")"
|
||||
|
||||
if [ "$LOCAL" = "$REMOTE" ]; then
|
||||
echo "이미 최신입니다 — v$(ver) ($(g rev-parse --short HEAD))."
|
||||
exit 0
|
||||
elif [ "$BASE" = "$REMOTE" ]; then
|
||||
echo "로컬이 upstream보다 앞서 있습니다 — 적용할 업데이트 없음."
|
||||
exit 0
|
||||
elif [ "$BASE" != "$LOCAL" ]; then
|
||||
echo "로컬이 upstream과 갈라져 있어 자동 업데이트를 멈춥니다(수동 병합 필요)."
|
||||
echo " local=$(g rev-parse --short HEAD) upstream=$(g rev-parse --short "$UPSTREAM")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 여기 도달 = behind, fast-forward 가능
|
||||
BEHIND="$(g rev-list --count "HEAD..$UPSTREAM")"
|
||||
echo "🔔 업데이트 있음: $BEHIND개 커밋 ($UPSTREAM)"
|
||||
g --no-pager log --oneline "HEAD..$UPSTREAM" 2>/dev/null | head -10 | sed 's/^/ /'
|
||||
|
||||
if [ "$CHECK_ONLY" = 1 ]; then
|
||||
echo "(--check: 적용하지 않음. 적용하려면 ./update.sh)"
|
||||
exit 10
|
||||
fi
|
||||
|
||||
OLD="$(ver)"
|
||||
echo "fast-forward pull…"
|
||||
g pull --ff-only
|
||||
echo "설치 재적용(install.sh, 멱등)…"
|
||||
"$REPO/install.sh" ${ARGS[@]+"${ARGS[@]}"}
|
||||
echo "✅ 자동 업데이트 완료: v$OLD → v$(ver)."
|
||||
Loading…
Add table
Add a link
Reference in a new issue