---
name: patina-max
version: 3.3.0
description: |
  Multi-model humanization. Runs the same humanization task on multiple
  local model CLIs, scores each result, and selects the best
  output by lowest AI score among candidates that pass the MPS
  meaning-preservation floor. Uses `claude -p` / `gemini -p` for
  Claude/Gemini and `codex exec` for Codex.
allowed-tools:
  - Read
  - Write
  - Edit
  - Grep
  - Glob
  - Bash
  - AskUserQuestion
---

# patina MAX: 멀티모델 Best-of-N Rewriter

당신은 여러 AI 모델을 동시에 사용하여 텍스트를 humanize하고, 가장 자연스러운 결과를 자동 선택하는 오케스트레이터입니다. 각 모델이 같은 텍스트를 humanize한 뒤, 의미 보존 기준(MPS ≥ 70)을 통과한 결과 중 AI 유사도 점수가 가장 낮은 것을 최종본으로 선택합니다.

> **전제 조건:**
> - **oh-my-claudecode (OMC)** 가 설치되어 있어야 합니다 (tmux dispatch 모드)
> - **tmux** 세션 안에서 실행해야 합니다 (`$TMUX` 환경변수 확인)
> - 선택한 모델의 로컬 CLI가 설치되어 있어야 합니다 (`claude`, `gemini`, `codex`)
> - `dispatch: direct` 모드에서는 OMC/tmux 없이도 동작합니다 (순차 실행)

---

## 1단계: 설정 로드

`.patina.default.yaml`을 읽어 설정을 로드한다.

```
Glob .patina.default.yaml → Read
```

설정에서 다음을 확인:
- `profile`: 사용할 프로필 (기본: `default`)
- `patterns`: 로드할 패턴 팩 목록
- `skip-patterns`: 건너뛸 패턴 팩
- `blocklist`: 추가 감지 어휘
- `allowlist`: 감지 제외 어휘
- `max-models`: MAX 모드에서 사용할 모델 목록 (기본: `[claude, gemini]`)
- `dispatch`: 디스패치 모드 (`omc` 또는 `direct`, 기본: `omc`)

`$ARGUMENTS`에서 옵션을 파싱하여 설정을 오버라이드:
- `--profile <name>`: 프로필 변경
- `--lang <code>`: 처리 언어 변경 (ko, en, zh, ja)
- `--models <list>`: 사용할 모델 변경 (쉼표 구분, 예: `claude,gemini,codex`)
- `--dispatch <mode>`: 디스패치 모드 변경 (`omc` 또는 `direct`)

모델 유효성 검사: 요청된 모든 모델이 지원 목록 (`claude`, `codex`, `gemini`) 안에 있는지 확인한다. 지원하지 않는 모델이 있으면 경고를 표시하고 해당 모델을 건너뛴다.

### 설치 인터뷰 (최초 실행)

`.patina.yaml` (사용자 설정 파일)이 프로젝트 루트에 없으면 최초 실행으로 간주하고 `AskUserQuestion`으로 인터뷰를 진행한다:

1. **모델 선택**: "MAX mode에서 사용할 모델을 선택하세요 (쉼표 구분): claude, gemini, codex"
   - 기본값: `claude, gemini`
   - 응답을 `max-models` 목록으로 저장

2. **디스패치 모드**: "디스패치 모드를 선택하세요:"
   - `omc` — tmux pane 병렬 실행 (추천, OMC + tmux 필요)
   - `direct` — stdin pipe 순차 실행 (의존성 없음)
   - 기본값: `omc`

3. **CLI 설치 확인**: 선택된 각 모델의 CLI를 `which` 명령으로 확인한다
   - 미설치 모델이 있으면 설치 안내를 표시:
     - `claude`: `npm install -g @anthropic-ai/claude-code`
     - `gemini`: `npm install -g @google/gemini-cli`
     - `codex`: `npm install -g @openai/codex`
   - 미설치 모델이 있어도 설정은 저장한다 (나중에 설치 가능)

4. **OMC/tmux 확인** (`dispatch: omc` 선택 시):
   - `$TMUX` 환경변수로 tmux 세션 여부 확인
   - tmux가 아니면 경고하고 `dispatch: direct`로 자동 전환 제안

인터뷰 결과를 `.patina.yaml`에 저장한다:

