# Contributing

> 5문항 스택 기준, pytest 실행, exit 코드 규약, redaction 게이트, master-ops 템플릿 경계, 릴리스 cut·CHANGELOG.

- Repository: local/mogui-ADE-orchestrator

- Human docs: https://grok-wiki.com/public/docs/local-mogui-ade-orchestrator-97afe791d5ac
- Complete Markdown: https://grok-wiki.com/public/docs/local-mogui-ade-orchestrator-97afe791d5ac/llms-full.txt

## Source Files

- `CONTRIBUTING.md`
- `SECURITY.md`
- `CHANGELOG.md`
- `docs/internal/release-runbook.md`
- `scripts/next-version`
- `.github/workflows/gates.yml`
- `hooks/pre-push`
- `tests/conftest.py`

---

---
title: "Contributing"
description: "5문항 스택 기준, pytest 실행, exit 코드 규약, redaction 게이트, master-ops 템플릿 경계, 릴리스 cut·CHANGELOG."
---

소규모 단일 유지보수 저장소다. 이슈와 PR 모두 받는다. 런타임 코드는 표준 라이브러리만 쓰고, 검증은 `PYTHONPATH=src python3 -m pytest tests -q`와 redaction 게이트, CI(`.github/workflows/gates.yml`)로 고정한다. 이 페이지는 스택 추가 기준, 테스트·exit 규약, redaction·pre-push, `master-ops/` 템플릿 경계, 릴리스 cut 절차를 묶는다.

## 5문항 스택 기준

하네스에 도구·컴포넌트를 넣기 전에 아래 다섯 답을 PR에 적는다. 답이 없으면 나중에 빼기 논쟁조차 하기 어렵다.

| # | 질문 | 통과 실패 시 |
| --- | --- | --- |
| 1 | API 키가 필요한가? | 보통 단독으로 탈락 |
| 2 | 텔레메트리를 강제하거나, 작업에 필요한 것보다 더 수집하는가? | 보통 단독으로 탈락 |
| 3 | 관리 지점을 하나 더 만드는가? | 보통 단독으로 탈락 |
| 4 | 한 사람 운영을 넘어가도 동작하는가? | 확장성 판단 |
| 5 | 에이전트 컨텍스트 외, 이 도구가 **실제로** 해소하는 문제는 무엇인가? | 답 없으면 선호(preference)로만 허용 |

1–3을 깨는 항목은 의존성처럼 보이지만 구독에 가깝다. 1–4를 통과하고 5에 답이 없으면 선호로 라벨링하고, 그 선호에 게이트를 걸지 않는다.

이 질문은 유지보수자용이다. 설치 쪽은 스택을 고르지 않고 받은 스택에서 무엇을 켤지 고른다. 같은 다섯 문항은 온보딩 단계 `master-ops/onboarding/08-settings-and-skills.md`(라우터 `master-ops/ONBOARDING.md`)에도 실린다.

## 범위

이 저장소가 소유하는 것:

- 워크스페이스 수준 오케스트레이션: 역할, succession, worker dispatch, lineage
- `src/master_runtime/`, `scripts/`

이 저장소가 소유하지 **않는** 것:

