Compare commits

..

25 commits

Author SHA1 Message Date
Liam lee
14aeb52d13
Merge pull request #29 from dlskawns96/feat/gemini-cli-extension
feat: Gemini CLI Extension 전역 설치 지원 및 슬래시 커맨드 연동
2026-06-09 17:32:52 +09:00
dlskawns96
01e226fc84 fix(gemini): remove blockquote formatting causing ImportProcessor warnings 2026-06-09 14:29:18 +09:00
dlskawns96
d83ecf2255 feat: Gemini CLI Extension 전역 설치 지원
한 줄 요약
Claude Code·Codex CLI에 이어 Gemini CLI(Antigravity)에서도 전역 설치 후
/humanize-korean 커맨드로 한글 AI 티 제거 윤문을 사용할 수 있게 패키징.

변경 내용

- gemini-extension.json: Gemini CLI Extension 매니페스트 신규
- GEMINI.md: 에이전트 컨텍스트 — monolith 룰 + quick-rules 인라인
- commands/humanize-korean.toml: /humanize-korean 커스텀 명령
- commands/humanize.toml: /humanize 별칭 명령
- commands/humanize-redo.toml: /humanize-redo 2차 윤문 명령
- install.sh: gemini CLI 자동 감지 + extensions link 블록 추가
  --gemini-only / --no-gemini 옵션 추가
- uninstall.sh: gemini extensions uninstall 블록 추가
- update.sh: ver() 함수에 gemini-extension.json fallback
- INSTALL.md: Gemini CLI 섹션 + 도구 표 갱신
- CLAUDE.md: 디렉토리 구조에 Gemini 파일 반영

