harness-export · diff
git:20260928.c57eb9e to git:20260928.6fc73a7
1 added, 0 removed. Audit A to A.
---
name: harness-export
description: Export the kit's host-neutral rules to every harness entrypoint file (AGENTS.md, GEMINI.md) so non-Claude-Code harnesses (Codex, OpenCode, Copilot, Gemini CLI, Pi, Hermes) follow the same discipline. Use when a repo is worked by more than one agent harness, or after changing rules/. Trigger with /harness-export.
+ disable-model-invocation: true
---
# Harness Export — 규범을 하네스 중립으로 내보내기
이 킷의 규범(`rules/*.md`)은 **Claude Code의 SessionStart 훅으로만** 주입된다.
그런데 Orca·Paseo 같은 ADE에서는 한 레포에 Claude Code와 Codex·OpenCode·Pi를
**동시에** 붙여 굴린다. 그 순간 레포의 절반은 이 킷의 규율 밖에서 동작한다.
이 스킬은 규범을 **각 하네스가 읽는 진입점 파일**로 내보내 그 구멍을 메운다.
| 전달 형태 | 대상 | 경로 |
| --- | --- | --- |
| 세션형 + 훅 | Claude Code | SessionStart 훅이 **자동 주입** — 이 스킬 불필요 |
| 세션형, 주입·포맷 훅 있음(신뢰 승인 후), 차단 훅 없음 | Codex | **`AGENTS.md`** — 훅 신뢰 전엔 이것이 유일한 경로(폴백). 승인 후엔 훅 주입과 **이중 도달**(아래) |
| 세션형, 세션 시작 훅 없음 | OpenCode · Copilot CLI · Cursor | **`AGENTS.md`** |
| 세션형, 세션 시작 훅 없음 | Gemini CLI 계열 | **`GEMINI.md`** |
| **API 성 호출**(세션 아님) | 래퍼가 모델을 함수처럼 부르는 레인 | **`--stdout` 으로 뽑아 프롬프트에 싣는다** |
**내용은 같다** — 규범이 하네스 중립이므로 블록도 sha 도 하나이고 파일만 여럿이다.
한 파일에만 내보내면 나머지 하네스는 규율 밖에서 돈다.
> **`CLAUDE.md` 는 의도적으로 뺐다.** Claude Code 는 이 킷의 SessionStart 훅이 규범을
> 직접 주입한다 — 파일로 또 실으면 같은 규범이 두 번 들어간다. 세션 시작 주입 훅이
> **돌지 않는** 하네스만 파일이 필요하다.
>
> **예외 — Codex 는 이중 도달을 감수한다.** Codex 는 훅 신뢰를 승인하면 세션 시작 주입
> 훅(portable 규범만)도 돌아 `AGENTS.md` 와 같은 규범이 두 번 들어간다. 그래도
> `AGENTS.md` 를 유지하는 것은, 신뢰 승인 **전에는** 훅이 조용히 건너뛰어져 `AGENTS.md`
> 가 유일한 경로이기 때문이다 — 폴백을 잃는 것이 중복보다 나쁘다.
진입점 목록을 바꾸려면 `--entrypoints AGENTS.md,GEMINI.md,OTHER.md` 로 덮어쓴다.
### API 성 호출 레인 — 파일이 개념적으로 없다
래퍼가 모델을 **함수처럼** 부르는 레인(분류·라우팅·스카우트 등)에는 세션도 진입점
파일도 없다. 규범은 **호출자가 프롬프트에 실어야** 도달한다:
```bash
NORMS="$(python3 "$EH" --stdout)" # 캐시하고 sha 로 갱신 판단
# → system 프롬프트 앞에 붙여 호출
```
**레인별로 켜라.** 규범 블록은 수천 토큰이다. 분류·라우팅처럼 응답이
수백 토큰인 레인에 매번 실으면 비용이 본문을 압도한다. 코드 생성·결정 산출처럼
**규율이 결과를 바꾸는 레인**에만 싣는다.
**전부 싣거나 아무것도 싣지 마라.** 티어별로 잘라도 절감이 10% 미만이고, 블록의
"정직한 한계" 표(*"이 규범은 강제 장치가 아니라 규율 문서다"*)는 **훅이 아예 없는
이 레인에서 더 중요하다** — 토큰을 아끼려고 그 사실을 지우면 모델이 집행이 있다고
오해한다.
## 사용 시점
| 상황 | 실행 |
| --- | --- |
| 레포를 두 개 이상의 하네스가 만진다 | `/harness-export` 1회 → 생성된 진입점 파일 전부 커밋 |
| `rules/*.md`를 고쳤다 | 재생성 (안 하면 다른 하네스가 옛 규범을 읽는다) |
| CI/게이트에서 최신 여부만 확인 | `--check` |
## 절차
### 소비자(플러그인 설치) 환경 — 기본 경로
구현은 **플러그인 안**에 있다(`<플러그인 루트>/hooks/export_harness.py`).
설치한 프로젝트에는 킷 레포의 개발용 래퍼가 없으므로 이 경로를 쓴다.
> **`$CLAUDE_PLUGIN_ROOT`를 그대로 신뢰하지 마라.** 이 변수는 스킬의 Bash 컨텍스트에
> **설정돼 있지 않을 수 있다**(kit의 `feedback.sh`·`auto-dev`가 같은 이유로 의존을
> 제거했다). 빈 값이면 `python3 "/hooks/export_harness.py"`가 되어 파일 없음으로
> **종료코드 2**가 나고, exit 표상 2는 "SKIPPED"라서 **원인을 오보고**하게 된다.
> 아래처럼 먼저 해석하라.
```bash
# 1) 구현 위치 해석 — 변수가 비면 캐시에서 찾는다
EH="${CLAUDE_PLUGIN_ROOT:-}/hooks/export_harness.py"
[ -f "$EH" ] || EH=$(ls -1 ~/.claude/plugins/cache/*/*/*/hooks/export_harness.py 2>/dev/null | sort -V | tail -1)
[ -f "$EH" ] || { echo "export_harness.py를 찾지 못했다 — 플러그인 설치 확인"; exit 2; }
# 2) 현재 프로젝트(git 최상위)의 AGENTS.md 갱신
python3 "$EH"
# 3) 드리프트 검사만 (기록하지 않음)
python3 "$EH" --check
# 4) 규범 블록을 표준출력으로 — **API 성 호출 레인의 유일한 경로**
python3 "$EH" --stdout
# 5) 다른 프로젝트 대상
python3 "$EH" --target /path/to/project
```
`--plugin-root`를 **명시**했는데 그 경로에 `rules/`가 없으면 자동 탐색으로 폴백하지
않고 exit 2다. 지정한 것과 다른 레포의 규범을 내보내고 성공을 보고하는 사고를 막는다.
명시하지 않으면 **스크립트 자기 위치**가 1순위이고(플러그인 캐시에서도 불변),
환경변수는 그 다음이다 — 셸에 남은 다른 플러그인의 값이 남의 규범을 내보내지 않도록.
### 이 킷 레포에서만 — 개발용 래퍼 (소비자 프로젝트에는 없다)
```bash
./scripts/export-harness.sh # 내보내기
./scripts/export-harness.sh --check # 드리프트 검사 (게이트가 쓰는 명령)
```
## exit code
| code | 의미 | 대응 |
| --- | --- | --- |
| 0 | 성공 / 드리프트 없음 | — |
| 1 | 드리프트 · **블록 본문 변조** · 마커 손상 · 분류 미등재/유령 엔트리 · 인코딩 실패 · 트리 밖 심링크 | **stderr의 원인을 읽어라** — 재생성으로 안 고쳐지는 종류가 있다 |
| 2 | SKIPPED — 규범 소스 미탐지 | `--plugin-root` 지정. **0으로 위장하지 말 것** |
## 불변식 [건너뛰기 금지]
1. **마커 블록 밖 불가침.** 대상의 `AGENTS.md`에서 `<!-- kit:begin ... -->` ~
`<!-- kit:end -->` 밖은 생성기가 절대 건드리지 않는다. 마커가 없는 사용자 파일은
덮어쓰지 않고 **append**한다. 이 규율을 깨는 수정은 consumer-first 위반이다.
2. **자기신고 sha를 믿지 않는다.** `--check`는 마커의 sha뿐 아니라 **블록 전문**을
생성 결과와 대조한다. sha만 보면 마커 줄을 그대로 둔 채 안쪽을 지워도 초록이 되고,
이 도구가 막겠다고 선언한 상황이 그대로 게이트를 통과한다.
3. **요약 금지.** 이식 대상 룰은 **원문 그대로** 실린다. 규범을 요약하면 원문과
의미가 갈리고, 갈린 규범은 규범이 아니다.
4. **분류 누락도, 유령 엔트리도 실패.** 새 룰을 추가하면 `hooks/export_harness.py`의 `PORTABLE` 또는
`NOT_PORTABLE`에 **사유와 함께** 등재해야 한다. 반대로 룰을 삭제·개명하면 그 엔트리를
빼야 한다 — 두면 소비자 AGENTS.md가 존재하지 않는 룰을 영구히 광고한다. 양쪽 다 exit 1.
## 마커 규약 — 자기 AGENTS.md에 마커를 *설명*하려면
블록으로 인정되는 마커는 **줄 전체를 차지하고 메타가 정확한 형식일 때뿐**이다:
| | 형식 |
| --- | --- |
| 여는 마커 | `<!-- kit:begin rules-v<버전> sha256:<64자리 hex> -->` (그 줄에 다른 내용 없음) |
| 닫는 마커 | `<!-- kit:end -->` (그 줄에 다른 내용 없음) |
**왜 이렇게 좁은가.** 2.14.0까지는 마커를 관대한 패턴으로 찾았다. 그러자 자기
`AGENTS.md`에 "이 블록이 뭔지" 설명해 둔 소비자에게 재생성을 돌렸을 때, 인용된 두
마커 **사이의 사용자 문장이 침묵 속에 삭제**되고 exit 0이 났다 — 그리고 그 뒤
`--check`는 green을 돌려줘 흔적조차 남지 않았다(2.14.1에서 수정). 이 도구의 대상
파일이 하필 "에이전트에게 규약을 설명하는 문서"라는 점에서 최악의 조합이었다.
따라서 문서에 마커를 인용할 때는 **문장 안에 인라인으로** 쓴다:
- ✅ `` 이 구간은 `<!-- kit:begin … -->` 로 시작한다. `` — 줄 앵커에 걸리지 않아 안전
- ❌ 마커만 한 줄로 적은 예시(코드 펜스 안 포함) — 형식이 정확하면 **진짜 블록으로
인정**되고, 형식이 어긋나면 "손상된 마커"로 **exit 1** 거부된다
거부는 파괴가 아니다 — 생성기는 추측해서 고치지 않고 그 줄을 사람에게 넘긴다.
## 다중 하네스 패키지와의 관계
킷은 Codex·Antigravity 용 **네이티브 플러그인 매니페스트**로도 배포된다. 둘은 **대체가 아니라
상보 관계**다:
- 이 스킬(`AGENTS.md`)은 **규범 텍스트**를 어느 하네스에서든 읽히는 자유형식 파일로 실어
나른다 — 플러그인 설치 여부와 무관하게 동작한다.
- 네이티브 패키지는 **스킬·컴포넌트**를 그 플랫폼의 설치 메커니즘으로 실어 나른다 — Codex 는
스킬만(규범 전용 필드가 없다), Antigravity 는 스킬+규범(에이전트는 비지원).
**Codex 에서 규범은 세션 시작 훅(훅 신뢰 승인 후에만 돈다) 아니면 이 스킬로만 전달된다** —
훅이 돌지 않는 환경에서는 `/harness-export` 로 만든 `AGENTS.md` 가 유일한 경로다.
## 이식되지 않는 것 (정직한 한계)
훅(`protect-sensitive`·`stop-validator`·`auto-format`), 서브에이전트 정의 전부,
그리고 Claude Code 프리미티브에 종속된 규범(frontmatter `portable: false`)은 이식되지 않는다.
생성물이 그 목록과 사유를 표로 남긴다.
**다른 하네스에서 이 킷은 규율 *문서*로 동작하지 강제 *장치*로 동작하지 않는다.**
강제가 필요하면 그 하네스의 네이티브 수단(pre-commit, CI)에 같은 검사를 건다.
## 게이트
소비자 프로젝트라면 `python3 "$EH" --check` 를 자기 CI·pre-commit 에 걸어라 — 진입점 파일이
규범보다 낡으면 막힌다. (이 킷 레포는 스스로 도그푸딩한다 — 그 레포의 완료 게이트와 CI 가
같은 `--check` 를 돈다.)