보일러플레이트 — 바로 가져다 쓰는 한 묶음
이 트랙에서 다룬 래퍼·hook·슬래시 커맨드·컨텍스트 파일을 복사해서 바로 시작할 수 있게 한곳에 모았어요.
이 챕터는 본인 레포에 그대로 복사해서 쓸 수 있는 파일 묶음이에요. 빈 골격은 본인 도메인·팀 룰로 채우면 돼요.
폴더 구조
your-repo/
├── .claude/
│ ├── commands/
│ │ └── ship.md
│ └── settings.local.json
├── .git/hooks/
│ └── pre-push ← git native hook (트래킹 X, 본인 머신 전용)
├── scripts/
│ └── codex-review.sh
├── AGENTS.md ← Codex가 읽음
├── CLAUDE.md ← Claude Code가 읽음
└── package.json
머신 이전·팀 공유 등으로 hook을 살려두려면 6장 끝의 "hook을 다른 머신에서도 살리고 싶다면" 절(dotfiles 백업 또는
git config core.hooksPath)을 참고. 1인이고 머신을 잘 안 옮기면 git native로 충분해요.
설치 명령 한 묶음
# 0) git 레포 안에서 실행 (필요하면 git init 먼저)
git rev-parse --git-dir >/dev/null 2>&1 || git init
# 1) Codex CLI
npm i -g @openai/codex
codex login # 또는: export OPENAI_API_KEY=sk-...
# 2) hook 파일 자리 잡기 (기존 core.hooksPath가 있으면 그쪽 사용)
HOOK_DIR=$(git config --get core.hooksPath || echo .git/hooks)
touch "${HOOK_DIR}/pre-push"
chmod +x "${HOOK_DIR}/pre-push"
# (아래 .git/hooks/pre-push 본문을 그대로 복붙)
# 3) wrapper 스크립트 실행 권한
chmod +x scripts/codex-review.sh
scripts/codex-review.sh
두 호출 모드(--base <ref> 위임 / 자유 prompt) 지원, JSON envelope 기본 + --raw Markdown, bash-native 타임아웃.
#!/usr/bin/env bash
# Advisory Codex review wrapper
# Usage:
# ./codex-review.sh --base origin/main [--timeout 120] [--raw]
# ./codex-review.sh "<prompt>" [--timeout 120] [--raw]
# ./codex-review.sh -- "-flag-prompt" # use -- to pass a prompt starting with -
#
# Always exits 0 — never blocks the caller.
# Default output: JSON envelope { status, reason, body }
# With --raw: human-readable Markdown for terminal display.
set -u
TIMEOUT=120
BASE_REF=""
PROMPT=""
RAW=0
json_escape() {
if command -v jq >/dev/null 2>&1; then
jq -Rs .
else
awk 'BEGIN{ORS=""; printf "\""}
{gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); gsub(/\r/,"\\r"); gsub(/\t/,"\\t"); printf "%s\\n",$0}
END{printf "\""}'
fi
}
emit() {
local status="$1" reason="$2" body_file="${3-}"
if [[ $RAW -eq 1 ]]; then
echo "## [$status] $reason"
if [[ -n "$body_file" && -s "$body_file" ]]; then
echo
cat "$body_file"
fi
else
local body='""'
if [[ -n "$body_file" && -s "$body_file" ]]; then
body=$(json_escape < "$body_file")
fi
printf '{"status":"%s","reason":"%s","body":%s}\n' "$status" "$reason" "$body"
fi
}
need_value() {
local flag="$1" val="${2-__MISSING__}"
if [[ "$val" == "__MISSING__" ]]; then
emit "ERROR" "missing value for ${flag}"
exit 0
fi
printf '%s' "$val"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--base) BASE_REF=$(need_value --base "${2-__MISSING__}"); shift 2 ;;
--timeout) TIMEOUT=$(need_value --timeout "${2-__MISSING__}"); shift 2 ;;
--raw) RAW=1; shift ;;
--) shift; PROMPT="${1-}"; break ;;
-*) emit "ERROR" "unknown flag: $1"; exit 0 ;;
*) PROMPT="$1"; shift ;;
esac
done
run_with_timeout() {
local seconds="$1"; shift
"$@" &
local pid=$!
local waited=0
while kill -0 "$pid" 2>/dev/null; do
if (( waited >= seconds )); then
kill -TERM "$pid" 2>/dev/null
local grace=0
while (( grace < 2 )) && kill -0 "$pid" 2>/dev/null; do
sleep 1
grace=$((grace + 1))
done
if kill -0 "$pid" 2>/dev/null; then
kill -KILL "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
return 124
fi
wait "$pid" 2>/dev/null
return $?
fi
sleep 1
waited=$((waited + 1))
done
wait "$pid" 2>/dev/null
return $?
}
if [[ -z "$BASE_REF" && -z "$PROMPT" ]]; then
emit "ERROR" "neither --base nor prompt provided"
exit 0
fi
if ! command -v codex >/dev/null 2>&1; then
emit "SKIPPED" "codex CLI not installed"
exit 0
fi
if [[ -z "${OPENAI_API_KEY:-}" ]] && ! codex login status >/dev/null 2>&1; then
emit "SKIPPED" "codex not authenticated (run \`codex login\` or set OPENAI_API_KEY)"
exit 0
fi
OUT_FILE=$(mktemp "${TMPDIR:-/tmp}/codex-review.XXXXXX")
trap 'rm -f "$OUT_FILE"' EXIT
if [[ -n "$BASE_REF" ]]; then
CODEX_ARGS=(
review
-c model_reasoning_effort=high
-c approval_policy=never
--base "$BASE_REF"
--output-last-message "$OUT_FILE"
)
else
CODEX_ARGS=(
exec
-c model_reasoning_effort=high
-c approval_policy=never
--output-last-message "$OUT_FILE"
"$PROMPT"
)
fi
run_with_timeout "$TIMEOUT" codex "${CODEX_ARGS[@]}" >/dev/null 2>&1
EXIT_CODE=$?
case "$EXIT_CODE" in
0) emit "OK" "advisory review" "$OUT_FILE" ;;
124) emit "TIMEOUT" "codex timed out after ${TIMEOUT}s" ;;
*) emit "ERROR" "codex exited with code ${EXIT_CODE}" ;;
esac
exit 0
.git/hooks/pre-push
base 브랜치 fallback 체인 + 시크릿 두 단계 방어선 + SKIP_CODEX=1 escape hatch.
#!/usr/bin/env bash
# Advisory Codex review on push — never blocks.
set -u
if [[ "${SKIP_CODEX:-0}" == "1" ]]; then
echo "[pre-push] SKIP_CODEX=1, skipping advisory review"
exit 0
fi
resolve_base() {
if [[ -n "${CODEX_BASE_BRANCH:-}" ]]; then
echo "$CODEX_BASE_BRANCH"; return
fi
for candidate in origin/main origin/develop main develop; do
if git rev-parse --verify "$candidate" >/dev/null 2>&1; then
echo "$candidate"; return
fi
done
echo ""
}
BASE=$(resolve_base)
if [[ -z "$BASE" ]]; then
echo "[pre-push] no base branch found (tried: \$CODEX_BASE_BRANCH, origin/main, origin/develop, main, develop). skipping."
exit 0
fi
CHANGED=$(git diff --name-only "${BASE}...HEAD" 2>/dev/null)
if [[ -z "$CHANGED" ]]; then
echo "[pre-push] no changes vs ${BASE}, skipping advisory review"
exit 0
fi
# 시크릿 방어선 1: 파일 패턴 → push abort (advisory와 별개의 결정론적 게이트)
SECRET_FILE_PATTERNS='(^|/)\.env($|\.|/)|\.(pem|key|p12|pfx|cer|crt)$|(^|/)secrets/|(^|/)credentials/|(^|/)private-key'
SECRET_FILE_ALLOWLIST='\.(example|sample|template|dist)(\.[a-zA-Z0-9]+)?$'
SECRET_HITS=$(echo "$CHANGED" \
| grep -E "$SECRET_FILE_PATTERNS" \
| grep -E -v "$SECRET_FILE_ALLOWLIST" || true)
if [[ -n "$SECRET_HITS" ]]; then
echo "[pre-push] ⛔ SECRET FILES detected — push aborted (deterministic gate):"
echo "$SECRET_HITS"
echo "If intentional: SKIP_CODEX=1 git push (review your secret hygiene first)"
exit 1
fi
# 시크릿 방어선 2: 인라인 정규식 → 경고만, 진행
DIFF_TEXT=$(git diff "${BASE}...HEAD" -- \
'*.ts' '*.tsx' '*.js' '*.jsx' '*.py' '*.sql' '*.go' '*.rs' \
2>/dev/null)
INLINE_PATTERNS='(api[_-]?key|secret|password|token)[[:space:]]*[:=][[:space:]]*['"'"'"][^'"'"'"]{16,}|Bearer[[:space:]]+[A-Za-z0-9._-]{20,}|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}'
if echo "$DIFF_TEXT" | grep -E -i -q "$INLINE_PATTERNS"; then
echo "[pre-push] ⚠️ inline secret-like pattern detected — please review"
fi
echo "─────────────────────────────────"
echo " Advisory Codex Review (push proceeds regardless)"
echo " base: ${BASE}"
echo "─────────────────────────────────"
bash scripts/codex-review.sh --base "$BASE" --raw --timeout 90
echo "─────────────────────────────────"
exit 0
위 예시는 포그라운드 기본이에요. push가 60~120초 늦어지지만 결과를 같은 화면에서 봐요. 백그라운드로 돌릴 거면 결과를
.git/codex-review.log에tee로 남기는 변형(6챕터 참고)을 써요.베이스 브랜치 결정 순서:
CODEX_BASE_BRANCHenv var →origin/main→origin/develop→main→develop. 본인 트렁크가 다르면.envrc나 CI 변수로CODEX_BASE_BRANCH=origin/release/2026-q2같이 export.
.claude/commands/ship.md
# /ship — Implementation Pipeline
Run the full implementation flow for an approved spec.
Usage: /ship <spec-id>
## Pipeline
### Phase 0: 사전 동기화 (블로킹)
- 외부 스펙/스키마 sync 명령 실행
- 실패 시 중단
### Phase 1: 기획 (사용자 승인 게이트)
- planner 서브에이전트 호출 → 작업 계약서 생성
- **STOP HERE and wait for user approval. Do NOT proceed without explicit approval.**
### Phase 2: 구현 (블로킹)
- logic-builder 서브에이전트 → API 모듈, 훅, 스키마, 폼 검증
- page-assembler 서브에이전트 → 위 결과 조립 (훅 재구현 금지)
### Phase 3: 검증 (블로킹)
- ui-reviewer / qa 서브에이전트
- 린트·타입·테스트
- 자동 수정 시도 후 재실행 (최대 2회)
### Phase 3.5: 교차 리뷰 (advisory)
- 베이스 브랜치 결정 (`CODEX_BASE_BRANCH` 또는 fallback)
- `bash scripts/codex-review.sh --base "$BASE"` 호출 (JSON envelope)
- `jq -r '.body'`로 본문 추출해서 보고에 첨부
- CRITICAL 발견해도 **다음 페이즈 진행**
### Phase 4: 보고
- 페이즈별 결과 요약
- Codex 결과는 advisory 섹션으로 분리해서 표시
## Rules
- NEVER skip Phase 1 (sprint contract).
- NEVER proceed past Phase 1 without user approval.
- NEVER let page-assembler re-implement hooks that logic-builder already created.
- NEVER bypass advisory — but advisory NEVER blocks.
Task: $ARGUMENTS
본인 도메인에 맞게 서브에이전트 이름·페이즈를 조정해요.
planner,logic-builder,page-assembler,ui-reviewer,qa는 예시 역할이에요. 단순한 프로젝트면 Phase 2를 한 단계로 합쳐도 돼요.
CLAUDE.md 시작 템플릿
# CLAUDE.md
> Claude Code가 자동으로 읽는 컨텍스트 파일. 작성자(메인 에이전트)에게 알려줄 것을 적어요.
## 이 레포 한 줄 소개
{한 문장으로 요약}
## 빌드 / 테스트 / 린트
- `pnpm dev` — 개발 서버
- `pnpm build` — 프로덕션 빌드
- `pnpm lint` — ESLint
- `pnpm preflight` — 위 셋을 묶은 게이트
## 코드 컨벤션
- {네이밍 룰}
- {폴더 구조}
- {임포트 순서}
## 자주 쓰는 라이브러리
- {선호 라이브러리와 사용 패턴}
## 도메인 용어
- {약어/특수 용어 → 풀이}
## 자주 하는 실수와 회피법
- {여기에 작업하면서 발견한 패턴을 한 줄씩 추가}
## 새 기능 시작 시
- {어디서부터 짜기 시작해야 하는지}
## 절대 금지 (양쪽 파일 동일 — 그대로 복붙)
- 시크릿/`.env` 커밋 금지
- `--force` push, `--no-verify` commit 금지
- {도메인 금지 패턴 — 예: 결제 흐름은 트랜잭션 필수}
- {도메인 금지 패턴}
AGENTS.md 시작 템플릿
# AGENTS.md
> Codex가 자동으로 읽는 컨텍스트 파일. advisory 리뷰어에게 검토 우선순위를 알려줘요.
## 이 레포 한 줄 소개 (작성자와 동일)
{CLAUDE.md와 동일한 한 문장 — 그대로 복붙}
## 검토 우선순위 (높음 → 낮음)
1. 보안 — 시크릿 노출, 인증/인가, SQL 인젝션
2. 데이터 무결성 — 트랜잭션, 동시성, 마이그레이션 안전성
3. 타입 안정성 — `any` 캐스팅, 누락된 null 체크
4. 도메인 무결성 — 아래 룰 위반
5. 성능 — N+1, 무한 리렌더, 큰 번들
6. (스타일/컨벤션은 리뷰 대상 아님 — 노이즈)
## 절대 금지 (양쪽 파일 동일 — 그대로 복붙)
- 시크릿/`.env` 커밋 금지
- `--force` push, `--no-verify` commit 금지
- {도메인 금지 패턴 — 예: 결제 흐름은 트랜잭션 필수}
- {도메인 금지 패턴}
## 도메인 무결성 룰
- {예: 사용자 ID는 항상 인증 컨텍스트에서 가져와야 함, request body 신뢰 X}
- {도메인 룰}
## 빌드 / 테스트 / 린트 (작성자와 동일 — 그대로 복붙)
- `pnpm dev` — 개발 서버
- `pnpm build` — 프로덕션 빌드
- `pnpm lint` — ESLint
- `pnpm preflight` — 위 셋을 묶은 게이트
## 리뷰 출력 형식
각 발견은 다음 형식으로:
[SEVERITY] path/to/file.ts:LINE — 한 줄 설명
SEVERITY: CRITICAL / WARNING / INFO
## 무시할 것
- 스타일 의견 (프리티어 / 임포트 순서 등)
- "이렇게 하면 더 좋을 것 같다" 류 일반론
- CLAUDE.md의 작성 컨벤션과 충돌하는 일반 모범 사례
## Security & Privacy
1. 외부 모델 전송 — advisory 리뷰는 diff/컨텍스트를 OpenAI(Codex)에 전송. NDA 금지면 비활성화
2. 절대 전송 금지 (pre-push가 자동 차단) — `.env*`, `*.pem|key|p12|pfx|cer|crt`, `secrets/`, `credentials/`, `private-key*`
3. 인라인 시크릿 패턴 (경고만) — `api_key/secret/password/token = "..."` 16자+, `Bearer <20+>`, `sk-...`, `ghp_...`, `AKIA[0-9A-Z]{16}`
4. 리뷰 결과 — 로컬 `.git/codex-review.log` (git untracked, 7일 후 삭제), CI artifact 7~14일 보존, PR 관련자만 접근
5. 인증 — `codex login` (OAuth, `~/.codex/`) 우선, 대안 `OPENAI_API_KEY` (회사 SSO 권장). git-tracked 파일 X
6. Escape hatch — 임시: `SKIP_CODEX=1 git push`. 장기 비활성: hook 제거 + 사유 PR
보안 사전 체크리스트 (적용 전 필수)
이 보일러플레이트를 그대로 깔기 전에 6가지를 먼저 통과시켜요. 7챕터의 보안/운영 섹션을 운영용 한 줄짜리 체크리스트로 압축한 거예요.
- NDA/계약 검토 — 고객사 코드가 외부 모델 제공자로 나가도 되는가? 막히면 해당 레포에 hook 두지 말 것
- Secret scanning 선행 —
gitleaks/trufflehogpre-commit hook이 advisory보다 먼저 돌아 시크릿이 push 전에 차단되는지 확인 -
.gitignore점검 —.env*,*.pem,*.key,secrets/**,credentials.json등록 - API 키 위치 —
OPENAI_API_KEY/ANTHROPIC_API_KEY는.envrc(direnv) 또는 OS keychain에만. 레포 트래킹 X - 로그 회전 —
.git/codex-review.log를 쓰는 환경이면 7일 이상 묵은 파일 자동 정리 (find .git -name 'codex-review.log' -mtime +7 -delete) - CI 결과 보관 정책 — advisory artifact 보존 기간 7~14일, PR 작성자/리뷰어 이외 접근 차단
체크리스트 한 칸이라도 안 채워지면 advisory를 끄는 게 맞아요. 사고 비용이 편익보다 훨씬 커요.
이제 본인 워크플로우로
위 묶음을 본인 레포에 복사하고 다음을 채워요.
CLAUDE.md도메인 용어·자주 하는 실수 — 첫 1주 작업하면서 한 줄씩 추가AGENTS.md도메인 무결성 룰 — 5~10개로 시작, 늘리기보다 줄이기.claude/commands/ship.md페이즈 — 본인 프로젝트의 실제 단계로 재구성.git/hooks/pre-pushCODEX_BASE_BRANCH— 트렁크가 main/develop이 아니면.envrc/CI 변수로 exportscripts/codex-review.sh타임아웃 — 팀의 평균 push 흐름에 맞춰 60~180초 사이에서 조정AGENTS.mdSecurity & Privacy — 고객사 NDA·사내 보안 정책 반영해서 1번/6번 항목 확정
7챕터에서 정리한 안티패턴과 보안 체크리스트를 주기적으로 다시 점검해요. 특히 advisory가 차단으로 변하는 순간, 또는 시크릿 누출 가능성이 한 번이라도 발견되면 즉시 되돌려요. 이중 구조의 핵심은 두 도구가 서로의 게이트가 되지 않는 것이에요.