- 저장소 로컬 rules·hooks·runbook → [mogui-agent-harness](https://github.com/baksohyeon/mogui-agent-harness)
- 디스패치 대상 에이전트 런타임 자체
- 모델 출력의 옳고 그름(수락 전 검증이 답)

개발·실행 측정 환경은 macOS / Claude Code 기준이다. Orca는 Linux·Windows 빌드를 제공하고 Linux 설치 보고가 있으나, 이 저장소 테스트는 그 플랫폼을 전면 보증하지 않는다. Windows CI 레그는 measurement-only다.

## 테스트 실행

런타임은 stdlib only. 테스트만 pytest가 필요하다.

```console
$ python3 -m pip install pytest
$ PYTHONPATH=src python3 -m pytest tests -q
```

릴리스 cut 쪽 runbook은 `uv run` 경로도 쓴다.

```console
$ PYTHONPATH=src uv run pytest tests -q
```

`tests/conftest.py`는 `tests/`를 `sys.path`에 넣어 헬퍼 공유를 허용한다. `tests/`를 패키지로 만들지 않는다(형제 모듈의 top-level import 유지).

### 머지 기준

| 변경 종류 | 기대 |
| --- | --- |
| 코드 | 그 변경 없이는 실패하는 테스트. PR에 해당 테스트 이름을 적거나, 테스트가 왜 불필요한지 설명 |
| 문서만 | 테스트 불필요. 이전 문구가 무엇이 틀렸는지 적기 |
| 통과 건수만 보고 | 불충분. 테스트 없이 통과한 브랜치와 같은 숫자일 수 있음 |

## Exit 코드 규약

여러 스크립트가 다음 삼원 모델을 쓴다.

| 코드 | 의미 |
| --- | --- |
| `0` | 깨끗함 / 후보 없음 |
| `1` | finding (발견·후보·거절 등 “판정 결과”) |
| `2` | cannot decide / undecidable (도구 없음, 입력 불명, usage, 측정 불가) |

**주의:** 처리되지 않은 예외가 `1`로 나가면 호출자는 crash와 finding을 구분하지 못한다. 실패 경로를 추가할 때 “판정 불가”는 `2`에 올려라. 이 실수가 리뷰에서 가장 큰 클래스다.

### 스크립트별 고정값

| 표면 | 0 | 1 | 2 |
| --- | --- | --- | --- |
| 일반 규약 | clean | finding | cannot decide |
| `scripts/redaction-scan.sh` | clean | findings | missing tool/required rules/usage 등 |
| `scripts/redaction-inventory` | uncovered 후보 없음 | 후보 발견(정상 triage) | 패턴 파일 없음·git repo 아님 등 |
| `scripts/next-version` | (버전 stdout) | — | bad args, `origin/main` 없음, shallow clone |

`redaction-scan.sh` 헤더는 usage·도구 오류를 문서화된 코드로 접는다. 각 스크립트 헤더의 Exit 절을 읽고 가정하지 마라.

셸에서 exit를 잡을 때 파이프 뒤에 `$?`를 두지 않는다.

```bash
out=$(cmd 2>&1); rc=$?
# 파이프 끝의 $?는 파이프라인 마지막 명령의 것이다
```

## Redaction 게이트

### `redaction-scan.sh`

gitleaks를 엔진으로 쓰고, 이 스크립트가 스코프·커밋 메시지·조직 규칙 주입·커버리지 선언을 맡는다.

```console
$ ./scripts/redaction-scan.sh                 # tracked 전체
$ ./scripts/redaction-scan.sh --staged        # index만
$ ./scripts/redaction-scan.sh --range A..B    # 범위 파일 + 해당 커밋 메시지
```

환경:

| 변수 | 역할 |
| --- | --- |
| `REDACTION_EXTRA_PATTERNS` | 조직 전용 규칙 파일 경로. 형식 `id\|description\|regex` (줄당 1). 공개 저장소에 커밋하지 않음 |
| `REDACTION_REQUIRE_EXTRA=1` | extra 파일 없거나 비면 exit `2` (generic만으로 조용히 통과 금지) |

예외는 gitleaks 메커니즘: `.gitleaksignore` fingerprint 또는 `config/gitleaks.toml` 클래스 단위.

### `redaction-inventory`

스캔의 역: 규칙이 가리키지 않는 토큰 후보를 보고한다. 후보 ≠ secret, 빈 결과 ≠ 안전 증명.

```console
$ REDACTION_EXTRA_PATTERNS=~/.config/redaction-extra.txt ./scripts/redaction-inventory
$ ./scripts/redaction-inventory --baseline .redaction-inventory-baseline
$ ./scripts/redaction-inventory --json
```

바이너리는 처음 8KB 안의 NUL 바이트로 휴리스틱 판별 후 스킵한다. 출력은 여전히 “tracked 전체”처럼 보일 수 있어 silent다.

### 스테이징 전제

스캐너는 **tracked** 내용만 읽는다. unstaged 새 파일은 보이지 않고 스캔이 green으로 돌아올 수 있다.

```console
$ git add -A
$ ./scripts/redaction-scan.sh
```

### Pre-push 훅

클론당 한 번:

```console
$ git config core.hooksPath hooks
```

`hooks/pre-push`는 push 범위에 대해 `redaction-scan.sh --range base..local_sha`를 돌린다. tip-of-tree만 보면 중간 커밋에 있었다가 사라진 비밀을 놓친다(이 저장소의 실제 유출 형태). 범위를 못 잡으면 tracked tree 전체로 fallback. **테스트 스위트는 훅에 넣지 않는다** — 느린 훅은 `--no-verify`로 우회된다. 훅은 조직 규칙을 강제하지 않는다(기여자는 private 규칙을 가질 수 없음).

### CI

`.github/workflows/gates.yml` — `pull_request`와 `main` push.

| job | 내용 | 비고 |
| --- | --- | --- |
| `tests` | Python 3.12, pytest, gitleaks 설치(pinned+checksum) | `ubuntu`/`macos` blocking; `windows-latest`는 `continue-on-error` measurement-only |
| `redaction` | `./scripts/redaction-scan.sh` (committed ruleset) | `redaction-inventory`는 informational (`|| true`) |

CI는 커밋된 규칙만 돌린다. `REDACTION_EXTRA_PATTERNS` 전체 스캔은 여전히 로컬이다. gitleaks는 redaction job뿐 아니라 tests job에도 설치한다(커밋 메시지 스캔 테스트가 엔진을 실측함).

## master-ops 템플릿 경계

`master-ops/`는 **템플릿**이다. 온보딩 시 사용자 ops 저장소로 복사된다. 이 저장소에 대한 문서가 아니다.

| 사실 | 결과 |
| --- | --- |
| 템플릿 변경 | **신규** 설치에만 도달 |
| 기존 설치 | 복사본; 자동 갱신 없음 |
| 템플릿 버전 | `master-ops/TEMPLATE-VERSION`, `master-ops/MANIFEST.json` |
| 템플릿 changelog | `master-ops/CHANGELOG.md` — 오케스트레이터 `CHANGELOG.md`와 **독립** 버전 |
| 업그레이드 | `scripts/template-check` / `scripts/template-apply` (dry-run 먼저; instance-owned 경로 거절) |

`master-ops/`를 건드리면 같은 변경에 `master-ops/CHANGELOG.md` `## Unreleased` 항목을 넣는다. `TEMPLATE-VERSION`은 릴리스 cut 때만 이동한다.

보안 범위에서도 템플릿은 out of scope: 복사 후 사용자가 실행 전에 검토한다.

## 커밋·PR

- Conventional commits, 영어: `feat(scope):`, `fix(scope):`, `docs(scope):`
- 무엇을 바꿨고 왜 필요했는지
- PR은 squash merge
- AI가 유지보수자 지도 아래 쓴 커밋은 `Co-Authored-By` trailer에 모델명. 트레일러 부재 = 관례 이전 작성이지, 반드시 수기라는 뜻은 아님

## 릴리스 cut

버전 형식: `MAJOR.MINOR.BUILD`.

| 자리 | 주체 |
| --- | --- |
| `MAJOR`, `MINOR` | 오너만 수동. 자동화 금지. `scripts/next-version`의 `OWNER_MANAGED_MAJOR_MINOR` (현재 `0.5`) |
| `BUILD` | cut 시점 `git rev-list --count refs/remotes/origin/main` |

major가 0인 동안 공개 표면은 불안정할 수 있다. CLI 플래그·파일 형식·모듈 인터페이스가 minor에서 바뀔 수 있다.

### 절차

<Steps>
  <Step title="Sync and derive">
    shallow면 unshallow. `origin/main`과 tags를 fetch한 뒤:

```console
$ version="$(./scripts/next-version)"
$ printf '%s\n' "$version"
```

`origin/main` 없거나 shallow면 `next-version` exit `2`.
  </Step>
  <Step title="Stage before redaction">
```console
$ git add -A
```
    unstaged 새 파일은 스캐너에 안 보인다.
  </Step>
  <Step title="Release gates">
```console
$ set -e
$ PYTHONPATH=src uv run pytest tests -q
$ ./scripts/redaction-scan.sh
$ rc=0
$ ./scripts/redaction-inventory || rc=$?
$ if [ "$rc" -ne 0 ]; then [ "$rc" -eq 1 ] || exit "$rc"; fi
```
    inventory exit `1`은 정상 triage. exit `2`는 cut 차단.
  </Step>
  <Step title="CHANGELOG">
    오케스트레이터 `CHANGELOG.md`에 `v${version}` 노트. Keep a Changelog 형식. 링크·날짜 확인. `master-ops/` 변경이 있으면 템플릿 changelog도 정리.
  </Step>
  <Step title="Tag (owner only)">
    **명시적 오너 승인 후에만**:

```console
$ [ -n "${version:-}" ] || exit 1
$ git tag "v${version}"
```

    태그 생성·push 자동화 금지. push도 오너가 요청할 때만.
  </Step>
</Steps>

## 보안 보고

공개 이슈에 취약점을 올리지 않는다. GitHub private vulnerability reporting을 쓴다. SLA 없음; 대략 1주 내 수신 확인, 1개월 내 수정 또는 결정을 현실적으로 기대한다. major 0 동안 최신 릴리스만 지원, backport 없음.

In scope 요약: `src/master_runtime/`, `scripts/`, 요청 밖 명령 실행·경로 읽기·디스패치, 스캐너가 읽지 않은 범위를 clean으로 보고하는 경우.

문서화된 한계(취약점으로 취급하지 않음): tracked-only 스캔, inventory 바이너리 silent skip. 설명보다 심각한 형태면 보고 대상.

## 로컬 PR 전 체크리스트

```console
$ PYTHONPATH=src python3 -m pytest tests -q
$ git add -A   # 새 파일 포함
$ ./scripts/redaction-scan.sh
$ ./scripts/redaction-inventory || true   # 1 = triage, 2 = 차단
```

선택: `git config core.hooksPath hooks`로 push 전 range 스캔.

PR 본문에 넣을 것:

1. (스택 추가 시) 5문항 답
2. 실패 없이 통과하지 않는 테스트 이름, 또는 문서-only 이유
3. exit `2` 경로를 건드렸다면 호출자가 finding과 혼동하지 않는지

## Related pages

<CardGroup>
  <Card title="Redaction gates" href="/redaction-gates">
    redaction-scan 범위·exit, REDACTION_REQUIRE_EXTRA, inventory, gitleaks, pre-push
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    preflight BLOCKED, undecidable exit 2, placement·probe 복구
  </Card>
  <Card title="방어 인벤토리" href="/defense-inventory">
    디스패치 게이트, probe, redaction, revival, onboarding 가드 표
  </Card>
  <Card title="온보딩" href="/onboarding">
    ONBOARDING 라우터, 템플릿 치환 경계, Stage 1/2
  </Card>
  <Card title="Installation" href="/installation">
    전제조건, preflight, 클론 후 측정 신호
  </Card>
  <Card title="Overview" href="/overview">
    공개 표면, Orca 전제, 마스터/워커 역할
  </Card>
</CardGroup>