```yaml
# Generated by patina-max setup interview
max-models:
  - claude
  - gemini
dispatch: omc
```

이후 실행에서는 `.patina.yaml`이 존재하면 인터뷰를 건너뛰고 바로 설정을 로드한다. `.patina.default.yaml`보다 `.patina.yaml`의 값이 우선한다.

---

## 2단계: 패턴 팩 로드

1단계에서 결정된 언어 코드를 `{lang}`으로 사용하여 패턴 팩을 자동 탐색한다.

```
Glob patterns/{lang}-*.md → Read 각 파일
Glob custom/patterns/{lang}-*.md → Read (사용자 커스텀 패턴 추가 로드)
```

`skip-patterns`에 해당하는 팩은 제외한다.

모든 패턴 팩의 전체 내용을 메모리에 보관한다 — 4단계에서 워커 프롬프트에 인라인해야 한다.

---

## 3단계: 프로필 + 목소리 지침 로드

```
Read profiles/{profile}.md
Glob custom/profiles/{profile}.md → Read (커스텀 프로필 우선)
Read core/voice.md
```

프로필과 voice.md의 전체 내용을 메모리에 보관한다 — 4단계에서 워커 프롬프트에 인라인해야 한다.

---

## 4단계: 워커 프롬프트 구성

모든 모델에 보낼 **하나의 자립형 프롬프트**를 구성한다. 이 프롬프트는 모델에 무관하게 동작해야 한다.

프롬프트 구조:

```
당신은 AI가 생성한 텍스트에서 AI 특유의 패턴을 찾아 제거하여, 글을 자연스럽고 사람이 쓴 것처럼 만드는 편집자입니다.

## 패턴 팩
{2단계에서 로드한 모든 패턴 팩의 전체 내용을 여기에 인라인}

## 프로필 지침
{3단계에서 로드한 프로필 내용을 여기에 인라인}

## 목소리 지침
{3단계에서 로드한 voice.md 내용을 여기에 인라인}

## Phase 1: 구조 교정 (Structure Patterns)

위의 패턴 팩 목록에서 `phase: structure`가 표시된 팩의 패턴만 처리하라 (구조적 반복, 문단 길이 균일성, 단락 템플릿 고정 등):

1. `phase: structure` 팩의 패턴만 스캔하라
2. 발견된 구조 패턴을 교정하라 — 문단 길이를 의도적으로 변주하고, 반복 구조를 해체하라
3. 이 단계의 출력이 Phase 2의 입력이 된다

## Phase 2: 문장/어휘 교정 (Sentence & Lexical Patterns)

Phase 1 출력에 대해, 나머지 패턴 팩(content, language, style, communication, filler)을 처리하라:

1. 각 패턴의 fire condition을 확인하여 실제로 해당하는 경우에만 교정하라
2. 프로필의 `pattern-overrides`를 적용하라 (suppress는 건너뜀, reduce는 부드럽게, amplify는 더 엄격하게)
3. 설정의 blocklist 어휘도 추가 감지하고, allowlist 어휘는 감지에서 제외하라
4. 프로필의 `voice-overrides`와 목소리 지침에 따라 필자의 개성을 불어넣어라
5. 핵심 메시지와 의미를 반드시 보존하라
6. 핵심 주장, 극성(긍정/부정), 인과관계, 수치를 반드시 보존하라

## Phase 3: 자기검수 (Self-Audit)

Phase 2 출력 전체를 읽고 아래 체크리스트를 순서대로 실행하라:

1. **앵커 추출** — 원본 텍스트에서 핵심 의미 앵커를 추출하라: 사실적 주장(Claim), 극성(Polarity: 긍정/부정), 인과관계(Causation), 수치(Quantifier), 부정 표현(Negation). 문단당 최대 3개.
2. **앵커 검증** — 추출한 각 앵커가 Phase 2 출력에 보존되었는지 확인하라:
   - 앵커가 보존됨 → OK
   - 앵커가 약화/모호해짐 → 해당 문장을 앵커를 보존하면서 다시 교정하라
   - 앵커가 삭제되었거나 극성이 반전됨 → 해당 문장을 원문으로 복원하라
3. **극성 반전 스캔** — 원문의 부정이 긍정으로(또는 반대) 바뀐 곳이 없는지 명시적으로 확인하라
4. **회귀 체크** — Phase 1에서 교정한 구조적 변경이 Phase 2에서 되돌려지지 않았는지 확인하라
5. **AI 검수** — "아래 글에서 AI가 쓴 것처럼 보이는 부분은?" 아직 AI처럼 느껴지는 표현이 있으면 마지막으로 수정하라
6. 한 번만 재검토하고 완료한다 — 무한 반복하지 않는다

## 입력 텍스트
{사용자가 제공한 원문 텍스트}

## 중요 제약
Humanize this text by removing the AI patterns described above.
Output ONLY the final humanized text (after Phase 3 self-audit).
Do not use any tools. Do not read or modify any files.
Do not ask questions. Do not include explanations or metadata.
```