설치: gemini extensions install https://github.com/epoko77-ai/im-not-ai.git
또는: git clone ... && ./install.sh --gemini-only
Gemini는 Codex와 동일하게 Fast(단일 호출) 모드만 제공.
strict 5인 파이프라인은 Claude Code 전용.
2026-06-09 10:19:56 +09:00
Liam lee
9cfb5f2f2d
Merge pull request #26 from PresentJay/global-skill-install-codex-claude
feat: Claude Code 플러그인 마켓플레이스 + Codex CLI 전역 설치 지원
2026-06-02 08:37:23 +09:00
Hyeonjae.Jeong
4f0d7012c2 feat(install): update.sh — 업데이트 자동 감지 + 자동 적용
- git upstream 확인 → 새 버전이면 ff-pull + install.sh 재적용(신규 스킬/에이전트 연결)
- --check(감지만, 최신=0/업데이트=10), install 플래그 패스스루, bash 3.2 호환
- 마켓플레이스 설치는 /plugin update로 갱신, 주기적 무인 갱신은 opt-in cron 문서화
- README·INSTALL.md 업데이트 섹션 보강, install.sh 종료 안내에 update.sh 추가
2026-06-01 16:06:15 +09:00
Hyeonjae.Jeong
5c011530ab docs(claude-md): 디렉토리 구조를 현행화 — agents/ 루트 이동·플러그인/설치/codex 추가 2026-06-01 14:22:23 +09:00
Hyeonjae.Jeong
499b0452d7 docs(readme): 신규 유저용 전역 설치 안내 보강
- 사용법 인트로: 전역 설치 시 클론/폴더 진입 단계 건너뛰기 안내
- 2단계: "꼭 폴더 안에서" 강제 → 전역 설치/체험 두 경로로 완화
- 방법 B: 죽은 .claude/commands/ 링크 → 스킬 경로로 수정
- 방법 C: 공식 플러그인 마켓플레이스 설치로 갱신(과거 "v1.6 검토 중" 제거)
- 방법 D: 공식 Codex CLI 지원으로 갱신(community 포트는 부가 안내로 유지)
2026-06-01 14:21:17 +09:00
Hyeonjae.Jeong
0f981e7a4c docs: Claude(마켓플레이스/스크립트)·Codex 전역 설치 가이드
- README 상단에 설치 섹션(마켓플레이스 + 스크립트) 추가
- INSTALL.md: 두 도구 × 설치 방법별 상세, 옵션표, 업데이트/제거, 트러블슈팅
2026-06-01 14:03:41 +09:00
Hyeonjae.Jeong
b7366a4fb3 feat(install): 저장소 소유 심링크만 제거하는 uninstall.sh
- readlink 비교로 우리 src를 가리키는 심링크만 rm, 일반 파일/타 링크/.bak은 skip
- --dry-run 지원
- 검증: dry-run 16건, 실제 제거, 디코이 파일 skip 안전성, 멱등 no-op, 재설치 원복 OK
2026-06-01 14:02:30 +09:00
Hyeonjae.Jeong
5bab459294 feat(install): Claude+Codex 멱등 설치 스크립트
- 심링크 기본(저장소 수정 즉시 반영) / --copy(cp -RL로 references 심링크 실체화)
- --claude-only/--codex-only, command -v로 CLI 자동 감지
- --force(.bak 백업 후 덮어쓰기), --dry-run
- 검증: 3 skills+12 agents(Claude)+1 skill(Codex) 심링크, 재실행 멱등(16 already-linked, .bak 0)
2026-06-01 14:01:37 +09:00
Hyeonjae.Jeong
ec6390db2c feat(codex): Codex CLI용 Fast Path humanize-korean 스킬 + references 심링크
- codex/skills/humanize-korean/SKILL.md: monolith 기반 자가완결 Fast Path(서브에이전트 불필요)
- references → ../../../.claude/skills/humanize-korean/references 상대 심링크로 SSOT 공유
- 출력 경로 _workspace/{run_id}/final.md (Claude 스킬과 일치)
2026-06-01 13:59:38 +09:00
Hyeonjae.Jeong
df4ca4abd3 feat(dist): Claude 플러그인 + 마켓플레이스 매니페스트, 에이전트를 플러그인 루트 agents/로 이동
- .claude-plugin/{plugin.json,marketplace.json} 추가 (source: ./, skills: ./.claude/skills/)
- .claude/agents/*.md(12종) → agents/ 로 이동: 플러그인 컨벤션상 에이전트는 루트 agents/에 있어야 로드됨
  (.claude/agents 커스텀 파일경로는 validate는 통과하나 런타임 미로드 — 실제 install로 확인)
- 실측 검증: claude plugin install → details = Skills(3) + Agents(12) 정상 로드
- SKILL.md 에이전트 위치 안내 갱신
2026-06-01 13:57:58 +09:00
Hyeonjae.Jeong
6ae5fe6a9e refactor(skill): /humanize·/humanize-redo 명령을 v1.5 스킬로 이식, 낡은 voice-profile·author-context 참조 제거
- disable-model-invocation: true 로 수동 전용 진입점 유지(humanize-korean과 자동호출 충돌 방지)
- 옛 .claude/commands/*.md 삭제
2026-06-01 13:45:00 +09:00
Hyeonjae.Jeong
8ee6bb03ad fix(skill): quick-rules 경로를 ${CLAUDE_SKILL_DIR} 기반으로 — 전역/플러그인 설치 호환 2026-06-01 13:43:27 +09:00
sh lee
807172694d
Merge pull request #23 from epoko77-ai/docs/v2.0-thumbnail-tweak2
docs(thumbnail): A-18 사례 — AI 학습 데이터로 교체
2026-05-07 23:56:47 +09:00
epoko77-ai
1cf0d0aa80 docs(thumbnail): A-18 사례 — AI 학습 데이터로 교체 (도메인 호응)
기존 "어제 도착한 택배" 사례가 좌향 수식 강도가 약하고 와닿음이
부족하다는 피드백 반영. 도구 자체 도메인(영-한 NMT가 만드는
한국어 출력)에서 가장 자주 나타나는 패턴으로 교체.

변경 (BEFORE/AFTER 마지막 행만):
- BEFORE: "AI가 학습한 데이터가 보여주는 언어 패턴"
  좌향 2중: "AI가 학습한" → "데이터가 보여주는" → 핵 어휘 "패턴"
- AFTER: "AI 학습 데이터의 언어 패턴"
  관형구를 단일 명사구로 압축 → 핵 어휘 도달이 빨라짐

도메인 호응: 영-한 번역체에서 진짜 자주 나오는 NMT 출력 패턴.
도구 자체가 다루는 문제와 직접 일치.
2026-05-07 23:56:38 +09:00
sh lee
52beb370c8
Merge pull request #22 from epoko77-ai/docs/v2.0-thumbnail-tweak
docs(thumbnail): A-18 예시 일상 톤으로 교체
2026-05-07 23:51:21 +09:00
epoko77-ai
be4377ba58 docs(thumbnail): A-18 BEFORE/AFTER 예시 일상 톤으로 교체
기존 "사고를 일으킨 회사" 부정적 톤이 v1 BEFORE/AFTER 사례들
("지속될 것으로 보여진다" 등 일상·중립)과 톤 차이.

변경 (BEFORE/AFTER 마지막 행만):
- "사고를 일으킨 회사에서 일했던 남자를 만났다."
  → "어제 도착한 택배에 들어 있던 책이 베스트셀러다."
- "남자를 만났는데, 사고를 일으킨 회사에서 일했다."
  → "어제 받은 택배의 책이 베스트셀러다."
- 처방 라벨: "후치 동격절" → "관형구 압축"

좌향 수식 2중(어제 도착한 / 들어 있던 / 책) → 1중 압축으로
A-18 패턴 강도는 동일 유지.
2026-05-07 23:51:12 +09:00
sh lee
6bab7a52a3
Merge pull request #21 from epoko77-ai/docs/v2.0-thumbnail
docs(thumbnail): v2.0 social preview — 한국 번역학계 8유형 흡수 시각화
2026-05-07 23:47:49 +09:00
epoko77-ai
ca2423bfda docs(thumbnail): v2.0 social preview — 한국 번역학계 8유형 흡수 시각화
기존 v1.1 시점 썸네일(74.5→9.5·v1.1 라벨)을 v2.0 메시지로 갱신.
디자인 톤(베이지 #F4EFE5·짙은 녹색 #2D5C3F·빨강 #C0573F·BEFORE/AFTER
2단)은 유지, 콘텐츠를 v2.0 신규 패턴 3건으로 교체.

변경
- assets/social-preview.png — v2.0 썸네일 (1280×640)
  - 우상단 부제 2줄: "한글 AI 티 제거기" / "v2.0 · 한국 번역학계 8유형 흡수"
  - BEFORE/AFTER 3행:
    * A-16 영어 대명사 직역: "그는 그의 시계를 보았다" → "시계를 보았다"
    * A-19 이중 조사 결합: "긴장으로부터의 해방" → "긴장에서 벗어남"
    * A-18 관계절 좌향 수식: 후치 동격절 변환 예시
  - 하단 메타: "10 categories · 60+ patterns · v2.0"
  - 학자 인용: "이근희·김정우·김도훈·김혜영·Toral 2019"
- assets/social-preview-v1.png — v1.1 썸네일 백업 (롤백 대비)
- scripts/build_social_preview_v2.py — Pretendard 8종 + Pillow 12.2 재생산
  스크립트 (디자인 토큰·BEFORE/AFTER 행 데이터 분리)

Release 노트의 raw.githubusercontent.com URL은 main 갱신 시 자동 반영.
SNS 링크 미리보기(og:image)는 별도로 GitHub Settings > Social Preview에
이 PNG를 등록해야 활성화.
2026-05-07 23:47:28 +09:00
sh lee
b5403b0891
Merge pull request #20 from epoko77-ai/docs/v2.0-readme
docs(readme): v2.0 발행 노트
2026-05-07 23:14:55 +09:00
epoko77-ai
b0404df7dc docs(readme): v2.0 발행 노트 — 한국 번역학계 8유형 + post-editese (A-17 hold)
- 제목 v1.6.1 → v2.0.0
- 분류 체계 요약 표에 신규 패턴(A-16·A-18·A-19·E-7) 명시 + scholarship.md 링크
- v2.0 발행 섹션 신설 (v1.6 섹션 위)
  - 핵심 변경 5축: 신규 4·보강 4·post-editese metric-only·양면 인용·룰북 §1.X
  - Hold 1건 (A-17) 사유·유지 자산·부활 조건
  - 검증 결과 표 (Phase 5 + 외부 회차 + pytest)
  - 한계·다음 회차 과제

monolith·5인 정의 무수정·도구 호출 3회 캡 보존 그대로. v1.6 섹션은 그대로 보존.
2026-05-07 23:14:46 +09:00
sh lee
67b14e7fef
Merge pull request #19 from epoko77-ai/v2.0
feat(taxonomy): v2.0 — 한국 번역학계 8유형 + post-editese metric (A-17 hold)
2026-05-07 23:10:59 +09:00
epoko77-ai
f53b8bc032 feat(taxonomy): v2.0 — 한국 번역학계 8유형 + post-editese metric 트랙 (A-17 hold)
본진 분류 체계에 한국 번역학계 8대 번역투 유형(이근희·김정우·김도훈·곽은주·
김순영·박옥수·김혜영·이영옥)을 흡수하고, Toral 2019 post-editese 3축
(simplification·normalisation·interference)을 14개 신규 metric 트랙으로
도입. monolith·5인 정의는 무수정, 도구 호출 3회 캡(v1.6.1) 보존.

신규 패턴 4건 (본진 등재):
  - A-16 영어 대명사 직역 [S1, 김도훈 2009 + Cho et al. 2019 ACL GeBNLP]
  - A-18 관계대명사절 직역 — 좌향 수식 [S2, 박옥수 2018]
  - A-19 이중 조사 결합 [S2, 김정우 2007, 단순 ~의 명시 제외]
  - E-7 청자 경어법 일관성 손실 [S2 estimated, 김혜영 2019, dialogue 가드]

본진 hold 1건 (v2.0b 외부 회차 결과):
  - A-17 무정물·추상명사 '-들' 부착 — v1.6 5편 + 외부 위키 6편 양성 0건.
    학술 anchor·metric·scholarship.md §4 보존, 본진 등재만 보류. NMT 원본
    회차 후 v2.1에서 동일 ID로 부활. patternID 안정성 보존.

본진 보강 4건 (본문 무수정 + 처방 추가):
  - A-15 추상 주어 — 사역·인지·발화 동사 3축 처방
  - A-7 가지고 있다 — light verb construction(have/make/take/give) 일반화
  - F-4 과잉 접두·접미 — 영어 명사화 -tion/-ment/-ness/-ity 통합
  - E-2 동일 종결어미 — 진행형 '~고 있다' 자동 매핑 처방

post-editese metric-only 트랙:
  - lexical_diversity_ttr·lexical_density·ending_diversity (simplification)
  - normalisation_score·da_streak_rate (normalisation)
  - inanimate_subject_rate·by/double_passive·pronoun_density·deul_overuse_rate
    ·relative_clause_nesting·have_make_literal·double_particle·progressive_aspect
    + interference_index 합성 (interference, T1~T8)
  - 14건 모두 본진 패턴 ID 미부여 (caveat C3: 한국어 정량 검증 부재).
    metrics_v2.py로 분리, baseline_v2.json 70셀 placeholder 상태.

회귀 검증:
  - v1.6 5편 input·final 점수 산출 (재윤문 없음). 회귀 0건.
  - lexical_diversity 5편 전수 상승 (post-editese 단순화 가설 1차 반증).
  - 외부 회차 위키 6편 — A-16 양성 50%·A-18 양성 67%, interference_index
    외부 평균 0.251 vs v1.6 0.05~0.10 (Toral 가설 1차 부합).

학술 인용 양면 보존:
  - SSOT 메타필드 한 줄 (ai-tell-taxonomy.md) + 외부 SSOT 전문
    (scholarship.md, 학자 29명·Caveat 6건 verbatim).
  - 룰북 슬림성 보존: quick-rules.md 126→133줄 (≤180 한도).

4대 철칙 준수:
  - monolith·5인 정의 무수정 (humanize-monolith·detector·rewriter·auditor·
    reviewer git diff 0줄).
  - 재윤문 없는 회귀 (사용자 토큰 통제 원칙).
  - 양면 인용 보존.
  - patternID 참조 안정성 (A-1~A-15·E-1~E-6 본문 무수정).

상세 PR 본문: _workspace/v2.0-2026-05-07/07_pr/07_pr_draft.md
외부 회차 보고: _workspace/v2.0-2026-05-07/05_regression/v2_external_samples/H1_revisited.md
2026-05-07 23:04:09 +09:00
sh lee
6138697bc3
Merge pull request #18 from epoko77-ai/v1.6.1
v1.6.1 hotfix — final.md 통합 산출물로 sub-agent Write 차단 회피
2026-05-07 00:53:29 +09:00
43 changed files with 3309 additions and 148 deletions

View 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", "윤문", "번역투"]
}
]
}

View 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/"]
}

View file

@ -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).

View file

@ -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 "권한 위계" 절

View file

@ -49,7 +49,7 @@ humanize-korean v1.5 — {fast|strict} 모드 / run_id: {YYYY-MM-DD-NNN}
입력:
```
input_path: <abs path>/_workspace/{run_id}/01_input.txt
quick_rules_path: <abs path>/.claude/skills/humanize-korean/references/quick-rules.md
quick_rules_path: ${CLAUDE_SKILL_DIR}/references/quick-rules.md
genre_hint: 칼럼 | 리포트 | 블로그 | 공적 | null
```
@ -150,9 +150,9 @@ v1.1 5인 파이프라인 그대로. 검증 분리·재윤문 루프가 의미
**모델:** 모두 `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종:
- `humanize-monolith` (v1.5 신규, fast 전용)

View file

@ -1,7 +1,9 @@
# AI 한글 티 분류 체계 v1.6 (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가 "간결하게" 의도하면 단문만 늘어놓아 끊어진 리듬이 그 자체로 시그니처가 된다.
@ -16,16 +18,16 @@ LLM(ChatGPT·Claude·Gemini 등)이 생성한 한글 글에서 반복적으로
## 목차
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
---
@ -64,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]
- 예: "판단**되어진다**" → "판단된다" / "판단한다"
@ -101,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-일본어영어식-조사-결합-에서의에로의으로의에의_
---
@ -247,6 +303,12 @@ 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문장 공식
- 문단 길이도 균일.
@ -268,6 +330,17 @@ J. 시각 장식 남용
- 예: 같은 문서 안에서 쉼표가 명사 뒤·부사 뒤·동사 뒤·관형사 뒤·인용 뒤·접속사 뒤 모두에 무차별 삽입.
- 처방: 쉼표 사용 위치를 (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
@ -292,6 +365,8 @@ 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가 흉내 낼 때 빈출.
@ -443,8 +518,30 @@ 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 강화 임계

View 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."
}
}

View 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())

View file

@ -1,6 +1,6 @@
# Quick Rules — Monolith Fast Path 전용 (v1.6)
# Quick Rules — Monolith Fast Path 전용 (v2.0)
`humanize-monolith` 에이전트가 한 콜에서 탐지·윤문·자체검증을 끝내기 위해 사용하는 슬림 룰북. 본진 `ai-tell-taxonomy.md`(490줄)에서 S1·S2 핵심 패턴만 추려 처방과 함께 한 줄로 압축했다.
`humanize-monolith` 에이전트가 한 콜에서 탐지·윤문·자체검증을 끝내기 위해 사용하는 슬림 룰북. 본진 `ai-tell-taxonomy.md`(590줄)에서 S1·S2 핵심 패턴만 추려 처방과 함께 한 줄로 압축했다.
**원칙:** 정의 1줄 + 처방 1줄. 예문 생략. 본진 ID와 1:1 매칭.
@ -20,12 +20,15 @@
| A-4 | "~라는 점에서" 3회+ | S2 | "~서", "~라는 이유로" |
| A-5 | "~와 관련하여/관련된" | S2 | "~에", "~의" |
| A-6 | "~에 기반하여/바탕으로" 남발 | S2 | "~로", "~을 보고" |
| A-7 | "가지고 있다" | S1 | 형용사형으로("강한 경쟁력을 가지고 있다" → "경쟁력이 강하다") |
| 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를 가져온다" → "X가 일어난다") |
| 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. 영어 인용·용어 과다
@ -57,17 +60,19 @@
| D-6 | 결말 공식 "~할 때다/~해야 한다/~지금이야말로" | S1 | 평서로 닫거나 삭제 |
| D-7 | 변환 공식 "X에서 Y로" 반복 | S2 | 한 번만, 나머지는 일반 서술 |
## E. 리듬
## E. 리듬·종결어미
| ID | 패턴 | 심각도 | 처방 |
|---|---|---|---|
| E-1 | 문장 길이 균일(stdev 8 미만) | S2 | 단문 1~2개 / 장문 1개를 각 문단에 의도적 삽입 |
| E-2 | 동일 종결어미 "~다" 4문장 연속 + 진행형 "~고 있다" 자동 매핑 | S2 | "~었다·~ㄴ다·~는다·~기 마련이다·~ㄹ 것이다" 등 다양화. "~고 있다" 단순 시제 환원 가능 시 환원("읽고 있다" → "읽는다") |
| E-7 | 청자 경어법 4단계(해라/하게/하오/해요/합쇼) 일관성 손실 (대화·구어 한정) | S2 | 한 단락 내 혼용 금지, 격식 일관 (김혜영 2019, estimated) |
## F. 과도한 수식·중복
| ID | 패턴 | 심각도 | 처방 |
|---|---|---|---|
| F-4 | 한자어 명사화 -성/-적/-화 밀도 (한 글 12회+) | S2 | 일부를 동사·형용사로 환원 |
| F-4 | 한자어 명사화 -성/-적/-화 + 영어 명사화 -tion/-ment/-ness/-ity 누적 (한 글 12회+) | S2 | 동사·형용사 어근으로 환원("the implementation of the policy" → "정책 시행" 또는 "정책을 시행하기") |
| F-5 | "~적 N" 추상 체인 ("전략적 함의·실천적 기반") | S2 | 명사+명사 또는 풀어쓰기("전략 함의·실천의 기반") |
## G. Hedging
@ -113,7 +118,7 @@
2. **변경률**: 30% 이하인가 (50% 초과는 작업 중단)
3. **장르 이탈 없음**: 칼럼이 에세이·문학으로 변하지 않았는가, 리포트가 블로그체로 떨어지지 않았는가
4. **register 보존**: 원문 격식체면 결과도 격식체. 평어체로 떨어뜨리지 않는다
5. **잔존 S1 패턴 0건**: D-1~D-7, A-8, C-5, C-10, C-11, H-1, I-1, J-2 핵심 S1이 남아있지 않은가
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건" 표기.
@ -124,3 +129,5 @@
- **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로 재평가.

View file

@ -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. 변경률 모니터링
- 윤문가는 변경 전후 텍스트의 **레벤슈타인 거리 / 원문 길이**를 계산해 변경률을 기록한다.

View 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 외 자체 추가·확장 없음. 본진 분류 체계 본문 직접 수정 권한 없음.

View 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`

View 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`

View file

@ -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
```
## 파이프라인

179
GEMINI.md Normal file
View 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
View 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 전용.

103
README.md
View file

@ -2,12 +2,38 @@
<img src="assets/social-preview.png" alt="im-not-ai — 한글 AI 티 제거기" width="820">
</p>
# Humanize KR — 한글 AI 티 제거기 v1.6.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 글의 티는 대부분 **영어 번역투**에서 나옵니다.
@ -74,18 +100,18 @@ final.md + summary.md
| 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 신규)
## 심각도 & 품질 등급
@ -102,6 +128,8 @@ final.md + summary.md
## 사용법 — 5분이면 따라합니다
> **전역 설치([설치](#설치-install))를 마쳤다면** 1~2단계(클론·폴더 진입)는 건너뛰고, 아무 폴더에서나 바로 **3단계**로 가세요. 아래는 설치 없이 리포에서 곧바로 체험하는 흐름입니다.
### 0. 전제
[Claude Code](https://claude.com/claude-code)가 설치돼 있어야 합니다. Mac · Windows · Linux 모두 지원합니다.
@ -120,13 +148,14 @@ 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가 쓴 한글 글 붙여넣고 부탁하기
@ -155,22 +184,30 @@ Claude Code에서는 세 가지 방법 중 편한 쪽으로 사용합니다. Cod
/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.6 검토 중입니다 ([Issue 추적 예정](https://github.com/epoko77-ai/im-not-ai/issues)).
본체가 이제 Claude Code Plugin/Marketplace를 **공식 지원**합니다. 클론 없이 마켓플레이스로 설치하세요:
**방법 D — Codex Plugin (community)**
```
/plugin marketplace add epoko77-ai/im-not-ai
/plugin install humanize-korean@im-not-ai
```
[`Squirbie/im-not-ai-codex`](https://github.com/Squirbie/im-not-ai-codex)에서 Codex Desktop/CLI용 community plugin 포트를 제공합니다. 원본 taxonomy/playbook을 유지하고 Codex plugin/skill 구조에 맞게 어댑터화한 별도 배포판이며, 공식 Claude Code 버전과 분리되어 관리됩니다.
스킬 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
codex plugin marketplace add Squirbie/im-not-ai-codex
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 로 윤문하는 커뮤니티 제작 포트입니다.
@ -228,6 +265,42 @@ 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배**(에세이)였습니다.

View file

@ -31,7 +31,7 @@ model: opus
### 입력
- `input_path`: `_workspace/{run_id}/01_input.txt` (절대 경로)
- `quick_rules_path`: `.../skills/humanize-korean/references/quick-rules.md` (절대 경로)
- `quick_rules_path`: 오케스트레이터가 전달하는 절대 경로(`${CLAUDE_SKILL_DIR}/references/quick-rules.md` 치환값). 에이전트는 이 인자를 그대로 Read 한다.
- `genre_hint`: 칼럼 | 리포트 | 블로그 | 공적 | null (null이면 첫 300자로 자체 추정)
### 출력

View 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 메타 슬림성)

View 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회 자체 보고

View 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 자체 보고

View 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건씩

View 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)가 직접 파싱하므로 형식이 깨지면 안 된다.

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before After
Before After

View 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` — 카테고리별 치환 레시피

View file

@ -0,0 +1 @@
../../../.claude/skills/humanize-korean/references

View 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}}
"""

View 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
View 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
View 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
View 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

View 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()

View file

@ -46,10 +46,15 @@ METRICS_DIR = PROJECT_ROOT / ".claude" / "skills" / "humanize-korean" / "referen
# 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 as _metrics_mod # type: ignore
except Exception: # pragma: no cover — module import itself failing.
_metrics_mod = None
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
# ---------------------------------------------------------------------------

361
tests/test_metrics_v2.py Normal file
View 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
View 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
View 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)."