299 lines
16 KiB
Markdown
299 lines
16 KiB
Markdown
# 3-플랫폼 동기화 Phase 2 (마지막) — hooks + 프로젝트 지침 + 정리 Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans. Steps use checkbox (`- [ ]`) syntax.
|
|
|
|
**Goal:** Claude의 결정론 훅(`wiki_structure_lint.py` 구조 린트 + `wiki_claim_gate.py` claim 게이트)을 Codex CLI(`.codex/hooks.json`)에 연결하고, 세 플랫폼이 동일한 프로젝트 지침(`CLAUDE.md`)을 읽도록 codex `AGENTS.md`/config를 구성하며, Phase 0~1에서 생긴 문서 잔여를 정리해 동기화를 완결한다.
|
|
|
|
**Architecture:** 훅 스크립트는 이미 멀티-variant 입력 리더(`tool_name` / `tool_call.name`)를 갖췄지만 *차단 규약*과 *출력 스키마*가 플랫폼마다 다르다(Claude: exit 2+stderr / Antigravity: `{decision:deny}` JSON / Codex: 경험적 확인 필요). 따라서 Phase 2는 **(1) 이식성 버그 선수정**(claim_gate 하드코딩 ROOT) → **(2) codex 훅 payload·차단 규약 경험적 캡처**(codex 설치됨) → **(3) `.codex/hooks.json` 배선 + 필요 시 codex-variant 어댑트** → **(4) 프로젝트 지침 DRY 구성** → **(5) CLAUDE.md 잔여 정리** 순으로 간다. 훅 *스크립트 SSOT* 는 `.claude/hooks/` 1벌을 유지하고 플랫폼별 config 가 그것을 가리킨다(스크립트 중복 금지).
|
|
|
|
**Tech Stack:** Python 3.12, codex-cli 0.136.0, agy(Antigravity), pytest(venv).
|
|
|
|
---
|
|
|
|
## 배경 / 현재 훅 지형 (실측)
|
|
|
|
| 훅 | 위치 | root 해석 | 입력 | 차단 규약 |
|
|
|---|---|---|---|---|
|
|
| `wiki_structure_lint.py` | `.claude/hooks/` | `DEFAULT_ROOT = SCRIPT.parents[2]` (동적·이식 OK) | `--hook`(stdin JSON→file_path, C2 non-blocking) / `--file` / `--all` | non-blocking(경고) |
|
|
| `wiki_claim_gate.py` | `.claude/hooks/` | **`ROOT=Path("/home/donghyeon/Documents/LLM Wiki")` 하드코딩 — 이 repo 경로 아님(버그)** | stdin JSON (`tool_name`/`tool_call.name`/`hook_event_name`) | Claude: exit 2 + stderr |
|
|
| `wiki_hard_gate.py` | `~/.gemini/antigravity-cli/hooks/` (global) | — | antigravity protobuf (`tool_call.{name,input}`) | Antigravity: stdout `{decision:"deny"}` |
|
|
|
|
- Claude `.claude/settings.json`: PreToolUse(claim_gate, matcher `*`) + PostToolUse(structure_lint `--hook`, matcher `Write|Edit|MultiEdit`) + SubagentStart/Stop(claim_gate). 명령은 `python3 "$CLAUDE_PROJECT_DIR"/.claude/hooks/<script>`.
|
|
- Antigravity `.agents/hooks.json`: `wiki-hard-gate` PreToolUse(matcher `*`) → global `wiki_hard_gate.py`. 이미 claim/self-grep 게이트 역할 수행.
|
|
- Codex hooks(공식, `developers.openai.com/codex/hooks`): `<repo>/.codex/hooks.json` 또는 `config.toml [hooks]`. 이벤트 PreToolUse/PostToolUse/Stop/SessionStart/SubagentStart/SubagentStop 등. matcher(regex)+command. **payload·차단 규약은 본 plan Task 2 에서 경험적 캡처.**
|
|
- Codex 프로젝트 지침: `AGENTS.md`(root-down 연결). config `project_doc_fallback_filenames` 로 다른 파일명을 지침으로 인정 가능 → **CLAUDE.md 를 codex 지침으로 재사용(DRY)** 가능.
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
- Modify: `.claude/hooks/wiki_claim_gate.py` — 하드코딩 ROOT → 동적(`SCRIPT.parents[2]` 또는 `$CLAUDE_PROJECT_DIR`/cwd). 모든 플랫폼 공용 버그픽스.
|
|
- Create: `.codex/hooks.json` — structure_lint(PostToolUse) + claim_gate(PreToolUse) 배선.
|
|
- Create: `.codex/config.toml` — `project_doc_fallback_filenames = ["CLAUDE.md"]` (codex 가 CLAUDE.md 를 지침으로 읽도록, DRY).
|
|
- Create (조건부): `AGENTS.md`(root) — config fallback 이 동작 안 하면 thin pointer 대안.
|
|
- Create: `docs/superpowers/notes/2026-06-04-phase2-codex-hook-schema.md` — codex 훅 payload·차단 규약 경험적 기록.
|
|
- Modify (정리): `CLAUDE.md` §2 표(line 48 `.claude/commands/` 행에 codex/antigravity 등가 1줄) + 자동화 섹션 잔여 "7개" 카운트/구식 서술.
|
|
|
|
> 훅 *스크립트* 는 `.claude/hooks/` 1벌만 SSOT. codex `.codex/hooks.json` 은 절대경로/`$CODEX_PROJECT_DIR` 로 그 스크립트를 가리키며 복제하지 않는다.
|
|
|
|
---
|
|
|
|
## Task 1: claim_gate 하드코딩 ROOT 버그픽스 (공용 선수정)
|
|
|
|
**Files:**
|
|
- Modify: `.claude/hooks/wiki_claim_gate.py`
|
|
- Test: `.claude/hooks/` 기존 테스트가 있으면 거기, 없으면 `scripts/test_sync_automation.py` 밖의 별도 확인.
|
|
|
|
- [ ] **Step 1: 현재 ROOT 사용처 확인**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
grep -nE "ROOT" .claude/hooks/wiki_claim_gate.py
|
|
```
|
|
사용처: L20 정의, L81 `ROOT / p`(상대→절대), L149 `relative_to(ROOT)`.
|
|
|
|
- [ ] **Step 2: 동적 root 로 교체**
|
|
|
|
`structure_lint.py` 와 동일 패턴 적용. L20 을:
|
|
```python
|
|
ROOT = Path(__file__).resolve().parents[2] # .claude/hooks/<this> -> repo root
|
|
```
|
|
로 교체(환경변수 우선이 필요하면 `Path(os.environ.get("CLAUDE_PROJECT_DIR") or Path(__file__).resolve().parents[2])` — 단 codex 는 `CODEX_PROJECT_DIR`, antigravity 는 다른 변수일 수 있으므로 **파일 위치 기반이 가장 이식적**. 파일 위치 기반으로 간다).
|
|
|
|
- [ ] **Step 3: 동작 확인 (allow 경로 + relative_to 안전)**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
# 무해한 read 툴 이벤트는 allow(exit 0)
|
|
echo '{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"README.md"}}' | python3 .claude/hooks/wiki_claim_gate.py; echo "exit=$?"
|
|
# repo 내부 경로가 relative_to 에서 깨지지 않음
|
|
echo '{"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":"raw/branch-notes/x.md","content":"---\ntitle: t\n---\n"}}' | python3 .claude/hooks/wiki_claim_gate.py; echo "exit=$?"
|
|
```
|
|
Expected: 첫 명령 `exit=0`. 둘째는 게이트 규칙에 따라 0 또는 2 — **ValueError/traceback 이 없어야** 함(하드코딩 ROOT 였으면 `relative_to` 에서 깨졌음).
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add .claude/hooks/wiki_claim_gate.py
|
|
git commit -m "fix(hooks): claim_gate uses dynamic repo root (was hardcoded stale path) — portable across platforms"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: codex 훅 payload·차단 규약 경험적 캡처
|
|
|
|
**Files:**
|
|
- Create: `docs/superpowers/notes/2026-06-04-phase2-codex-hook-schema.md`
|
|
|
|
> codex 훅이 PreToolUse 에 넘기는 JSON 구조와 *차단 방법*(exit code? stdout JSON `decision`?)을 실제로 캡처해야 `.codex/hooks.json` 을 정확히 쓸 수 있다.
|
|
|
|
- [ ] **Step 1: codex 훅 문서/도움말 확인**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
codex --help 2>&1 | grep -iE "hook" || echo "no hook in top help"
|
|
codex exec --help 2>&1 | grep -iE "hook" || echo "no hook flag in exec"
|
|
```
|
|
|
|
- [ ] **Step 2: 캡처용 probe 훅으로 실제 payload 덤프**
|
|
|
|
`.codex/hooks.json` 에 임시 probe(stdin 을 파일로 덤프 후 allow)를 걸고 codex 로 1개 툴을 실행시켜 payload 를 캡처:
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
mkdir -p .codex
|
|
cat > /tmp/codex-hook-probe.py <<'PY'
|
|
import sys, json, pathlib
|
|
data = sys.stdin.read()
|
|
pathlib.Path("/tmp/codex-hook-payload.json").write_text(data)
|
|
sys.exit(0) # allow
|
|
PY
|
|
cat > .codex/hooks.json <<'JSON'
|
|
{ "hooks": { "PreToolUse": [ { "matcher": "*",
|
|
"hooks": [ { "type": "command", "command": "python3 /tmp/codex-hook-probe.py", "timeout": 30 } ] } ] } }
|
|
JSON
|
|
# codex 로 안전한 read-only 작업 한 번 실행 (sandbox read-only, 승인 never)
|
|
codex exec -s read-only -C /home/donghyeon/dev/llm-wiki-private "list the files in the repo root with ls" 2>&1 | tail -5 || true
|
|
echo "--- captured payload ---"; cat /tmp/codex-hook-payload.json 2>/dev/null || echo "no payload captured"
|
|
```
|
|
캡처된 JSON 의 필드(`tool_name`? `tool_call.name`? `hook_event_name`?)를 노트에 기록.
|
|
|
|
- [ ] **Step 3: 차단 규약 확인**
|
|
|
|
probe 를 `sys.exit(2)` + stderr 로 바꿔 codex 가 툴을 *차단*하는지, 아니면 stdout JSON(`{"decision":"deny"}`)을 요구하는지 확인(공식 hooks 문서 + 실측). 결과를 노트에 기록.
|
|
|
|
- [ ] **Step 4: 정리 + 노트 커밋**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
rm -f .codex/hooks.json # probe 제거 (Task 3 에서 정식 작성)
|
|
git add docs/superpowers/notes/2026-06-04-phase2-codex-hook-schema.md
|
|
git commit -m "docs(phase2): capture codex hook payload + block convention (empirical)"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: `.codex/hooks.json` 배선 (+ 필요 시 claim_gate codex-variant)
|
|
|
|
**Files:**
|
|
- Create: `.codex/hooks.json`
|
|
- Modify (조건부): `.claude/hooks/wiki_claim_gate.py` (codex payload·차단 variant — Task 2 결과가 Claude 규약과 다를 때만)
|
|
|
|
- [ ] **Step 1: `.codex/hooks.json` 작성**
|
|
|
|
Task 2 에서 확인한 이벤트명/matcher 로:
|
|
```json
|
|
{
|
|
"hooks": {
|
|
"PreToolUse": [
|
|
{ "matcher": "Edit|Write|MultiEdit",
|
|
"hooks": [ { "type": "command",
|
|
"command": "python3 .codex/../.claude/hooks/wiki_claim_gate.py",
|
|
"timeout": 30 } ] } ],
|
|
"PostToolUse": [
|
|
{ "matcher": "Edit|Write|MultiEdit",
|
|
"hooks": [ { "type": "command",
|
|
"command": "python3 .codex/../.claude/hooks/wiki_structure_lint.py --hook",
|
|
"timeout": 30 } ] } ]
|
|
}
|
|
}
|
|
```
|
|
> command 경로는 Task 2 에서 codex 훅의 cwd 가 repo root 인지 확인 후 결정: repo-root 이면 `python3 .claude/hooks/...` (상대), 아니면 절대경로 `python3 /home/donghyeon/dev/llm-wiki-private/.claude/hooks/...`. **하드코딩 절대경로는 이식성 해치므로 codex 가 제공하는 project-dir 변수(`$CODEX_PROJECT_DIR` 등, Task 2 확인)를 우선 사용.** matcher 정규식은 codex 의 실제 tool 이름(Task 2)으로 맞춤 — codex tool 이름이 `shell`/`apply_patch` 등이면 그에 맞게 조정.
|
|
|
|
- [ ] **Step 2: claim_gate codex-variant (조건부)**
|
|
|
|
Task 2 가 "codex 차단 = exit 2 / 입력 = `tool_call.name`" 으로 Claude 와 호환이면 **스크립트 수정 불필요**(이미 멀티-variant 리더). 다르면(예: codex 가 stdout JSON 요구) `wiki_claim_gate.py` 에 codex 분기 추가:
|
|
- 입력: `hook_event_name`/`tool_name` 없으면 codex 필드에서 추출.
|
|
- 출력: codex 차단 규약에 맞춰 emit. **기존 Claude/antigravity 경로 회귀 없이** 분기.
|
|
TDD: codex payload 샘플(Task 2 캡처)을 fixture 로 한 단위 테스트 추가.
|
|
|
|
- [ ] **Step 3: 실호출 검증**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
# codex 가 구조 위반 파일 저장을 차단/경고하는지 (read-only 가 아닌 workspace-write 로 한 번)
|
|
codex exec -s workspace-write -C /home/donghyeon/dev/llm-wiki-private "create a file raw/branch-notes/zzz-hook-test.md with body 'no frontmatter'" 2>&1 | tail -8 || true
|
|
ls raw/branch-notes/zzz-hook-test.md 2>&1 # claim_gate 가 frontmatter 없는 raw 를 막았는지
|
|
rm -f raw/branch-notes/zzz-hook-test.md
|
|
```
|
|
기대: claim_gate 규칙에 따라 차단되거나 경고. 결과를 노트에 기록.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add .codex/hooks.json .claude/hooks/wiki_claim_gate.py docs/superpowers/notes/
|
|
git commit -m "feat(codex): wire .codex/hooks.json (claim gate + structure lint) reusing .claude/hooks scripts"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 4: 프로젝트 지침 DRY 구성 (codex AGENTS.md / config)
|
|
|
|
**Files:**
|
|
- Create: `.codex/config.toml`
|
|
- Create (조건부): `AGENTS.md`
|
|
|
|
- [ ] **Step 1: config fallback 시도 (DRY 우선)**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
cat > .codex/config.toml <<'TOML'
|
|
# codex 가 CLAUDE.md 를 프로젝트 지침으로 읽게 함 (SSOT 중복 방지)
|
|
project_doc_fallback_filenames = ["CLAUDE.md"]
|
|
TOML
|
|
# 확인: codex 가 CLAUDE.md 를 지침으로 로드하는지
|
|
codex exec -s read-only -C /home/donghyeon/dev/llm-wiki-private "이 저장소의 운영 규칙 문서 이름과 첫 섹션 제목을 말해줘" 2>&1 | tail -8 || true
|
|
```
|
|
codex 가 CLAUDE.md 내용을 인지하면 **AGENTS.md 불필요**(DRY 달성). 노트 기록.
|
|
|
|
- [ ] **Step 2: fallback 미동작 시 thin AGENTS.md (대안)**
|
|
|
|
config fallback 이 안 먹으면 root 에 thin pointer 작성(중복 본문 금지):
|
|
```markdown
|
|
# AGENTS.md
|
|
|
|
이 저장소의 운영 규칙 SSOT 는 `CLAUDE.md` 다. 모든 agent/명령은 `CLAUDE.md` 와 `rules/` 를 정독한다.
|
|
(이 파일은 codex/antigravity 가 AGENTS.md 를 우선 탐색할 때의 포인터일 뿐, 규칙 본문을 중복하지 않는다.)
|
|
```
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add .codex/config.toml AGENTS.md 2>/dev/null; git add .codex/config.toml
|
|
git commit -m "feat(codex): read CLAUDE.md as project instructions via config fallback (DRY, no duplication)"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 5: antigravity hooks 커버리지 점검 (보강 여부 결정)
|
|
|
|
**Files:**
|
|
- Modify (조건부): `.agents/hooks.json`
|
|
|
|
- [ ] **Step 1: hard_gate 가 structure-lint/claim-gate 를 이미 커버하는지 판정**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
grep -nE "frontmatter|Parent|Claim|structure|template|raw/branch|decision" ~/.gemini/antigravity-cli/hooks/wiki_hard_gate.py | head
|
|
```
|
|
`wiki_hard_gate.py` 가 이미 claim/구조 게이트를 antigravity 스키마로 수행 중이면(현재 그렇게 보임) **추가 배선 불필요** — 그 사실을 노트에 명시하고 종료. 빠진 검사(예: structure_lint 의 C2)가 있고 antigravity 에서 필요하면 PreToolUse 항목 추가(단 antigravity `Stop` 은 응답 텍스트 못 봄 → content gate 는 PreToolUse 만).
|
|
|
|
- [ ] **Step 2: (보강 시) `.agents/hooks.json` 갱신 + Commit. (불필요 시) 노트만 커밋.**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
git add docs/superpowers/notes/ .agents/hooks.json 2>/dev/null
|
|
git commit -m "docs(phase2): antigravity hook coverage assessment (hard_gate already covers claim/structure)" || echo "nothing to commit"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 6: CLAUDE.md / 문서 잔여 정리
|
|
|
|
**Files:**
|
|
- Modify: `CLAUDE.md`
|
|
|
|
- [ ] **Step 1: §2 표 + 잔여 카운트 정리**
|
|
|
|
- §2 디렉터리 역할 표(line 48 부근 `.claude/commands/`)에 "codex `.agents/skills/` · antigravity `.agents/workflows/` 로 동기화(`scripts/sync_automation.py commands`)" 1줄.
|
|
- 자동화 섹션의 "동일 7개 agent" → "9개 agent" (branch-depth-auditor·coverage-auditor 추가 반영). antigravity bullet 도 동일.
|
|
- (Phase 0~1 에서 이미 codex bullet·sync 경로는 갱신됨 — 누락분만.)
|
|
|
|
- [ ] **Step 2: 최종 동기화 sanity**
|
|
|
|
```bash
|
|
cd /home/donghyeon/dev/llm-wiki-private
|
|
python3 scripts/sync_automation.py agents --check && python3 scripts/sync_automation.py commands --check && echo "ALL DRIFT-0"
|
|
.venv/bin/python -m pytest scripts/test_sync_automation.py -q | tail -2
|
|
grep -rn "convert-wiki-agents" . --include=*.md 2>/dev/null | grep -v docs/superpowers || echo "no dead convert-script refs"
|
|
```
|
|
Expected: `ALL DRIFT-0`, 테스트 green, dead ref 없음.
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add CLAUDE.md
|
|
git commit -m "docs: CLAUDE.md final cleanup — 9 agents, commands cross-platform mapping (Phase 2)"
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2 완료 기준 (Definition of Done)
|
|
|
|
- claim_gate 하드코딩 ROOT 버그 수정(이식성).
|
|
- codex 훅 payload·차단 규약 경험적 캡처 + `.codex/hooks.json` 배선 동작(실호출 검증).
|
|
- codex 가 CLAUDE.md 를 프로젝트 지침으로 읽음(config fallback) 또는 thin AGENTS.md.
|
|
- antigravity 훅 커버리지 판정(보강 또는 "이미 커버" 명시).
|
|
- CLAUDE.md 잔여(카운트·매핑) 정리, dead ref 0.
|
|
- `agents`/`commands` 모두 drift-0, 테스트 green.
|
|
|
|
이로써 **3-플랫폼 동기화 전체 완료**: agents(9)·commands(13)·hooks·project-instructions 가 Codex·Antigravity 에 Claude 와 동등하게 구성됨. 이후: 브랜치 merge/PR (`finishing-a-development-branch`).
|
|
|
|
---
|
|
|
|
## Self-Review (작성자 체크)
|
|
|
|
- **Spec coverage**: 설계 §4 Phase 2 의 모든 항목 — codex hooks.json, AGENTS.md/지침, antigravity hooks 보강, CLAUDE.md 정리 — Task 1~6 에 매핑. 추가로 발견된 claim_gate 하드코딩 ROOT 버그를 Task 1 로 선수정(공용 이득).
|
|
- **Placeholder scan**: codex 훅 payload/차단 규약은 *미지(unknown)* 라 Task 2 에서 경험적 캡처 후 Task 3 가 그 결과에 분기하도록 구성(추측 금지). config fallback 동작 여부도 Task 4 에서 실측 후 분기.
|
|
- **Type/일관성**: 훅 스크립트 SSOT 1벌(`.claude/hooks/`) 원칙 유지, 플랫폼 config 가 가리키기만 함. codex command/agent 생성기(Phase 0~1)와 독립.
|
|
- **알려진 한계**: codex 훅의 정확한 payload/차단 규약이 docs 로 확정 안 됨 → Task 2 경험적 캡처에 의존. codex 가 설치돼 있어 실측 가능(미설치였으면 BLOCKED). antigravity 훅은 이미 hard_gate 로 커버될 가능성이 높아 Task 5 는 "판정 우선, 보강은 조건부".
|