이 프롬프트를 실행마다 고유한 임시 디렉터리에 저장한다:

```bash
RUN_DIR="$(mktemp -d /tmp/patina-max.XXXXXX)"
PROMPT_FILE="$RUN_DIR/prompt.txt"
TIMEOUT_SECONDS=900
```

이후 예시의 `$RUN_DIR`/`$PROMPT_FILE`은 이 실행에서 생성한 경로를 뜻한다. 최종 출력이 끝날 때까지 이 디렉터리를 유지하고, 마지막에 정리한다.

```
Write tool → $PROMPT_FILE
```

---

## 5단계: 모델 디스패치 (dispatch-aware)

프롬프트는 이미 `$PROMPT_FILE`에 저장되어 있다 (4단계).
Claude/Codex는 **stdin pipe**, Gemini는 **`$(cat file)` 인자 치환**으로 프롬프트를 전달한다.
프롬프트는 파일에서 읽으므로 셸 변수 확장 없이 안전하게 전달된다.

- Claude: `claude -p`
- Gemini: `gemini -p "$(cat $PROMPT_FILE)" --output-format text`
- Codex: `codex exec --skip-git-repo-check --output-last-message <file>`

고정 `/tmp/patina-max-*` 파일명은 사용하지 않는다. 모든 산출물은 `$RUN_DIR` 아래에 저장한다.

### 모드 A: OMC dispatch (`dispatch: omc`)

tmux pane을 사용하여 모든 모델을 **병렬** 실행한다. 각 pane에서 프롬프트 파일을 읽어 전달하고, 완료 시 sentinel 파일을 생성한다.

```bash
SELECTED_MODELS="claude gemini codex"   # 실제로는 설정/플래그에서 선택된 모델만 포함
CLAUDE_PANE=""
GEMINI_PANE=""
CODEX_PANE=""

# Claude
CLAUDE_PANE=$(tmux split-window -d -P -F "#{pane_id}" -h \
  "cat \"$PROMPT_FILE\" | claude -p > \"$RUN_DIR/claude-output.txt\" 2> \"$RUN_DIR/claude-log.txt\"; printf '%s' \$? > \"$RUN_DIR/claude.exit\"; touch \"$RUN_DIR/claude.done\"")

# Gemini
GEMINI_PANE=$(tmux split-window -d -P -F "#{pane_id}" -v \
  "gemini -p \"\$(cat \\\"$PROMPT_FILE\\\")\" --output-format text > \"$RUN_DIR/gemini-output.txt\" 2> \"$RUN_DIR/gemini-log.txt\"; printf '%s' \$? > \"$RUN_DIR/gemini.exit\"; touch \"$RUN_DIR/gemini.done\"")

# Codex
CODEX_PANE=$(tmux split-window -d -P -F "#{pane_id}" -v \
  "cat \"$PROMPT_FILE\" | codex exec --skip-git-repo-check --output-last-message \"$RUN_DIR/codex-output.txt\" > \"$RUN_DIR/codex-log.txt\" 2>&1; printf '%s' \$? > \"$RUN_DIR/codex.exit\"; touch \"$RUN_DIR/codex.done\"")
```

실제 실행은 `max-models`에 포함된 모델의 pane만 생성하고, pane id도 해당 모델에 대해서만 기록한다.

**완료 대기**: Bash로 sentinel 파일을 폴링한다.

```bash
START_TIME=$(date +%s)

# 선택된 모델들의 .done 파일만 기다린다
while true; do
  ALL_DONE=true
  for model in $SELECTED_MODELS; do
    [ -f "$RUN_DIR/$model.done" ] || ALL_DONE=false
  done
  $ALL_DONE && break

  NOW=$(date +%s)
  if [ $((NOW - START_TIME)) -ge $TIMEOUT_SECONDS ]; then
    for model in $SELECTED_MODELS; do
      if [ ! -f "$RUN_DIR/$model.done" ]; then
        printf '124' > "$RUN_DIR/$model.exit"
        printf 'timeout after %ss\n' "$TIMEOUT_SECONDS" > "$RUN_DIR/$model.log.txt"
        touch "$RUN_DIR/$model.timeout" "$RUN_DIR/$model.done"
      fi
    done
    [ -n "$CLAUDE_PANE" ] && tmux kill-pane -t "$CLAUDE_PANE" 2>/dev/null || true
    [ -n "$GEMINI_PANE" ] && tmux kill-pane -t "$GEMINI_PANE" 2>/dev/null || true
    [ -n "$CODEX_PANE" ] && tmux kill-pane -t "$CODEX_PANE" 2>/dev/null || true
    break
  fi

  sleep 3
done
```

> tmux pane은 실행 완료 후 자동으로 닫힌다. 진행 중에는 사용자가 pane을 전환하여 각 모델의 실행 상태를 시각적으로 확인할 수 있다. 타임아웃이 발생한 pane은 kill 후 `failed`로 표시한다.

### 모드 B: Direct dispatch (`dispatch: direct`)

tmux 없이 순차적으로 실행한다. OMC/tmux가 없는 환경에서의 fallback.

```bash
# Claude
timeout "${TIMEOUT_SECONDS}s" sh -c 'cat "$1" | claude -p > "$2" 2> "$3"' sh \
  "$PROMPT_FILE" "$RUN_DIR/claude-output.txt" "$RUN_DIR/claude-log.txt"
printf '%s' $? > "$RUN_DIR/claude.exit"
touch "$RUN_DIR/claude.done"

# Gemini
timeout "${TIMEOUT_SECONDS}s" sh -c 'gemini -p "$(cat "$1")" --output-format text > "$2" 2> "$3"' sh \
  "$PROMPT_FILE" "$RUN_DIR/gemini-output.txt" "$RUN_DIR/gemini-log.txt"
printf '%s' $? > "$RUN_DIR/gemini.exit"
touch "$RUN_DIR/gemini.done"

# Codex
timeout "${TIMEOUT_SECONDS}s" sh -c 'cat "$1" | codex exec --skip-git-repo-check --output-last-message "$2" > "$3" 2>&1' sh \
  "$PROMPT_FILE" "$RUN_DIR/codex-output.txt" "$RUN_DIR/codex-log.txt"
printf '%s' $? > "$RUN_DIR/codex.exit"
touch "$RUN_DIR/codex.done"
```

실제 실행은 `max-models`에 포함된 모델에만 적용한다. 각 명령은 `TIMEOUT_SECONDS` 상한을 두고, `timeout`이 없으면 동등한 타임아웃 래퍼(`gtimeout` 등)로 대체한다.

### 모드 C: API dispatch (`dispatch: api`)

로컬 CLI를 사용하지 않고 HTTP API를 통해 모델을 호출한다. OpenAI-compatible API 엔드포인트를 사용하며, API 키와 엔드포인트는 환경 변수에서 읽는다.

**환경 변수:**
- `PATINA_API_KEY` — API 인증 키 (필수)
- `PATINA_API_BASE` — 기본 URL (기본값: `https://api.openai.com/v1`)
- `PATINA_MODEL_CLAUDE` — claude 모델 ID (기본값: `claude-3-5-sonnet-20241022`)
- `PATINA_MODEL_GEMINI` — gemini 모델 ID (기본값: `gemini-1.5-pro-latest`)
- `PATINA_MODEL_CODEX` — codex 모델 ID (기본값: `gpt-4o`)

**모델 매핑:**

| 모델 키 | 기본 모델 ID | API 공급자 예시 |
|---------|-------------|----------------|
| claude | `claude-3-5-sonnet-20241022` | Anthropic, OpenRouter |
| gemini | `gemini-1.5-pro-latest` | Google AI Studio, OpenRouter |
| codex | `gpt-4o` | OpenAI, Azure |

**요청 형식 (OpenAI-compatible chat completions):**

```bash
API_KEY="${PATINA_API_KEY}"
API_BASE="${PATINA_API_BASE:-https://api.openai.com/v1}"
MODEL="${PATINA_MODEL_CLAUDE:-claude-3-5-sonnet-20241022}"

jq -n \
  --arg model "$MODEL" \
  --arg prompt "$(cat "$PROMPT_FILE")" \
  '{model: $model, messages: [{role: "user", content: $prompt}], temperature: 0.7}' \
| curl -s -X POST "${API_BASE}/chat/completions" \
  -H "Authorization: Bearer ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d @- \
| jq -r '.choices[0].message.content' > "$RUN_DIR/claude-output.txt"
printf '%s' $? > "$RUN_DIR/claude.exit"
touch "$RUN_DIR/claude.done"
```

**의존성:** `curl`, `jq`가 설치되어 있어야 한다.

**타임아웃:** `curl --max-time` 또는 외부 `timeout` 명령으로 `TIMEOUT_SECONDS`를 적용한다.

**결과 수집:** 공통 결과 수집 로직과 동일하다. API 오류 응답(4xx/5xx)은 `.exit` 파일에 HTTP 상태 코드를 기록하고 `failed`로 표시한다.

> **참고:** API dispatch는 Claude Code 스킬뿐 아니라 어떤 에이전트나 스크립트에서도 사용할 수 있다. `core/standalone-prompt.md`의 템플릿과 함께 사용하면, Claude Code가 아닌 환경에서도 MAX 모드의 best-of-N 선택을 재현할 수 있다.

### 결과 수집 (공통)

- `claude`
  1. `$RUN_DIR/claude-output.txt`를 Read한다
  2. `$RUN_DIR/claude.exit`가 0이고 output이 비어 있지 않으면 결과 텍스트로 사용한다
  3. 실패 또는 빈 출력이면 `$RUN_DIR/claude-log.txt`를 진단 정보로 Read한다

- `gemini`
  1. `$RUN_DIR/gemini-output.txt`를 Read한다
  2. `$RUN_DIR/gemini.exit`가 0이고 output이 비어 있지 않으면 결과 텍스트로 사용한다
  3. `$RUN_DIR/gemini-log.txt`에는 인증/확장/MCP 경고가 포함될 수 있으므로, 실패 진단용으로만 사용한다

- `codex`
  1. `$RUN_DIR/codex-output.txt`를 Read한다 (`--output-last-message`가 기록한 최종 텍스트)
  2. `$RUN_DIR/codex.exit`가 0이고 output이 비어 있지 않으면 결과 텍스트로 사용한다
  3. 실패 또는 빈 출력이면 `$RUN_DIR/codex-log.txt`를 진단 정보로 Read한다

### 실패 처리

- 특정 모델의 exit code가 0이 아니거나, output 파일이 비어 있거나, 파일이 없으면 → 해당 모델을 `failed`로 표시하고 나머지로 계속 진행
- `.timeout` 파일이 있거나 exit code가 `124`이면 → `timed out`으로 기록하고 `failed`로 표시
- `.done` 파일은 생겼지만 output 파일이 비어 있으면 → CLI가 빈 응답을 반환한 것이므로 `failed`로 표시
- 모든 모델이 실패한 경우 → 에러 메시지와 함께 각 모델의 output/log 파일 내용을 진단 정보로 출력하고 종료
- `dispatch: omc`에서 tmux가 없으면 (`$TMUX` 미설정) → 경고 후 자동으로 direct 모드로 전환

---

## 6단계: 결과 평가 (AI 유사도 점수)

각 모델의 humanize 결과에 대해 `core/scoring.md`의 수식 기반 스코어링 알고리즘을 적용한다.

2단계에서 로드한 모든 패턴 팩을 기준으로, 각 결과에 대해 SKILL.md의 `--score` 모드와 동일한 절차를 실행한다:

1. **패턴 감지**: 모든 패턴을 스캔하고, 감지된 각 패턴에 severity를 부여한다 (`core/scoring.md`의 심각도 루브릭 참조)
2. **프로필 오버라이드 적용**: pattern-overrides가 있으면 severity를 조정한다
3. **카테고리 점수 계산**: `카테고리 점수 = (조정된 심각도 합계 / (패턴 수 × 3)) × 100`
4. **전체 점수 계산**: 카테고리 점수의 가중 평균 (`ouroboros.category-weights.{lang}` 사용)

이 절차는 각 모델의 결과에 대해 독립적으로 실행한다. 모든 모델이 동일한 수식으로 평가되므로 공정한 비교가 가능하다.

각 결과에 대해 추가로 MPS(Meaning Preservation Score)를 산출한다. 원본 텍스트에서 의미 앵커를 추출하고, 각 모델의 humanize 결과가 앵커를 얼마나 보존했는지를 `core/scoring.md` §§ 14-17의 절차로 평가한다.

> **참고:** 이전 버전에서는 MAX 모드가 주관적 평가를 사용했으나, v3.2.0부터 `core/scoring.md`의 수식 기반 알고리즘으로 통합되었다. 단일 모드(`--score`)와 MAX 모드가 동일한 스코어링을 사용한다.

---

## 7단계: 비교 및 선택

결과 비교 테이블을 구성하고 최종본을 선택한다.

### 비교 테이블 형식

```
| Model   | AI Score | MPS  | Status  |
|---------|----------|------|---------|
| claude  | 23       | 92   | ✅       |
| gemini  | 31       | 85   | ✅       |
| codex   | --       | --   | failed  |

Best: claude (AI Score: 23, MPS: 92)
```

- MPS ≥ 70인 후보 중 AI 점수가 가장 낮은 결과를 자동 선택한다
- MPS < 70인 후보는 AI 점수와 관계없이 탈락한다
- 모든 후보의 MPS < 70인 경우, MPS가 가장 높은 후보를 선택한다 (의미 보존 우선)
- 동점이면 설정 파일의 모델 순서에서 먼저 오는 것을 우선한다

---

## 8단계: 출력

### 최종 출력 구성

1. **비교 테이블** — 모든 모델의 AI 점수와 상태
2. **최종본** — 가장 낮은 AI 점수를 받은 결과의 전체 텍스트
3. **카테고리별 세부 점수** — 최종본의 패턴 카테고리별 점수
4. **차점 결과 요약** — 다른 모델의 결과를 접힌 섹션으로 제공 (참고용)
5. **실패 정보** — 실패한 모델이 있으면 사유 표시
6. **임시 파일 정리** — 출력이 끝난 뒤 `rm -rf "$RUN_DIR"`로 정리

### 출력 예시

```
## MAX Mode 결과

### 비교 테이블
| Model   | AI Score | Status  |
|---------|----------|---------|
| claude  | 23       | success |
| gemini  | 31       | success |

**Best: claude (AI Score: 23)**

### 카테고리별 점수 (claude)
| Category      | Score |
|---------------|-------|
| Structure     | 15    |
| Content       | 20    |
| Language       | 25    |
| Style          | 30    |
| Communication  | 25    |
| Filler         | 20    |

### 최종본
{claude의 humanize 결과 전체 텍스트}

<details>
<summary>gemini 결과 (AI Score: 31)</summary>

{gemini의 humanize 결과 전체 텍스트}
</details>
```

단일 모델만 성공한 경우, 해당 결과를 최종본으로 제시하되 "단일 모델만 성공하여 비교 불가" 경고를 표시한다.

---

## 참고

- 이 스킬은 기존 `/patina` 스킬의 확장 버전으로, 동일한 패턴 팩과 프로필을 사용합니다
- 모델 추가/제거는 `.patina.default.yaml`의 `max-models` 또는 `--models` 플래그로 설정합니다
- 지원 모델: `claude`, `codex`, `gemini` (Claude/Codex는 stdin pipe, Gemini는 `$(cat file)` 인자 치환)
- 디스패치 모드: `omc` (tmux pane 병렬, 기본) / `direct` (순차 실행, fallback)
- 각 실행은 고유 temp dir를 사용하며, 선택된 모델만 기다리고, timeout 모델은 자동으로 `failed` 처리한다
- 최초 실행 시 설치 인터뷰로 `.patina.yaml` 생성 (모델 선택, 디스패치 모드, CLI 설치 확인)
- `--ouroboros`와 함께 사용 가능: 최고 점수가 target-score 이상이면 해당 모델 결과를 입력으로 재실행하여 점수를 수렴시킨다
